Curation plans
curation-plans.RmdThe curation helpers in zot convert candidate records
into plan rows or Zotero payloads. The returned objects can be inspected
before any API write.
library(data.table)
#>
#> Attaching package: 'data.table'
#> The following object is masked from 'package:base':
#>
#> %notin%
library(zot)Normalize incoming metadata
ztCleanName() preserves a filename extension while
removing recognized source labels and copy suffixes.
ztCleanTitle() also removes a trailing file extension.
ztCleanPayload() applies the corresponding rules to text
fields, creator names, and tags.
ztCleanName("Dynamics (1).pdf")
#> [1] "Dynamics.pdf"
ztCleanTitle("Dynamics (1).pdf")
#> [1] "Dynamics"
Payload <- list(
itemType = "book",
title = "Structural Analysis (1).pdf",
creators = list(list(
creatorType = "author",
firstName = "A.",
lastName = "Author"
)),
filename = "Structural Analysis (1).pdf"
)
ztCleanPayload(Payload)
#> $itemType
#> [1] "book"
#>
#> $title
#> [1] "Structural Analysis"
#>
#> $creators
#> $creators[[1]]
#> $creators[[1]]$creatorType
#> [1] "author"
#>
#> $creators[[1]]$firstName
#> [1] "A."
#>
#> $creators[[1]]$lastName
#> [1] "Author"
#>
#>
#>
#> $filename
#> [1] "Structural Analysis.pdf"Other parenthetical text is retained. zotPreview()
rejects a payload that still contains one of the source labels
recognized by the package.
Retitle changed records
ztRetitle() applies a transformation and returns a patch
row only when the result differs from the original title.
Items <- data.table(
itemKey = c("A", "B"),
library = "user",
title = c("Reviewed book (1).pdf", "Existing title")
)
RetitlePlan <- ztRetitle(Items, transform = ztCleanTitle)
zotPreview(RetitlePlan, describe = "Normalize reviewed titles")
#> <zotPreview> Normalize reviewed titles
#> digest: 1737f2bbf3c2706d92827a746c5299a38e518e066d343dd596632c04ae8abb9e
#> action library N
#> <char> <char> <int>
#> 1: patch user 1ztRefile() builds collection-membership patches. It
reads each item’s current memberships and includes their union with the
requested collection, because the Zotero API replaces the complete
membership list.
Compare duplicate candidates
zotSameItem() compares exact DOI, exact attachment MD5,
or normalized title with year and first-author family name. A title
alone does not produce a positive match. A record with a file is not
considered equivalent to a candidate without that file.
zotSameItem(
list(doi = "10.1000/Example", hasFile = TRUE),
list(doi = "10.1000/example", hasFile = TRUE)
)
#> $match
#> [1] TRUE
#>
#> $basis
#> [1] "doi"
zotSameItem(
list(title = "Introduction"),
list(title = "Introduction")
)
#> $match
#> [1] FALSE
#>
#> $basis
#> [1] "none"ztTwins() evaluates pairs within normalized-title groups
and returns the matching verdict and its evidence class.
Records <- data.table(
itemKey = c("A", "B", "C"),
title = c("Paper", "Paper", "Paper"),
year = c(2026L, 2026L, NA_integer_),
author = c("Lovelace", "Lovelace", NA_character_),
doi = NA_character_,
md5 = NA_character_,
hasFile = FALSE
)
ztTwins(Records)
#> keyA keyB match basis
#> <char> <char> <lgcl> <char>
#> 1: A B TRUE title-year-author
#> 2: A C FALSE none
#> 3: B C FALSE noneMap Crossref metadata
ztCrossref() normally requests a DOI record from
Crossref. Its injectable fetcher also permits offline and reproducible
mapping.
Message <- function(doi) list(
title = list("A reproducible record"),
author = list(list(given = "Ada", family = "Lovelace")),
`container-title` = list("Example Journal"),
issued = list(`date-parts` = list(list(2026L)))
)
ztCrossref("10.1000/example", getJson = Message)
#> $itemType
#> [1] "journalArticle"
#>
#> $title
#> [1] "A reproducible record"
#>
#> $creators
#> $creators[[1]]
#> $creators[[1]]$creatorType
#> [1] "author"
#>
#> $creators[[1]]$firstName
#> [1] "Ada"
#>
#> $creators[[1]]$lastName
#> [1] "Lovelace"
#>
#>
#>
#> $DOI
#> [1] "10.1000/example"
#>
#> $publicationTitle
#> [1] "Example Journal"
#>
#> $date
#> [1] "2026"The result is a payload; the function does not create a Zotero item.
Build a cross-library move
Zotero item keys belong to one library.
ztBlockMoveCreate() therefore builds create rows for the
destination. It rejects records with imported files, because copying
their metadata alone would omit attachments.
MoveRecords <- data.table(
itemKey = "OLD1",
payload = list(list(itemType = "book", title = "Reviewed book")),
hasFile = FALSE
)
CreatePlan <- ztBlockMoveCreate(
MoveRecords,
toLibrary = "5107760",
collectionKey = "DEST"
)
CreatePlan
#> action library itemKey payload
#> <char> <char> <char> <list>
#> 1: create 5107760 <NA> <list[3]>ztBlockMoveTrash() adds an original item to a Trash plan
only when its corresponding create-ledger entry is marked
"done".
CreateLedger <- list(items = list(
`create-1` = list(status = "done", new = "NEW1")
))
TrashPlan <- ztBlockMoveTrash(
MoveRecords,
createLedger = CreateLedger,
fromLibrary = "user"
)
TrashPlan
#> action library itemKey payload
#> <char> <char> <char> <list>
#> 1: trash user OLD1 <list[0]>The second plan is independent of the first execution. Trash is recoverable; the package does not empty Zotero’s Trash.