Skip to content

Commit 1bfb606

Browse files
committed
Enhance documentation, add new dependencies, and improve data parsing logic
- Update .Rbuildignore to exclude additional files - Add 'withr' to DESCRIPTION suggests - Improve documentation for ORCID API functions, including expanded-search endpoint - Refactor data parsing functions to handle edge cases and avoid duplicates - Add WORDLIST for common terms used in the package - Include 'withr' library in test setup
1 parent 2cc6e41 commit 1bfb606

13 files changed

Lines changed: 176 additions & 97 deletions

File tree

.Rbuildignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,7 @@
1414
^altdoc$
1515
^doc$
1616
^Meta$
17+
^coverage\.html$
18+
^DEVELOPMENT\.md$
19+
^lib$
20+
^TODO\.md$

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Imports:
2121
jsonlite (>= 1.8.0)
2222
Suggests:
2323
testthat (>= 3.0.0),
24+
withr,
2425
knitr,
2526
rmarkdown
2627
Config/testthat/edition: 3

R/api-works.R

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616
#' \item{orcid}{ORCID identifier}
1717
#' \item{put_code}{Unique identifier for this work record}
1818
#' \item{title}{Title of the work}
19-
#' \item{type}{Type of work (e.g., journal-article, dataset, preprint)}
19+
#' \item{type}{Type of work. Common values include: "journal-article",
20+
#' "conference-paper", "conference-poster", "book", "book-chapter",
21+
#' "dissertation", "data-set", "preprint", "report", "working-paper",
22+
#' "other". Use this field to distinguish between different publication types.}
2023
#' \item{publication_date}{Publication date (ISO format)}
2124
#' \item{journal}{Journal or venue name (if available)}
2225
#' \item{doi}{Digital Object Identifier (if available)}
@@ -47,9 +50,11 @@
4750
#' works <- orcid_works("0000-0002-1825-0097")
4851
#' print(works)
4952
#'
50-
#' # Filter by type
51-
#' articles <- works[type == "journal-article"]
53+
#' # Filter by type to distinguish between different publication types
54+
#' journal_articles <- works[type == "journal-article"]
55+
#' conference_posters <- works[type == "conference-poster"]
5256
#' datasets <- works[type == "data-set"]
57+
#' preprints <- works[type == "preprint"]
5358
#'
5459
#' # With authentication
5560
#' Sys.setenv(ORCID_TOKEN = "your-token-here")

R/parsers.R

Lines changed: 98 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -133,54 +133,55 @@ parse_works <- function(json_data, orcid_id) {
133133

134134
records <- lapply(groups, function(group) {
135135
summaries <- safe_extract(group, "work-summary")
136-
if (is.null(summaries)) {
136+
if (is.null(summaries) || length(summaries) == 0) {
137137
return(NULL)
138138
}
139139

140-
lapply(summaries, function(summary) {
141-
if (is.null(summary)) {
142-
return(NULL)
143-
}
140+
# Only take the first work-summary to avoid duplicates
141+
# Multiple work-summaries in a group represent the same work from different sources
142+
summary <- summaries[[1]]
144143

145-
# Extract title
146-
title <- safe_extract(summary, "title", "title", "value")
147-
148-
# Extract DOI from external IDs
149-
doi <- NA_character_
150-
ext_ids <- safe_extract(summary, "external-ids", "external-id")
151-
if (!is.null(ext_ids) && is.list(ext_ids)) {
152-
for (ext_id in ext_ids) {
153-
if (
154-
!is.null(ext_id) &&
155-
identical(safe_extract(ext_id, "external-id-type"), "doi")
156-
) {
157-
doi <- safe_extract(ext_id, "external-id-value")
158-
break
159-
}
144+
if (is.null(summary)) {
145+
return(NULL)
146+
}
147+
148+
# Extract title
149+
title <- safe_extract(summary, "title", "title", "value")
150+
151+
# Extract DOI from external IDs
152+
doi <- NA_character_
153+
ext_ids <- safe_extract(summary, "external-ids", "external-id")
154+
if (!is.null(ext_ids) && is.list(ext_ids)) {
155+
for (ext_id in ext_ids) {
156+
if (
157+
!is.null(ext_id) &&
158+
identical(safe_extract(ext_id, "external-id-type"), "doi")
159+
) {
160+
doi <- safe_extract(ext_id, "external-id-value")
161+
break
160162
}
161163
}
164+
}
162165

163-
# Extract URL
164-
url <- safe_extract(summary, "url", "value")
166+
# Extract URL
167+
url <- safe_extract(summary, "url", "value")
165168

166-
list(
167-
orcid = orcid_id,
168-
put_code = as.character(safe_extract(summary, "put-code")),
169-
title = title,
170-
type = safe_extract(summary, "type"),
171-
publication_date = orcid_date_to_iso(safe_extract(
172-
summary,
173-
"publication-date"
174-
)),
175-
journal = safe_extract(summary, "journal-title", "value"),
176-
doi = doi,
177-
url = url
178-
)
179-
})
169+
list(
170+
orcid = orcid_id,
171+
put_code = as.character(safe_extract(summary, "put-code")),
172+
title = title,
173+
type = safe_extract(summary, "type"),
174+
publication_date = orcid_date_to_iso(safe_extract(
175+
summary,
176+
"publication-date"
177+
)),
178+
journal = safe_extract(summary, "journal-title", "value"),
179+
doi = doi,
180+
url = url
181+
)
180182
})
181183

182-
records_flat <- unlist(records, recursive = FALSE)
183-
records_flat <- records_flat[!vapply(records_flat, is.null, logical(1))]
184+
records_flat <- records[!vapply(records, is.null, logical(1))]
184185

185186
if (length(records_flat) == 0) {
186187
return(data.table::data.table(
@@ -364,7 +365,12 @@ parse_peer_reviews <- function(json_data, orcid_id) {
364365
parse_affiliations <- function(json_data, orcid_id) {
365366
groups <- safe_extract(json_data, "affiliation-group")
366367

367-
if (is.null(groups) || length(groups) == 0) {
368+
# Check if groups is NA (not just NULL)
369+
if (
370+
is.null(groups) ||
371+
length(groups) == 0 ||
372+
(length(groups) == 1 && is.na(groups))
373+
) {
368374
return(data.table::data.table(
369375
orcid = character(0),
370376
put_code = character(0),
@@ -381,26 +387,26 @@ parse_affiliations <- function(json_data, orcid_id) {
381387

382388
records <- lapply(groups, function(group) {
383389
summaries <- safe_extract(group, "summaries")
384-
if (is.null(summaries)) {
390+
if (is.null(summaries) || (length(summaries) == 1 && is.na(summaries))) {
385391
return(NULL)
386392
}
387393

388394
lapply(summaries, function(item) {
389395
# Try different summary types
390396
summary <- safe_extract(item, "distinction-summary")
391-
if (is.null(summary)) {
397+
if (is.null(summary) || (length(summary) == 1 && is.na(summary))) {
392398
summary <- safe_extract(item, "invited-position-summary")
393399
}
394-
if (is.null(summary)) {
400+
if (is.null(summary) || (length(summary) == 1 && is.na(summary))) {
395401
summary <- safe_extract(item, "membership-summary")
396402
}
397-
if (is.null(summary)) {
403+
if (is.null(summary) || (length(summary) == 1 && is.na(summary))) {
398404
summary <- safe_extract(item, "qualification-summary")
399405
}
400-
if (is.null(summary)) {
406+
if (is.null(summary) || (length(summary) == 1 && is.na(summary))) {
401407
summary <- safe_extract(item, "service-summary")
402408
}
403-
if (is.null(summary)) {
409+
if (is.null(summary) || (length(summary) == 1 && is.na(summary))) {
404410
return(NULL)
405411
}
406412

@@ -932,8 +938,13 @@ parse_search_results <- function(json_data) {
932938
num_found <- 0L
933939
}
934940

935-
# Extract results array
936-
results <- safe_extract(json_data, "result")
941+
# Extract results array - use expanded-result for v3.0 API
942+
results <- safe_extract(json_data, "expanded-result")
943+
944+
# Fall back to "result" if expanded-result doesn't exist (older API versions)
945+
if (is.null(results) || (length(results) == 1 && is.na(results))) {
946+
results <- safe_extract(json_data, "result")
947+
}
937948

938949
# Handle empty results (safe_extract returns NA for null)
939950
if (
@@ -954,36 +965,55 @@ parse_search_results <- function(json_data) {
954965

955966
# Parse each result
956967
parsed <- lapply(results, function(item) {
957-
# Extract ORCID identifier
968+
# Extract ORCID identifier - handle both formats
958969
orcid_path <- safe_extract(item, "orcid-identifier", "path")
959-
orcid_id <- if (!is.null(orcid_path)) orcid_path else NA_character_
960-
961-
# Extract given names
962-
given_names <- safe_extract(item, "given-names")
963-
if (is.null(given_names)) {
964-
given_names <- NA_character_
970+
if (is.null(orcid_path) || is.na(orcid_path)) {
971+
# Try direct orcid-id field (expanded-search format)
972+
orcid_path <- item[["orcid-id"]]
965973
}
966-
967-
# Extract family name
968-
family_name <- safe_extract(item, "family-names")
969-
if (is.null(family_name)) {
970-
family_name <- NA_character_
971-
}
972-
973-
# Extract credit name
974-
credit_names <- safe_extract(item, "credit-name")
975-
credit_name <- if (!is.null(credit_names) && length(credit_names) > 0) {
976-
credit_names[[1]]
974+
orcid_id <- if (!is.null(orcid_path) && !is.na(orcid_path)) {
975+
orcid_path
977976
} else {
978977
NA_character_
979978
}
980979

980+
# Helper function to safely extract and convert to character
981+
extract_name_field <- function(field_name) {
982+
val <- item[[field_name]]
983+
if (is.null(val)) {
984+
return(NA_character_)
985+
}
986+
if (length(val) == 0) {
987+
return(NA_character_)
988+
}
989+
if (is.list(val)) {
990+
# If it's a list, try to get the first non-null element
991+
for (i in seq_along(val)) {
992+
if (!is.null(val[[i]]) && length(val[[i]]) > 0) {
993+
return(as.character(val[[i]]))
994+
}
995+
}
996+
return(NA_character_)
997+
}
998+
# Direct value
999+
return(as.character(val))
1000+
}
1001+
1002+
# Extract name fields
1003+
given_names <- extract_name_field("given-names")
1004+
family_name <- extract_name_field("family-names")
1005+
credit_name <- extract_name_field("credit-name")
1006+
9811007
# Extract other names
982-
other_names_data <- safe_extract(item, "other-names")
1008+
other_names_val <- item[["other-names"]]
1009+
if (is.null(other_names_val)) {
1010+
# Try alternate field name
1011+
other_names_val <- item[["other-name"]]
1012+
}
9831013
other_names <- if (
984-
!is.null(other_names_data) && length(other_names_data) > 0
1014+
!is.null(other_names_val) && length(other_names_val) > 0
9851015
) {
986-
other_names_data
1016+
if (is.list(other_names_val)) other_names_val else list(other_names_val)
9871017
} else {
9881018
list(character(0))
9891019
}

R/search.R

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
#'
2626
#' @details
2727
#' This function queries the ORCID search endpoint:
28-
#' \code{https://pub.orcid.org/v3.0/search}
28+
#' \code{https://pub.orcid.org/v3.0/expanded-search}
29+
#'
30+
#' The expanded-search endpoint returns name information along with ORCID IDs,
31+
#' unlike the basic search endpoint which only returns identifiers.
2932
#'
3033
#' **Query Field Examples:**
3134
#' \itemize{
@@ -42,7 +45,7 @@
4245
#' \code{"family-name:Smith AND affiliation-org-name:Harvard"}
4346
#'
4447
#' @references
45-
#' ORCID API Search Documentation: \url{https://info.orcid.org/documentation/integration-guide/searching-the-orcid-registry/}
48+
#' ORCID API Search Documentation: \url{https://info.orcid.org/documentation/api-tutorials/api-tutorial-searching-the-orcid-registry/}
4649
#'
4750
#' @seealso
4851
#' \code{\link{orcid_search}} for a more user-friendly interface,
@@ -102,7 +105,7 @@ orcid <- function(query = NULL, rows = 10, start = 0, token = NULL, ...) {
102105
}
103106

104107
# Construct URL
105-
url <- paste0(orcid_base_url(), "/search")
108+
url <- paste0(orcid_base_url(), "/expanded-search")
106109

107110
# Build request
108111
req <- httr2::request(url) |>
@@ -200,7 +203,7 @@ orcid <- function(query = NULL, rows = 10, start = 0, token = NULL, ...) {
200203
#' @details
201204
#' This function constructs a Solr query from the provided parameters and
202205
#' calls \code{\link{orcid}} internally. Multiple parameters are combined
203-
#' with AND logic.
206+
#' with AND logic. Uses the expanded-search endpoint to return name information.
204207
#'
205208
#' @examples
206209
#' \dontrun{

README.Rmd

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ knitr::opts_chunk$set(
1717

1818
[![Lifecycle:
1919
experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
20-
[![CRAN
21-
status](https://www.r-pkg.org/badges/version/orcidtr)](https://CRAN.R-project.org/package=orcidtr)
20+
21+
<!-- [![CRAN
22+
status](https://www.r-pkg.org/badges/version/orcidtr)](https://CRAN.R-project.org/package=orcidtr) -->
23+
2224
[![R-CMD-check](https://github.com/lorenzoFabbri/orcidtr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/lorenzoFabbri/orcidtr/actions/workflows/R-CMD-check.yaml)
23-
[![codecov](https://codecov.io/gh/lorenzoFabbri/orcidtr/branch/main/graph/badge.svg)](https://codecov.io/gh/lorenzoFabbri/orcidtr)
24-
[![Buy Me a Coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=☕&slug=epilorenzo&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff)](https://www.buymeacoffee.com/epilorenzo)
25+
[![codecov](https://codecov.io/gh/lorenzoFabbri/orcidtr/branch/main/graph/badge.svg)](https://app.codecov.io/gh/lorenzoFabbri/orcidtr)
26+
[![Buy Me a Coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=☕&slug=epilorenzo&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff)](https://buymeacoffee.com/epilorenzo)
2527

2628
<!-- badges: end -->
2729

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55

66
[![Lifecycle:
77
experimental](https://img.shields.io/badge/lifecycle-experimental-orange.svg)](https://lifecycle.r-lib.org/articles/stages.html#experimental)
8-
[![CRAN
9-
status](https://www.r-pkg.org/badges/version/orcidtr)](https://CRAN.R-project.org/package=orcidtr)
8+
9+
<!-- [![CRAN
10+
status](https://www.r-pkg.org/badges/version/orcidtr)](https://CRAN.R-project.org/package=orcidtr) -->
11+
1012
[![R-CMD-check](https://github.com/lorenzoFabbri/orcidtr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/lorenzoFabbri/orcidtr/actions/workflows/R-CMD-check.yaml)
11-
[![codecov](https://codecov.io/gh/lorenzoFabbri/orcidtr/branch/main/graph/badge.svg)](https://codecov.io/gh/lorenzoFabbri/orcidtr)
13+
[![codecov](https://codecov.io/gh/lorenzoFabbri/orcidtr/branch/main/graph/badge.svg)](https://app.codecov.io/gh/lorenzoFabbri/orcidtr)
1214
[![Buy Me a
13-
Coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=☕&slug=epilorenzo&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff)](https://www.buymeacoffee.com/epilorenzo)
15+
Coffee](https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=☕&slug=epilorenzo&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff)](https://buymeacoffee.com/epilorenzo)
1416

1517
<!-- badges: end -->
1618

inst/WORDLIST

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
CMD
2+
codecov
3+
Hadley
4+
httr
5+
https
6+
iDs
7+
Lifecycle
8+
OAuth
9+
orcid
10+
ORCiD
11+
ORCID
12+
ORCIDs
13+
ORCID's
14+
preprint
15+
preprints
16+
README
17+
recognitions
18+
ResearcherID
19+
rorcid
20+
roxygen
21+
Scopus
22+
Solr
23+
Wickham's
24+
XXXX

man/orcid.Rd

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)