diff --git a/CHANGELOG.md b/CHANGELOG.md index f72b6dd..bde57bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The Playground demos (release, main build, and WordPress.org Preview) open on a demo page with copy-ready samples of every accepted input format, an empty Bibliography block to paste them into, brief notes on the key features, and three example bibliographies (Chicago notes-bibliography with Cite / Export, APA 7, IEEE). The Welcome Guide no longer covers the page. The page is defined once in `playground/demo-content.json` and `playground/demo-content.php`, and `npm run playground:build` writes it into every Blueprint. +### Changed + +- The nine citation styles and their locales are rewritten in full from each style manual: + - Chicago notes-bibliography and author-date (17th ed.); + - APA 7; + - MLA 9; + - Harvard (Cite Them Right); + - IEEE; + - Vancouver (*Citing Medicine*); + - OSCOLA; + - ABNT NBR 6023. + + The earlier files were about 2 KB stand-ins. They initialized every given name and dropped volume, issue, pages, editors, translators, editions, and access dates. Every manual's author-list rules now apply: "and" or "&", "et al." thresholds, and APA's 21+ ellipsis. So do its journal, chapter, webpage, thesis, report, conference-paper, and preprint forms. + + The en-US, en-GB, and pt-BR locales now carry month names, quotation marks, and role terms. pt-BR was an English copy, so ABNT printed "and" and "accessed"; it is now written in Portuguese. Harvard now uses British conventions. + + The styles are still written for Borges and licensed GPL-2.0-or-later, not copied from the CC BY-SA CSL repository. `docs/csl-styles.md` lists each source and the few deliberate deviations. + + Existing bibliographies keep their saved text until an entry is added or edited or the style is changed. + ### Fixed +- Organization authors (CSL `literal` names, such as "Open Research Alliance") no longer vanish from formatted entries. +- An entry after one shortened to "et al." no longer loses the "and" before its last author. Each entry is now formatted on its own. - A bibliography block no longer opens as invalid ("Attempt Block Recovery") when an editor using a different language from the one who saved it opens the post. `save()` used to write the Cite / Export labels ("Cite / Export", "Copy citation", "Copied", "RIS", "CSL-JSON", "BibTeX", "BibLaTeX") and the "Link to publication" link label into post content in the saving editor's language. The editor checks saved markup against `save()` in the *current* editor's language, so the two did not match. Saved markup is now the same in every language: the panel labels are stored in English and translated for visitors on the server as the block renders (`includes/frontend-labels.php`, a `render_block` filter), so they are translated without JavaScript and the view script loads no `wp-i18n`, and a link whose citation has no title or container title carries no `aria-label`, so screen readers announce its visible URL. Existing posts still validate in any language, because the new deprecation reads the labels back from the saved markup. The exception is blocks saved in the oldest markup shapes (before the biblioentry role was removed) that contain a linked URL in a citation with no title or container title: those still validate only in the language they were saved in, as before. They switch to the new markup the next time they are saved. The PHP port of `save()` (`includes/save-markup.php`) matches. - In all 19 bundled translations, seven strings were compiled as "Add citations" even though they mean something else: "Copy citation", "Copy citation: %s", "Copied citation.", "Edit citation: %s", "Delete citation: %s", "Added 1 citation.", and "Added 1 citation. %s". These were stale fuzzy matches, and `wp i18n make-mo` compiles fuzzy entries. The translations are cleared so these strings fall back to English, and the `.mo` files are rebuilt. - Pasting a whole reference-manager `.bib` export no longer loses or misreads records: diff --git a/README.md b/README.md index a012a06..9814406 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ Borges is a static-output block: formatted bibliography HTML, JSON-LD, and COinS | Metric | Value | |---|---| -| First-party PHP | ~1,978 LOC main plugin file; ~6,428 LOC total with `includes/` | +| First-party PHP | ~2,064 LOC main plugin file; ~6,514 LOC total with `includes/` | | JS source (`src/`) | ~9,956 LOC | | Frontend runtime shipped to visitors | `view.js` ~1.4 KB + `style-index.css` ~2.9 KB, no script dependencies, enqueued only when the block is present | | Installed footprint | ~1.9 MB (`vendor/` ~792 KB, translations 724 KB, build assets ~324 KB) | diff --git a/SPEC.md b/SPEC.md index 2191b2d..9c82853 100644 --- a/SPEC.md +++ b/SPEC.md @@ -482,7 +482,7 @@ These handle: - `plugin-doi`: DOI string → CSL-JSON fallback support when direct CrossRef fetch is unavailable - `plugin-bibtex`: BibTeX string → CSL-JSON -Formatted bibliography strings are generated locally through `citeproc-php` using plugin-owned GPL-compatible CSL style and locale fixtures. Do not bundle the official CSL style or locale repositories in the WordPress.org release package. +Formatted bibliography strings are generated locally through `citeproc-php` using plugin-owned GPL-2.0-or-later CSL styles and locales, written from each style manual's rules (see `docs/csl-styles.md`). Do not bundle the official CSL style or locale repositories in the WordPress.org release package. --- diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index a0ca6ad..f8200af 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -7,6 +7,6 @@ Borges Bibliography Builder is licensed GPL-2.0-or-later. The release package in - `myclabs/php-enum` — MIT License — https://github.com/myclabs/php-enum - `@citation-js/core`, `@citation-js/plugin-doi`, and `@citation-js/plugin-bibtex` are used at build time/editor runtime through the bundled editor script and are MIT licensed — https://citation.js.org/ -The Composer path packages named `citation-style-language/styles` and `citation-style-language/locales` in this repository are **not** the official CSL project packages. They contain project-authored, minimal CSL style and locale fixtures licensed GPL-2.0-or-later for this plugin. They are included so the WordPress.org release does not bundle the official CC BY-SA CSL style or locale repositories. +The Composer path packages named `citation-style-language/styles` and `citation-style-language/locales` in this repository are **not** the official CSL project packages. They contain CSL styles and locales written for this plugin from the style manuals' own rules (see docs/csl-styles.md), licensed GPL-2.0-or-later. They are included so the WordPress.org release does not bundle the official CC BY-SA CSL style or locale repositories. The source tree and release package must not include dependencies or files licensed only as CPAL-1.0, AGPL-1.0, or CC-BY-SA-3.0. diff --git a/bibliography-builder.php b/bibliography-builder.php index 25e50ca..c617a8a 100644 --- a/bibliography-builder.php +++ b/bibliography-builder.php @@ -232,7 +232,7 @@ function bibliography_builder_get_formatter_style_definitions() { ), 'harvard' => array( 'template' => 'harvard1', - 'locale' => 'en-US', + 'locale' => 'en-GB', 'family' => 'author-date', ), 'ieee' => array( @@ -1112,6 +1112,27 @@ function bibliography_builder_normalize_formatted_text( $text, $style ) { $text = preg_replace( '/\bp\.\s+p{1,2}\.\s+/u', 'p. ', $text ); } + // citeproc-php puts a space between a name and a label whose prefix is + // ", " ("Lindqvist , eds."); a space before a comma is never wanted. + $text = str_replace( ' ,', ',', $text ); + + // American styles put commas and periods inside closing quotes. citeproc-php + // only does so for an element's own suffix, not for a group delimiter + // (“Title”, in IEEE). + if ( isset( $style['locale'] ) && 'en-US' === $style['locale'] ) { + $text = str_replace( array( '”,', '”.' ), array( ',”', '.”' ), $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( + '/(\p{Lu})(\.?)- (\p{Ll})\p{Ll}*/u', + static function ( $parts ) { + return $parts[1] . $parts[2] . '-' . mb_strtoupper( $parts[3], 'UTF-8' ) . $parts[2]; + }, + $text + ); + return str_replace( 'and et al.', 'et al.', $text ); } @@ -1169,6 +1190,63 @@ function bibliography_builder_extract_citeproc_entries( $html, $style ) { return $entries; } +/** + * Adapt a CSL-JSON item to what citeproc-php 2.7 can render. + * + * - Literal names (organizations, one-name authors): citeproc-php renders a + * name only from its `family` part, so `{ "literal": "Open Research + * Alliance" }` would vanish. As a family-only name it prints whole, never + * inverted or initialized. + * - `page-first`: citeproc-php does not derive it from `page`, and OSCOLA + * cites a journal article by its first page. + * + * @param array $item CSL-JSON item. + * @return array + */ +function bibliography_builder_prepare_csl_for_formatter( $item ) { + $name_variables = array( + 'author', + 'collection-editor', + 'composer', + 'container-author', + 'director', + 'editor', + 'editorial-director', + 'illustrator', + 'interviewer', + 'original-author', + 'recipient', + 'reviewed-author', + 'translator', + ); + + foreach ( $name_variables as $variable ) { + if ( empty( $item[ $variable ] ) || ! is_array( $item[ $variable ] ) ) { + continue; + } + + foreach ( $item[ $variable ] as $position => $name ) { + $is_literal = is_array( $name ) && empty( $name['family'] ) + && isset( $name['literal'] ) && is_string( $name['literal'] ); + $literal = $is_literal ? trim( $name['literal'] ) : ''; + + if ( '' !== $literal ) { + $item[ $variable ][ $position ] = array( 'family' => $literal ); + } + } + } + + if ( empty( $item['page-first'] ) && isset( $item['page'] ) && is_scalar( $item['page'] ) ) { + $first = trim( preg_split( '/[-\x{2013}\x{2014},&]/u', (string) $item['page'] )[0] ); + + if ( '' !== $first ) { + $item['page-first'] = $first; + } + } + + return $item; +} + /** * Format CSL-JSON items as plain-text bibliography entries. * @@ -1216,7 +1294,7 @@ function bibliography_builder_format_csl_items( $csl_items, $style_key ) { $prepared_items = array(); foreach ( array_values( $csl_items ) as $index => $item ) { - $item_array = is_array( $item ) ? $item : array(); + $item_array = bibliography_builder_prepare_csl_for_formatter( is_array( $item ) ? $item : array() ); $item_array['id'] = 'bibliography-builder-format-' . $index; $prepared_items[] = $item_array; } @@ -1246,9 +1324,17 @@ function bibliography_builder_format_csl_items( $csl_items, $style_key ) { ), ); + // One formatter per entry: citeproc-php keeps per-render state on its + // parsed name elements (once one entry is cut to "et al.", later entries + // in the same render lose their "and"), and no style here needs + // cross-entry context such as disambiguation or author substitution. try { - $formatter = new \Seboettg\CiteProc\CiteProc( $style_xml, $style['locale'], $markup_extension ); - $html = $formatter->render( $items_for_formatter, 'bibliography' ); + $html = ''; + + foreach ( $items_for_formatter as $item_for_formatter ) { + $formatter = new \Seboettg\CiteProc\CiteProc( $style_xml, $style['locale'], $markup_extension ); + $html .= $formatter->render( array( $item_for_formatter ), 'bibliography' ); + } } catch ( Throwable $error ) { return new WP_Error( 'bibliography_builder_formatter_failed', diff --git a/docs/csl-styles.md b/docs/csl-styles.md new file mode 100644 index 0000000..42b0f69 --- /dev/null +++ b/docs/csl-styles.md @@ -0,0 +1,84 @@ +# Bundled citation styles + +Borges formats bibliographies with `citeproc-php` against nine CSL styles and three locales that live in this repository: + +- styles: `packages/citation-style-language-styles/` +- locales: `packages/citation-style-language-locales/` + +## Why they are project-authored + +The CSL project's official styles and locales are licensed CC BY-SA 3.0, which is not GPL-compatible. WordPress.org requires GPL-compatible code and assets, so Borges must not bundle them (see `THIRD-PARTY-NOTICES.txt` and `SPEC.md`). + +Instead, every style here was written for Borges from its style manual's own rules, and is licensed GPL-2.0-or-later. Style rules themselves are not copyrightable. The official CSL files were not used as a source. + +Earlier releases shipped about 2 KB stand-ins. Those initialized every given name, dropped volume, issue and pages, and ignored editors, translators, editions and access dates. The locales had no month names or quotation marks, and pt-BR was a copy of English. + +## Styles + +| Key | File | Source | Notes | +| --- | --- | --- | --- | +| `chicago-notes-bibliography` | `chicago-notes-bibliography.csl` | Chicago Manual of Style, 17th ed., ch. 14 (bibliography entries) | First author inverted, "and" before the last name, 11+ authors cut to 7 + "et al." | +| `chicago-author-date` | `chicago-author-date.csl` | Chicago Manual of Style, 17th ed., ch. 15 | Year after the author; journal form `27 (2): 97–111` | +| `apa-7` | `apa.csl` | APA Publication Manual, 7th ed., ch. 9–10 | Initials, `&`, up to 20 authors, 21+ as the first 19, "…", and the last; no publisher place | +| `mla-9` | `modern-language-association.csl` | MLA Handbook, 9th ed., ch. 5 | 3+ authors as "First, et al."; MLA month abbreviations (June, July, Sept.) | +| `harvard` | `harvard1.csl` | Cite Them Right, 12th ed. | en-GB; initials without spaces, 4+ authors "et al.", `(eds)`, `edn`, single quotes, "Available at: … (Accessed: …)" | +| `ieee` | `ieee.csl` | IEEE Reference Guide (2023) | Initials first; 7+ authors as first + "et al."; `doi:` form; "[Online]. Available:" | +| `vancouver` | `vancouver.csl` | NLM *Citing Medicine*, 2nd ed. (ICMJE) | `Green S`; 7+ authors as 6 + "et al."; `2020 Apr;27(2):97-111`; "[Internet] … [cited …]" | +| `oscola` | `oscola.csl` | OSCOLA, 4th ed. (bibliography) | en-GB; `Alvarez MI`, 4+ "and others"; `(2020) 27 Journal 97`; `Smith v Jones [2019] UKSC 12`; no final full stop | +| `abnt` | `abnt.csl` | ABNT NBR 6023:2018 | pt-BR; SURNAMES in capitals, `;` between authors, 4+ "et al."; `3. ed.`, `(org.)`, `In:`, `[S. l.]`, `[s. d.]`, "Disponível em: … Acesso em: …" | + +Deviations shared by every style, all forced by how the block stores and renders entries: + +- **Titles keep their stored capitalization.** APA's sentence case is not applied, and neither is ABNT's capitalized first word for title-first entries. The block re-applies italics by finding the stored title text in the formatted entry, so a case-transformed title would lose its italics. +- **URLs and DOIs stay linkable.** The block links `https://` URLs up to the next space. So OSCOLA prints URLs without angle brackets, and ABNT prints DOIs as `https://doi.org/` links. +- **No Chicago 3-em dash for repeated authors.** It depends on list order, and the block sorts entries itself, after formatting. +- **No citation numbers.** Numeric styles number with the list element. +- **Journal titles print as stored.** They are not abbreviated (IEEE, Vancouver). + +## Writing or changing a style + +Follow these conventions. Each one works around citeproc-php 2.7 behavior (see `docs/external-eccentricities.md`): + +1. **Suffix each part with `.` or `,` inside a space-delimited group.** citeproc-php moves punctuation inside a closing quote, and drops a doubled period ("eds.."), only for an element's own suffix, and only when it is exactly `.`, `,` or `;`. It never does this for a group delimiter. +2. **Use `` before the names for role phrases.** A `