From c99abd0146a7333d2980265d8e860e2c4c6259d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Wed, 17 Dec 2025 15:02:06 +0100 Subject: [PATCH 1/7] Add CSV delimiter get/set to redcapConnection --- R/redcapConnection.R | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/R/redcapConnection.R b/R/redcapConnection.R index 2b28603b..86f66243 100644 --- a/R/redcapConnection.R +++ b/R/redcapConnection.R @@ -236,6 +236,22 @@ redcapConnection <- function(url = getOption('redcap_api_url'), length.out = rtry) rtry_q <- retry_quietly + # CSV delimiter state for this connection ------------------------- + getCsvDelimiterAPI <- function(csv_delimiter) { + csv_delimiter_api <- switch( + csv_delimiter, + "," = "comma", + "\t" = "tab", + ";" = "semicolon", + "|" = "pipe", + "^" = "caret", + "comma" + ) + csv_delimiter_api + } + csv_delim <- "," + csv_delim_api <- getCsvDelimiterAPI(csv_delim) + getter <- function(export, ...){ switch(export, "metadata" = exportMetaData(rc), @@ -397,9 +413,22 @@ redcapConnection <- function(url = getOption('redcap_api_url'), len = 1, any.missing = FALSE) rtry_q <<- rq - } + }, + + set_csv_delimiter = function(d) { + checkmate::assert_choice( + x = d, + choices = c(",", "\t", ";", "|", "^") + ) + csv_delim <<- d + csv_delim_api <<- getCsvDelimiterAPI(csv_delim) + }, + csv_delimiter = function() csv_delim, + csv_delimiter_api = function() csv_delim_api ) class(rc) <- c("redcapApiConnection", "redcapConnection") + # Initialize csv_delimiter with default + rc$set_csv_delimiter(",") rc } From 4e5db14ad7256573abcd8bbe2bd74aef8d7e7ad9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Wed, 17 Dec 2025 15:04:10 +0100 Subject: [PATCH 2/7] Fix CSV delimiter in (some) export functions --- R/exportDags.R | 23 ++++++++++++----------- R/exportFieldNames.R | 3 ++- R/exportProjectInformation.R | 5 +++-- R/exportRecordsTyped.R | 5 +++-- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/R/exportDags.R b/R/exportDags.R index 76d3bacd..f699cf09 100644 --- a/R/exportDags.R +++ b/R/exportDags.R @@ -11,28 +11,29 @@ exportDags <- function(rcon, ...){ #' @order 4 #' @export -exportDags.redcapApiConnection <- function(rcon, +exportDags.redcapApiConnection <- function(rcon, ...) { ################################################################### # Argument Validation #### - + coll <- checkmate::makeAssertCollection() - - checkmate::assert_class(x = rcon, - classes = "redcapApiConnection", + + checkmate::assert_class(x = rcon, + classes = "redcapApiConnection", add = coll) checkmate::reportAssertions(coll) - + ################################################################### # Make the API Body list #### - - body <- list(content = "dag", - format = "csv", - returnFormat = "csv") + + body <- list(content = "dag", + format = "csv", + returnFormat = "csv", + csvDelimiter = rcon$csv_delimiter_api()) ################################################################### # Call the API #### - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportFieldNames.R b/R/exportFieldNames.R index 8b03c130..02403bbc 100644 --- a/R/exportFieldNames.R +++ b/R/exportFieldNames.R @@ -135,5 +135,6 @@ exportFieldNames.redcapApiConnection <- function(rcon, # Make the API Call ----------------------------------------------- read.csv(text = as.character(makeApiCall(rcon, body, ...)), na.strings = "", - stringsAsFactors = FALSE) + stringsAsFactors = FALSE, + sep = rcon$csv_delimiter()) } diff --git a/R/exportProjectInformation.R b/R/exportProjectInformation.R index e9aaee46..9238aa85 100644 --- a/R/exportProjectInformation.R +++ b/R/exportProjectInformation.R @@ -30,9 +30,10 @@ exportProjectInformation.redcapApiConnection <- function(rcon, body <- list(content = 'project', format = 'csv', - returnFormat = 'csv') + returnFormat = 'csv', + csvDelimiter = rcon$csv_delimiter_api()) ################################################################## # Call the API - as.data.frame(makeApiCall(rcon, body, ...)) + as.data.frame(makeApiCall(rcon, body, ...), sep = rcon$csv_delimiter()) } diff --git a/R/exportRecordsTyped.R b/R/exportRecordsTyped.R index 4f3f6232..c573fced 100644 --- a/R/exportRecordsTyped.R +++ b/R/exportRecordsTyped.R @@ -47,7 +47,7 @@ exportRecordsTyped.redcapApiConnection <- filter_empty_rows = TRUE, warn_zero_coded = TRUE, ..., - csv_delimiter = ",", + csv_delimiter = rcon$csv_delimiter(), batch_size = 100L) { logEvent("INFO", call='exportRecordsTyped', @@ -682,7 +682,8 @@ exportRecordsTyped.redcapOfflineConnection <- function(rcon, record_response <- makeApiCall(rcon, body = c(list(content = "record", format = "csv", - outputFormat = "csv"), + outputFormat = "csv", + csvDelimiter = rcon$csv_delimiter_api()), vectorToApiBodyList(target_field, "fields")), log=FALSE, From 496746bff1203cea3ce332c5b74ee9036aa5b4a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Wed, 17 Dec 2025 15:05:27 +0100 Subject: [PATCH 3/7] Fix CSV delimiter in (some) import functions --- R/importDags.R | 2 +- R/writeDataForImport.R | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/R/importDags.R b/R/importDags.R index 2089fa5a..1ce8e56b 100644 --- a/R/importDags.R +++ b/R/importDags.R @@ -53,7 +53,7 @@ importDags.redcapApiConnection <- function(rcon, action = "import", format = "csv", returnFormat = "csv", - data = writeDataForImport(data)) + data = writeDataForImport(data, csv_delimiter = rcon$csv_delimiter())) ################################################################### # Call the API #### diff --git a/R/writeDataForImport.R b/R/writeDataForImport.R index 2fc8b3fc..9c583061 100644 --- a/R/writeDataForImport.R +++ b/R/writeDataForImport.R @@ -5,9 +5,11 @@ #' of a CSV for import through the API. #' #' @param data `data.frame` to be imported to the API -#' +#' @param csv_delimiter `character(1)` Delimiter used to separate fields in the generated CSV +#' string. Defaults to `","`. +#' -writeDataForImport <- function(data){ +writeDataForImport <- function(data, csv_delimiter = ","){ coll <- checkmate::makeAssertCollection() checkmate::assert_data_frame(x = data, @@ -17,10 +19,13 @@ writeDataForImport <- function(data){ output <- utils::capture.output( - utils::write.csv(data, - file = "", - na = "", - row.names = FALSE) + utils::write.table(data, + file = "", + sep = csv_delimiter, + na = "", + row.names = FALSE, + col.names = TRUE, + qmethod = "double") ) paste0(output, collapse = "\n") From 34ba4734ba74f199a94cfec41c9d61eb9dcc6b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Thu, 18 Dec 2025 11:06:44 +0100 Subject: [PATCH 4/7] Remove distinction between csvDelimiter and csvDelimiterApi --- R/exportDags.R | 2 +- R/exportProjectInformation.R | 2 +- R/exportRecordsTyped.R | 2 +- R/redcapConnection.R | 17 +---------------- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/R/exportDags.R b/R/exportDags.R index f699cf09..c9b68546 100644 --- a/R/exportDags.R +++ b/R/exportDags.R @@ -31,7 +31,7 @@ exportDags.redcapApiConnection <- function(rcon, body <- list(content = "dag", format = "csv", returnFormat = "csv", - csvDelimiter = rcon$csv_delimiter_api()) + csvDelimiter = rcon$csv_delimiter()) ################################################################### # Call the API #### diff --git a/R/exportProjectInformation.R b/R/exportProjectInformation.R index 9238aa85..6ec4ddab 100644 --- a/R/exportProjectInformation.R +++ b/R/exportProjectInformation.R @@ -31,7 +31,7 @@ exportProjectInformation.redcapApiConnection <- function(rcon, body <- list(content = 'project', format = 'csv', returnFormat = 'csv', - csvDelimiter = rcon$csv_delimiter_api()) + csvDelimiter = rcon$csv_delimiter()) ################################################################## # Call the API diff --git a/R/exportRecordsTyped.R b/R/exportRecordsTyped.R index c573fced..f9e79b84 100644 --- a/R/exportRecordsTyped.R +++ b/R/exportRecordsTyped.R @@ -683,7 +683,7 @@ exportRecordsTyped.redcapOfflineConnection <- function(rcon, body = c(list(content = "record", format = "csv", outputFormat = "csv", - csvDelimiter = rcon$csv_delimiter_api()), + csvDelimiter = rcon$csv_delimiter()), vectorToApiBodyList(target_field, "fields")), log=FALSE, diff --git a/R/redcapConnection.R b/R/redcapConnection.R index 86f66243..d9615f8d 100644 --- a/R/redcapConnection.R +++ b/R/redcapConnection.R @@ -237,20 +237,7 @@ redcapConnection <- function(url = getOption('redcap_api_url'), rtry_q <- retry_quietly # CSV delimiter state for this connection ------------------------- - getCsvDelimiterAPI <- function(csv_delimiter) { - csv_delimiter_api <- switch( - csv_delimiter, - "," = "comma", - "\t" = "tab", - ";" = "semicolon", - "|" = "pipe", - "^" = "caret", - "comma" - ) - csv_delimiter_api - } csv_delim <- "," - csv_delim_api <- getCsvDelimiterAPI(csv_delim) getter <- function(export, ...){ switch(export, @@ -421,10 +408,8 @@ redcapConnection <- function(url = getOption('redcap_api_url'), choices = c(",", "\t", ";", "|", "^") ) csv_delim <<- d - csv_delim_api <<- getCsvDelimiterAPI(csv_delim) }, - csv_delimiter = function() csv_delim, - csv_delimiter_api = function() csv_delim_api + csv_delimiter = function() csv_delim ) class(rc) <- c("redcapApiConnection", "redcapConnection") # Initialize csv_delimiter with default From e39cf5c5aa805509b4840f05fa586f304f84f327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Thu, 18 Dec 2025 11:09:10 +0100 Subject: [PATCH 5/7] Add whitespace as a valid CSV delimiter --- R/docsRecordsTypedMethods.R | 2 +- R/exportRecordsTyped.R | 2 +- R/exportReportsTyped.R | 2 +- R/redcapConnection.R | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/R/docsRecordsTypedMethods.R b/R/docsRecordsTypedMethods.R index 608db877..67a24803 100644 --- a/R/docsRecordsTypedMethods.R +++ b/R/docsRecordsTypedMethods.R @@ -42,7 +42,7 @@ #' Otherwise, records created or modified after this date will be returned. #' @param date_end `POSIXct(1)` or `NULL`. Ignored if `NULL` (default). #' Otherwise, records created or modified before this date will be returned. -#' @param csv_delimiter `character`. One of `c(",", "\t", ";", "|", "^")`. Designates the +#' @param csv_delimiter `character`. One of `c(",", ";", "\t", " ", "|", "^")`. Designates the #' delimiter for the CSV file received from the API. #' @param batch_size `integerish(1)` or `NULL`. When `NULL`, #' all records are pulled. Otherwise, the records all pulled in batches of this size. diff --git a/R/exportRecordsTyped.R b/R/exportRecordsTyped.R index f9e79b84..7527f8f6 100644 --- a/R/exportRecordsTyped.R +++ b/R/exportRecordsTyped.R @@ -112,7 +112,7 @@ exportRecordsTyped.redcapApiConnection <- add = coll) csv_delimiter <- checkmate::matchArg(x = csv_delimiter, - choices = c(",", "\t", ";", "|", "^"), + choices = c(",", ";", "\t", " ", "|", "^"), .var.name = "csv_delimiter", add = coll) diff --git a/R/exportReportsTyped.R b/R/exportReportsTyped.R index 4c7e514b..05ca544f 100644 --- a/R/exportReportsTyped.R +++ b/R/exportReportsTyped.R @@ -59,7 +59,7 @@ exportReportsTyped.redcapApiConnection <- function(rcon, add = coll) csv_delimiter <- checkmate::matchArg(x = csv_delimiter, - choices = c(",", "\t", ";", "|", "^"), + choices = c(",", ";", "\t", " ", "|", "^"), .var.name = "csv_delimiter", add = coll) diff --git a/R/redcapConnection.R b/R/redcapConnection.R index d9615f8d..ed141a95 100644 --- a/R/redcapConnection.R +++ b/R/redcapConnection.R @@ -405,7 +405,7 @@ redcapConnection <- function(url = getOption('redcap_api_url'), set_csv_delimiter = function(d) { checkmate::assert_choice( x = d, - choices = c(",", "\t", ";", "|", "^") + choices = c(",", ";", "\t", " ", "|", "^") ) csv_delim <<- d }, From e0b1bd54bbad56d5319fbbb447ecb65eff89353a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Thu, 18 Dec 2025 11:11:57 +0100 Subject: [PATCH 6/7] Update list of contributors in DESCRIPTION --- DESCRIPTION | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index e5dc58eb..bc1f2240 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -24,7 +24,8 @@ Authors@R: c( person("Philip", "Chase", role="ctb"), person("Paddy", "Tobias", role = "ctb"), person("Michael", "Chirico", role = "ctb"), - person("William", "Sharp", role="ctb")) + person("William", "Sharp", role="ctb"), + person("Alexander", "StrĂ¼bing", role="ctb")) Maintainer: Shawn Garbett Description: Access data stored in 'REDCap' databases using the Application Programming Interface (API). 'REDCap' (Research Electronic Data CAPture; From e3fc092ef5b7e663afd0ccb895c549fb331c3856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Stru=CC=88bing?= Date: Thu, 18 Dec 2025 11:12:43 +0100 Subject: [PATCH 7/7] Update .rd files --- man/recordsTypedMethods.Rd | 4 ++-- man/redcapAPI.Rd | 1 + man/writeDataForImport.Rd | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/man/recordsTypedMethods.Rd b/man/recordsTypedMethods.Rd index 332ceb79..c2137875 100644 --- a/man/recordsTypedMethods.Rd +++ b/man/recordsTypedMethods.Rd @@ -40,7 +40,7 @@ exportReportsTyped(rcon, report_id, ...) filter_empty_rows = TRUE, warn_zero_coded = TRUE, ..., - csv_delimiter = ",", + csv_delimiter = rcon$csv_delimiter(), batch_size = 100L ) @@ -150,7 +150,7 @@ HTML and UNICODE raw label and scanning for units=\{"UNITS"\} in description} \item{filter_empty_rows}{\code{logical(1)}. Filter out empty rows post retrieval. Defaults to \code{TRUE}.} -\item{csv_delimiter}{\code{character}. One of \code{c(",", "\\t", ";", "|", "^")}. Designates the +\item{csv_delimiter}{\code{character}. One of \code{c(",", ";", "\\t", " ", "|", "^")}. Designates the delimiter for the CSV file received from the API.} \item{batch_size}{\code{integerish(1)} or \code{NULL}. When \code{NULL}, diff --git a/man/redcapAPI.Rd b/man/redcapAPI.Rd index 17269fe1..69a384ff 100644 --- a/man/redcapAPI.Rd +++ b/man/redcapAPI.Rd @@ -56,6 +56,7 @@ Other contributors: \item Paddy Tobias [contributor] \item Michael Chirico [contributor] \item William Sharp [contributor] + \item Alexander StrĂ¼bing [contributor] } } diff --git a/man/writeDataForImport.Rd b/man/writeDataForImport.Rd index 57167297..8926a039 100644 --- a/man/writeDataForImport.Rd +++ b/man/writeDataForImport.Rd @@ -4,10 +4,13 @@ \alias{writeDataForImport} \title{Prepare a Data Frame for Import Through the API} \usage{ -writeDataForImport(data) +writeDataForImport(data, csv_delimiter = ",") } \arguments{ \item{data}{\code{data.frame} to be imported to the API} + +\item{csv_delimiter}{\code{character(1)} Delimiter used to separate fields in the generated CSV +string. Defaults to \code{","}.} } \description{ Converts a dataframe into a character value in the format