diff --git a/NEWS.md b/NEWS.md index 498ee56..aba8160 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,10 @@ * Update Examples to make them more realistic #327 * Bug fixes - ... is correctly passed for exporting ODS and feather #318 + - POTENTIALLY BREAKING: JSON are exported in UTF-8 by default; solved encoding issues on + Windows R < 4.2. This won't affect any modern R installation where UTF-8 is the default. #318 + - POTENTIALLY BREAKING: YAML are exported using yaml::write_yaml(). But it can't pass the UTF-8 check on older systems. + Disclaimer added. #318 * Declutter - remove the obsolete data.table option #323 - write all documentation blocks in markdown #311 diff --git a/R/export.R b/R/export.R index 89d932c..cdb47e8 100644 --- a/R/export.R +++ b/R/export.R @@ -38,7 +38,7 @@ #' \item OpenDocument Spreadsheet (.ods), using [readODS::write_ods()]. (Currently only single-sheet exports are supported.) #' \item HTML (.html), using a custom method based on [xml2::xml_add_child()] to create a simple HTML table and [xml2::write_xml()] to write to disk. #' \item XML (.xml), using a custom method based on [xml2::xml_add_child()] to create a simple XML tree and [xml2::write_xml()] to write to disk. -#' \item YAML (.yml), using [yaml::as.yaml()] +#' \item YAML (.yml), using [yaml::write_yaml()], default to write the content with UTF-8. Might not work on some older systems, e.g. default Windows locale for R <= 4.2. #' \item Clipboard export (on Windows and Mac OS), using [utils::write.table()] with `row.names = FALSE` #' } #' diff --git a/R/export_methods.R b/R/export_methods.R index d1fef11..348c8e5 100644 --- a/R/export_methods.R +++ b/R/export_methods.R @@ -104,7 +104,7 @@ export_delim <- function(file, x, fwrite = TRUE, sep = "\t", row.names = FALSE, ' colClasses = c("', paste0(col_classes, collapse = '","') ,'"))\n'), domain = NA) } } - cat(paste0("#", capture.output(write.csv(dict, row.names = FALSE, quote = FALSE))), file = file, sep = "\n") + .write_as_utf8(paste0("#", capture.output(write.csv(dict, row.names = FALSE, quote = FALSE))), file = file, sep = "\n") utils::write.table(dat, file = file, append = TRUE, row.names = row.names, sep = sep, quote = quote, col.names = col.names, ...) } @@ -205,7 +205,7 @@ export_delim <- function(file, x, fwrite = TRUE, sep = "\t", row.names = FALSE, #' @export .export.rio_json <- function(file, x, ...) { .check_pkg_availability("jsonlite") - cat(jsonlite::toJSON(x, ...), file = file) + .write_as_utf8(jsonlite::toJSON(x, ...), file = file) } #' @importFrom foreign write.arff @@ -303,7 +303,7 @@ export_delim <- function(file, x, fwrite = TRUE, sep = "\t", row.names = FALSE, #' @export .export.rio_yml <- function(file, x, ...) { .check_pkg_availability("yaml") - cat(yaml::as.yaml(x, ...), file = file) + yaml::write_yaml(x, file = file, ...) } #' @export diff --git a/R/utils.R b/R/utils.R index a7dee6b..2d130e9 100644 --- a/R/utils.R +++ b/R/utils.R @@ -125,3 +125,7 @@ twrap <- function(value, tag) { } return(invisible(NULL)) } + +.write_as_utf8 <- function(text, file, sep = "") { + writeLines(enc2utf8(text), con = file, sep = sep, useBytes = TRUE) +} diff --git a/man/export.Rd b/man/export.Rd index e2f8002..8797764 100644 --- a/man/export.Rd +++ b/man/export.Rd @@ -54,7 +54,7 @@ The output file can be to a compressed directory, simply by adding an appropriat \item OpenDocument Spreadsheet (.ods), using \code{\link[readODS:write_ods]{readODS::write_ods()}}. (Currently only single-sheet exports are supported.) \item HTML (.html), using a custom method based on \code{\link[xml2:xml_replace]{xml2::xml_add_child()}} to create a simple HTML table and \code{\link[xml2:write_xml]{xml2::write_xml()}} to write to disk. \item XML (.xml), using a custom method based on \code{\link[xml2:xml_replace]{xml2::xml_add_child()}} to create a simple XML tree and \code{\link[xml2:write_xml]{xml2::write_xml()}} to write to disk. -\item YAML (.yml), using \code{\link[yaml:as.yaml]{yaml::as.yaml()}} +\item YAML (.yml), using \code{\link[yaml:write_yaml]{yaml::write_yaml()}}, default to write the content with UTF-8. Might not work on some older systems, e.g. default Windows locale for R <= 4.2. \item Clipboard export (on Windows and Mac OS), using \code{\link[utils:write.table]{utils::write.table()}} with \code{row.names = FALSE} } diff --git a/tests/testthat/test_format_json.R b/tests/testthat/test_format_json.R index 5eb30ab..0848dda 100644 --- a/tests/testthat/test_format_json.R +++ b/tests/testthat/test_format_json.R @@ -18,5 +18,13 @@ test_that("Export to JSON (non-data frame)", { expect_true(length(import("list.json")) == 2L) }) +test_that("utf-8", { + content <- c("\"", "\u010d", "\u0161", "\u00c4", "\u5b57", "\u30a2", "\u30a2\u30e0\u30ed") + x <- data.frame(col = content) + tempjson <- tempfile(fileext = ".json") + y <- import(export(x, tempjson)) + testthat::expect_equal(content, y$col) +}) + unlink("iris.json") unlink("list.json") diff --git a/tests/testthat/test_format_yml.R b/tests/testthat/test_format_yml.R index ca63f9c..ac441a6 100644 --- a/tests/testthat/test_format_yml.R +++ b/tests/testthat/test_format_yml.R @@ -13,4 +13,14 @@ test_that("Import from YAML", { expect_identical(import("iris.yml")$Species, as.character(iris$Species)) }) +test_that("utf-8", { + skip_if(getRversion() <= "4.2") + content <- c("\"", "\u010d", "\u0161", "\u00c4", "\u5b57", "\u30a2", "\u30a2\u30e0\u30ed") + x <- data.frame(col = content) + tempyaml <- tempfile(fileext = ".yaml") + y <- import(export(x, tempyaml)) + testthat::expect_equal(content, y$col) +}) + + unlink("iris.yml")