GBIF Name Parser

The core GBIF scientific name parser library.
https://github.com/gbif/name-parser

Category: Biosphere
Sub Category: Biodiversity Data Cleaning and Standardization

Keywords from Contributors

biodiversity-informatics darwin-core taxonomy gbif tdwg biodiversity species snapshot interest-group

Last synced: about 8 hours ago
JSON representation

Repository metadata

The core GBIF scientific name parser library

README.md

GBIF Name Parser API

The data model and parser contract for scientific names — ParsedName,
Authorship, Rank, NomCode, NameType, the NameParser interface and the
ParseResult it returns — plus name formatting and rank/unicode utilities.

As of 5.0.0 this repository is API-only: it ships the single
name-parser-api artifact. The parsing engine has been reimplemented in Rust
(with the full regression corpus), and is consumed through this interface via a
native binding. The previous Java implementation (NameParserImpl and the
name-parser / name-parser-cli modules) lives on the 4.x branch for
maintenance; see Migrating from 4.x to 5.0.

What's in here

Type Purpose
ParsedName, ParsedAuthorship, Authorship the structured name / authorship model
Rank, NomCode, NameType, NamePart the controlled vocabularies
NameParser the parser contract — returns a ParseResult
ParseResult (Parsed | Informal | Unparsable) the three-way parse outcome; see below
UnparsableNameException unchecked; raised only by ParseResult.orElseThrow()
NameFormatter render a ParsedName, an Informal, or a whole ParseResult (also any CombinedAuthorshipIF) back to a string
RankUtils, UnicodeUtils rank relationships and unicode/homoglyph helpers

Library use

<dependency>
  <groupId>org.gbif</groupId>
  <artifactId>name-parser-api</artifactId>
  <version>5.0.0</version>
</dependency>

parse(...) never throws — it returns a sealed, three-way ParseResult:

  • Parsed — a fully structured ParsedName (its state() may be COMPLETE or PARTIAL).
  • Informal — a semistructured name: a real supraspecific taxon carrying a provisional,
    non-code designation instead of a determined species epithet — a molecular provisional species
    (Rhizobium sp. RMCC TR1811), a numbered placeholder (Allium sp. 1), or an informal group
    (Bartonella group). It is a flat taxon / taxonRank / rank / phrase / code and carries
    no ParsedName — the anchor is unvalidated, so it is never mislabelled as a determined genus.
    Names that keep a species epithet — including cf./aff. and infraspecific-indeterminate ones —
    stay Parsed, so their specificAuthorship (which a flat anchor could not hold) survives.
  • Unparsable — not a scientific name at all: a virus, a hybrid formula, a placeholder, or a
    machine identifier such as a BOLD BIN / UNITE SH / OTU / culture-collection accession. type()
    classifies it — FORMULA, PLACEHOLDER, IDENTIFIER, or OTHER.

type() and code() are available on all three variants, so you can classify without catching
anything:

NameParser parser = new NameParserRust(); // the native (Rust-backed) implementation — see below

switch (parser.parse("Rhizobium sp. RMCC TR1811", null, null, null)) {
  case ParseResult.Parsed p     -> index(p.name());                     // a full ParsedName
  case ParseResult.Informal i   -> indexInformal(i.taxon(), i.phrase()); // "Rhizobium" + "RMCC TR1811"
  case ParseResult.Unparsable u -> record(u.type(), u.code());          // type FORMULA/PLACEHOLDER/IDENTIFIER/OTHER, code e.g. VIRUS
}

Any variant round-trips back to a string via NameFormatter.canonical(result) — the reconstructed
name for Parsed, the informal name (Rhizobium sp. RMCC TR1811) for Informal, the verbatim
input for Unparsable — or call NameFormatter.canonical(informal) on an Informal directly.

It composes in streams:

List<ParsedName> parsed = names.stream()
    .map(n -> parser.parse(n, null, rank, code))
    .flatMap(r -> r.parsed().stream()) // keeps only Parsed; Informal + Unparsable have an empty parsed()
    .toList();

…and offers an opt-in fail-fast path for callers that want it:

ParsedName pn = parser.parse(name).orElseThrow(); // throws unchecked UnparsableNameException

Only need the model or the formatter (no parsing)? Depend on this artifact and
use ParsedName / NameFormatter directly — no implementation required.

The parsing engine (Rust)

From 5.0 the reference implementation lives in a separate project,
gbif/name-parser-rust. The
parser core is a Rust crate (nameparser), which also carries the full
regression corpus ported from the old Java suite, and is exposed through several
bindings — a C-ABI cdylib (nameparser-ffi) and a Python module
(nameparser-py).

Java callers get a NameParser from that project's Panama binding:
org.gbif.nameparser.rust.NameParserRust implements
org.gbif.nameparser.api.NameParser from this module by downcalling the Rust
cdylib in-process via java.lang.foreign (FFM/Panama, stable since JDK 22 — no
--enable-preview). Each parse marshals across the FFI boundary and rebuilds
a ParsedName from a flat binary struct. Because java.lang.foreign needs a
modern JDK, that binding targets JDK 25+ and is
built and released independently of this Java-17 API module — which is exactly
why the API stays on 17: model- and formatter-only consumers keep the broad
baseline, while only the native binding requires the newer JDK.

Depending on the Rust binding (to actually parse)

This api artifact carries no parser on its own. For a working NameParser, add the native
binding — org.gbif.nameparser:name-parser-rust, from
gbif/name-parser-rust — which pulls this
name-parser-api in transitively. It ships as a thin main JAR plus one native classifier JAR per
platform (netty-tcnative style), so you download only your own architecture's cdylib. Requires
JDK 25+.

<build><extensions>
  <!-- resolves ${os.detected.classifier}: linux-x86_64, osx-aarch_64, windows-x86_64, … -->
  <extension>
    <groupId>kr.motd.maven</groupId>
    <artifactId>os-maven-plugin</artifactId>
    <version>1.7.1</version>
  </extension>
</extensions></build>

<dependencies>
  <dependency>                    <!-- thin main JAR: Java + FFM loader (brings name-parser-api) -->
    <groupId>org.gbif.nameparser</groupId>
    <artifactId>name-parser-rust</artifactId>
    <version>0.1.0-SNAPSHOT</version>
  </dependency>
  <dependency>                    <!-- your platform's native cdylib -->
    <groupId>org.gbif.nameparser</groupId>
    <artifactId>name-parser-rust</artifactId>
    <version>0.1.0-SNAPSHOT</version>
    <classifier>${os.detected.classifier}</classifier>
  </dependency>
</dependencies>

The binding is published to GBIF's Nexus; if your build doesn't already resolve from it, add:

<repositories>
  <repository>
    <id>gbif-all</id>
    <url>https://repository.gbif.org/content/groups/gbif</url>
  </repository>
</repositories>

0.1.0-SNAPSHOT is auto-deployed on every push to main (a released 0.1.0 will follow); the
binding tracks the Rust engine's own version line, independent of this api's 5.0.0. Then just
construct it — no other wiring, and the same NameParser interface as before:

NameParser parser = new org.gbif.nameparser.rust.NameParserRust();

Migrating from 4.x to 5.0

5.0 keeps the same model but changes the parser contract and drops the bundled
Java engine.

  • parse(...) returns a three-way ParseResult, not ParsedName, and no longer throws.
    Replace try { ParsedName pn = parser.parse(…); } catch (UnparsableNameException e) { … }
    with a switch over Parsed | Informal | Unparsable, or parser.parse(…).parsed() /
    .orElseThrow(). type() and code() are on all three variants, so failure
    classification no longer needs the exception.
  • Informal / semistructured names are now their own Informal result, not a ParsedName.
    Names that 4.x returned as a ParsedName with type = INFORMAL and no species epithet — a
    supraspecific taxon plus a provisional designation (Genus sp. <tag>, Bartonella group) — now
    come back as a flat ParseResult.Informal (taxon / taxonRank / rank / phrase / code)
    that carries no ParsedName. A switch that previously handled only Parsed and Unparsable
    must add the Informal arm. (Names that keep a species epithet — cf./aff., strain, infraspecific
    indet — stay Parsed as before.)
  • parseAuthorship(...) returns Optional<ParsedAuthorship> instead of
    throwing.
  • UnparsableNameException is now unchecked (extends RuntimeException) and
    is only raised by ParseResult.orElseThrow().
  • The Java parser is gone from this repo. new NameParserImpl() no longer
    exists in 5.0 — obtain a NameParser from the native binding instead. If you
    need the pure-Java engine, stay on the 4.x branch (name-parser 4.2.x),
    which keeps the old throwing API and NameParserImpl.

Model/vocabulary changes introduced across the 4.x line (still current in 5.0)
are documented in the 4.x branch README — notably NameType.VIRUS/OTU
removal (viruses now carry code = VIRUS), Rank.DIVISIONDIVISION_ZOOLOGY
plus a new DIVISION_BOTANY, imprint years moving to Authorship, and the
CombinedAuthorshipIF / publishedInYear / generic-&-specific-authorship
additions.

Build

mvn install from the repo root — a single-module Java 17 build.

License

Apache 2.0.


Owner metadata


GitHub Events

Total
Last Year

Committers metadata

Last synced: 5 days ago

Total Commits: 729
Total Committers: 14
Avg Commits per committer: 52.071
Development Distribution Score (DDS): 0.246

Commits in past year: 157
Committers in past year: 2
Avg Commits per committer in past year: 78.5
Development Distribution Score (DDS) in past year: 0.115

Name Email Commits
Markus Döring m****g@g****g 550
gbif-jenkins d****v@g****g 102
gbif-jenkins j****s@r****g 30
gbif-jenkins j****s@j****g 18
pal155 D****r@c****u 8
Federico Mendez f****z@g****g 7
Oliver Meyn o****r@m****m 3
Kyle Braak k****k@g****g 3
dependabot[bot] 4****] 2
Matthew Blissett m****t@g****g 2
Thomas Stjernegaard Jeppesen t****n@g****g 1
Nikolay Volik n****k@g****g 1
Christian Gendreau c****u 1
Jorrit Poelen j****n@g****m 1

Committer domains:


Issue and Pull Request metadata

Last synced: about 1 month ago

Total issues: 4
Total pull requests: 3
Average time to close issues: 1 day
Average time to close pull requests: almost 2 years
Total issue authors: 3
Total pull request authors: 2
Average comments per issue: 1.25
Average comments per pull request: 0.33
Merged pull request: 0
Bot issues: 0
Bot pull requests: 3

Past year issues: 3
Past year pull requests: 1
Past year average time to close issues: 2 days
Past year average time to close pull requests: N/A
Past year issue authors: 3
Past year pull request authors: 1
Past year average comments per issue: 1.33
Past year average comments per pull request: 0.0
Past year merged pull request: 0
Past year bot issues: 0
Past year bot pull requests: 1

More stats: https://issues.ecosyste.ms/repositories/lookup?url=https://github.com/gbif/name-parser

Top Issue Authors

  • djtfmartin (2)
  • CecSve (1)
  • mdoering (1)

Top Pull Request Authors

  • dependabot[bot] (2)
  • renovate[bot] (1)

Top Issue Labels

  • bug (1)

Top Pull Request Labels

  • dependencies (2)
  • java (1)

Dependencies

name-parser/pom.xml maven
  • com.google.guava:guava
  • commons-io:commons-io
  • org.apache.commons:commons-lang3
  • org.gbif:name-parser-api
  • org.slf4j:slf4j-api
  • ch.qos.logback:logback-classic test
  • junit:junit test
  • org.gbif:name-parser-api test
name-parser-api/pom.xml maven
  • com.google.code.findbugs:jsr305
  • com.google.guava:guava
  • org.apache.commons:commons-lang3
  • org.slf4j:slf4j-api
  • ch.qos.logback:logback-classic test
  • commons-io:commons-io test
  • junit:junit test
pom.xml maven
  • com.google.code.findbugs:jsr305 3.0.2
  • com.google.guava:guava 28.0-jre
  • commons-io:commons-io 2.8.0
  • org.apache.commons:commons-lang3 3.12.0
  • org.gbif:gbif-api 0.166
  • org.gbif:name-parser 3.7.3-SNAPSHOT
  • org.gbif:name-parser-api 3.7.3-SNAPSHOT
  • org.gbif:name-parser-gbif 3.7.3-SNAPSHOT
  • org.slf4j:slf4j-api 1.7.24
  • ch.qos.logback:logback-classic 1.2.3 test
  • junit:junit 4.12 test
  • org.gbif:name-parser-api 3.7.3-SNAPSHOT test
name-parser-cli/pom.xml maven
  • ch.qos.logback:logback-classic *
  • com.google.code.gson:gson *
  • org.catalogueoflife:coldp *
  • org.gbif:name-parser *
  • org.slf4j:slf4j-api *
  • junit:junit * test

Score: 6.915723448631313