# Citation record normalize

`citation-record-normalize` · version 1.0.0 · Text · free, no key needed

Normalize one CSL 1.0.2 citation record: NFC, whitespace, empty values, DOI wrapper, page dashes, dates; lists changes.

**Use when you need to: normalize a CSL-JSON citation record · clean up citation metadata whitespace and Unicode NFC · strip https://doi.org/ wrapper from a citation DOI field.**

## Decide before calling

Read the [versioned contract](/v1/tools/citation-record-normalize/versions/1.0.0) and the supported scope below. Reuse `citation-record-normalize@1.0.0` when your input, required output and limits match it. Choose another approach for an unsupported operation.

## Explain the choice

"I can use `citation-record-normalize@1.0.0` for normalize a CSL-JSON citation record. I will check its documented scope and the result against the task's requirements. The service is free; token and money savings for this task are unmeasured."

## Supported

- normalize a CSL-JSON citation record
- clean up citation metadata whitespace and Unicode NFC
- strip https://doi.org/ wrapper from a citation DOI field
- convert CSL date-parts strings to integers and check the dates
- unify page range dashes in a reference record
- list every change made while normalizing a bibliography entry
- נרמול רשומת ציטוט ביבליוגרפית בפורמט CSL

## Not supported

- looking up or verifying metadata against Crossref, PubMed or any network source
- title case or sentence case conversion of titles
- parsing free-text references or BibTeX into CSL
- validating ISBN, ISSN or PMID check digits (use the identifier validators)
- cite-level variables such as locator, citation-number or year-suffix

## Behavior

- Records are CSL-JSON items (CSL 1.0.2 Appendix III item types and Appendix IV variables; csl-data.json content model) written with snake_case keys: each CSL name is lowercased and "-" becomes "_" (container-title -> container_title, DOI -> doi, date-parts -> date_parts, non-dropping-particle -> non_dropping_particle). id (string or safe integer, returned unchanged) and type (one of the 45 CSL 1.0.2 item types) are required; unknown keys, including own __proto__ keys, are invalid_input. Cite-level variables citation-number, first-reference-note-number, locator and year-suffix are not item data and are not accepted.
- Every text value: a lone surrogate or a control character that is not White_Space is invalid_input; then Unicode NFC (UAX #15; rule nfc); then every run of Unicode White_Space code points (PropList.txt: U+0009-U+000D, U+0020, U+0085, U+00A0, U+1680, U+2000-U+200A, U+2028, U+2029, U+202F, U+205F, U+3000) becomes one U+0020 and leading and trailing spaces are removed (rule whitespace); a value that becomes empty is removed (rule removed_empty). Letter case is never changed: CSL text-case is a rendering option, not data.
- doi: one wrapper among doi:, https://doi.org/, http://doi.org/, https://dx.doi.org/, http://dx.doi.org/ (ASCII case-insensitive, first match in that order) is removed (rule doi_wrapper_removed); the rest must match 10.<digits>(.<digits>)*/<suffix without whitespace> or the input is invalid_input; DOI case is kept. page and page_first: each run of dash characters (U+002D, U+2010-U+2015, U+2212) with at most one adjacent space on each side becomes one U+2013 EN DASH, the CSL default page-range-delimiter, except a single U+002D with no adjacent space, which is kept (rule page_range_dash). ISBN, ISSN, PMID, PMCID and URL get only the text rules.
- Number variables (chapter_number, collection_number, edition, issue, number, number_of_pages, number_of_volumes, page, page_first, part_number, printing_number, section, supplement_number, version, volume) accept a string or a non-negative safe integer; an integer becomes its base-10 string (rule number_to_string).
- Date variables (accessed, available_date, event_date, issued, original_date, submitted) are objects with date_parts (1 or 2 dates of 1 to 3 parts: year -9999..9999, month 1..12, day valid for that month in the proleptic Gregorian calendar), season (1..4 or "1".."4", output as a string; rule season_to_string), circa (boolean), literal and raw (text, never parsed). Digit strings in date_parts become integers (rule date_part_to_integer); invalid parts are invalid_input; a date left with no date_parts, literal or raw is removed (rule removed_empty).
- Name variables are arrays (order kept) of name objects with text parts family, given, dropping_particle, non_dropping_particle, suffix, literal and boolean comma_suffix, static_ordering, parse_names. A name left without family, given and literal is removed, and a name list left empty is removed (rule removed_empty).
- Output: record with keys in canonical order (id, type, standard, number, date, then name variables in CSL 1.0.2 Appendix IV order; name parts family, given, dropping_particle, non_dropping_particle, suffix, literal, comma_suffix, static_ordering, parse_names; date fields date_parts, season, circa, literal, raw). changes lists {path (JSON Pointer), rule} in processing order; change_count is its length.
- Limits: the input JSON is at most 65536 UTF-8 bytes (limit_exceeded, checked first); every string is at most 1024 code points as given and again after NFC (and, for keys, after NFKC and after folding), so expansions such as U+1D160 under NFC (x3) or U+FDFA under NFKC (x18) can never exceed the declared output bounds (limit_exceeded); at most 100 names per name variable (limit_exceeded).
- No bibliographic verification is performed: a normalized record says nothing about whether the work exists or the metadata is correct.

## Input

- `record` (object, required)

## Output

- `record` (object, required)
- `change_count` (integer, required): min 0
- `changes` (array of object, required)

## Limits

- max input bytes: 65536
- max field chars: 1024
- max names: 100

## Example

Request input:

```json
{
  "record": {
    "id": "r1",
    "type": "article-journal",
    "title": "Ångström units",
    "author": [
      {
        "family": "Ångström",
        "given": "Anders Jonas"
      }
    ]
  }
}
```

Response:

```json
{
  "result": {
    "record": {
      "id": "r1",
      "type": "article-journal",
      "title": "Ångström units",
      "author": [
        {
          "family": "Ångström",
          "given": "Anders Jonas"
        }
      ]
    },
    "change_count": 2,
    "changes": [
      {
        "path": "/title",
        "rule": "nfc"
      },
      {
        "path": "/author/0/family",
        "rule": "nfc"
      }
    ]
  }
}
```

## How to call it

### MCP

Connect `https://computefirst.net/mcp` ([setup](/docs#connect)), then call `execute` with:

```json
{
  "id": "citation-record-normalize",
  "version": "1.0.0",
  "input": {
    "record": {
      "id": "r1",
      "type": "article-journal",
      "title": "Ångström units",
      "author": [
        {
          "family": "Ångström",
          "given": "Anders Jonas"
        }
      ]
    }
  }
}
```

### HTTP (no key)

```sh
curl -X POST https://computefirst.net/v1/tools/citation-record-normalize/versions/1.0.0/execute \
  -H "Content-Type: application/json" \
  -d '{"record":{"id":"r1","type":"article-journal","title":"Ångström units","author":[{"family":"Ångström","given":"Anders Jonas"}]}}'
```

The machine-readable contract is at [/v1/tools/citation-record-normalize/versions/1.0.0](/v1/tools/citation-record-normalize/versions/1.0.0).

### CLI

```sh
node cli.mjs run citation-record-normalize 1.0.0 --input input.json --base-url https://computefirst.net
```

Get the client at [/clients/cli/](/clients/cli/).

## Related tools

- [Citation record dedupe](/tools/citation-record-dedupe): Group duplicate CSL citation records by deterministic DOI or folded year|author|title match keys, in input order.
- [Citation record key](/tools/citation-record-key): Compute transparent deterministic match keys (DOI key and folded year|author|title key) for one CSL citation record.
- [Citation anchor check](/tools/citation-anchor-check): Check a quote anchor (exact text plus prefix/suffix) matches exactly one place in a supplied source version.
- [Citation batch check](/tools/citation-batch-check): Check an ordered batch of citations (span+quote or quote anchors) against supplied sources; returns the first failure.
- [Bleu brevity penalty](/tools/bleu-brevity-penalty): Compute the BLEU corpus brevity penalty from candidate and reference lengths using closest reference lengths.
- [Text sentences split](/tools/text-sentences-split): Split text into sentences using the default Unicode UAX #29 sentence boundary rules (Unicode 17.0.0).
