Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,26 @@

Significant speed improvement; also `xml2` is no longer a dependency.

## POTENTIALLY BREAKING: reading single-row / single-column (F)ODS and `col_names` / `row_names`

Prior the previous stable release, <= 1.9.0, reading single-row / single-column with `col_names = TRUE` / `row_names = TRUE` produced errors.

In v1.9.0 (and the stable version v2.0.0 on CRAN), reading single-row / single-column (F)ODS with `col_names = TRUE` / `row_names = TRUE` will
override the two parameters and return a non-empty data frame. This behavior is consistent with other data reading R functions (see #146) such as `readxl::read_xlsx()`, `readr::read_csv()`, `data.table::fread()`, and `openxlsx::read.xlsx()`. For these functions, either a empty or zero-row data.frame is returned.

We changed this behavior. The following will return a zero-row data.frame by default.

```r
read_ods(write_ods(mtcars[0,])) ## col_names is TRUE by default
```

However, the previous behavior is in the stable release and backward compatibility is needed. If you need that previous behavior, please set the `options("readODS.v200" = TRUE)`

```r
options("readODS.v200" = TRUE)
read_ods(write_ods(mtcars[0,])) ## col_names is TRUE by default
```

# readODS 2.06

## `write_ods` and `write_fods` allow list of data frames
Expand Down
35 changes: 29 additions & 6 deletions R/read_ods.R
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
.return_zerorow <- function(x, row_header, .name_repair) {
jcol <- ifelse(row_header, 2, 1)
col_n <- vctrs::vec_as_names(as.character(x[1,jcol:ncol(x)]), repair = .name_repair)
g <- data.frame(matrix(data = NA_character_, ncol = length(col_n), nrow = 0))
colnames(g) <- col_n
return(g)
}

.change_df_with_col_row_header <- function(x, col_header, row_header, .name_repair) {
if((nrow(x) < 2 && col_header )|| (ncol(x) < 2 && row_header)) {
if (nrow(x) == 1 && col_header && isFALSE(getOption("readODS.v200", FALSE))) {
return(.return_zerorow(x, row_header, .name_repair))
}
if (((nrow(x) < 2 && col_header ) || (ncol(x) < 2 && row_header)) &&
isTRUE(getOption("readODS.v200", FALSE))) {
## 2.0.0 behavior
warning("Cannot make column/row names if this would cause the dataframe to be empty.", call. = FALSE)
return(x)
}
irow <- ifelse(col_header, 2, 1)
jcol <- ifelse(row_header, 2, 1)

g <- x[irow:nrow(x), jcol:ncol(x), drop=FALSE] # maintain as dataframe for single column
g <- x[irow:nrow(x), jcol:ncol(x), drop = FALSE] # maintain as dataframe for single column


rownames(g) <- if(row_header) x[seq(irow, nrow(x)), 1] else NULL # don't want character row headers given by 1:nrow(g)
Expand All @@ -19,6 +32,8 @@
return(g)
}



## Based on readxl, although the implementation is different.
## If max row is -1, read to end of row.
## Row and column-numbers are 1-based
Expand Down Expand Up @@ -106,8 +121,10 @@
}

.return_empty <- function(as_tibble = FALSE) {
warning("empty sheet, return empty data frame.", call. = FALSE)
if(as_tibble) {
if (getOption("readODS.v200", FALSE)) {
warning("empty sheet, return empty data frame.", call. = FALSE)
}
if (as_tibble) {
return(tibble::tibble())
}
return(data.frame())
Expand All @@ -125,7 +142,7 @@
}

.handle_col_types <- function(res, col_types, verbose, na) {
if (isTRUE(is.na(col_types))) {
if (isTRUE(is.na(col_types)) || nrow(res) == 0) {
return(res)
}
.type_convert(df = res, col_types = col_types, verbose = verbose, na = na)
Expand Down Expand Up @@ -201,7 +218,13 @@
stop_col = limits["max_col"],
sheet = sheet,
formula_as_formula = formula_as_formula)
if (strings[1] == 0 || strings[2] == 0) {

if (((strings[1] == 0 || strings[2] == 0)) &&
isTRUE(getOption("readODS.v200", FALSE))) {
return(.return_empty(as_tibble = as_tibble))
}
if (((strings[1] == 0 || strings[2] == 0) || (strings[1] == 1 && row_names)) &&
isFALSE(getOption("readODS.v200", FALSE))) {
return(.return_empty(as_tibble = as_tibble))
}
res <- as.data.frame(
Expand Down
24 changes: 19 additions & 5 deletions tests/testthat/test_read_fods.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,29 @@ test_that("Error when not correct", {
expect_error(read_fods("../testdata/notreal.ods"))
})

test_that("Return blank/error if mangled FODS", {
expect_warning(read_fods("../testdata/norows.fods"))
expect_warning(read_fods("../testdata/nocells.fods"))
})

test_that("fix #157", {
skip_if(file.access("~/", 2) != 0)
file.copy("../testdata/flat.fods", "~/flat_you_must_not_use_this_path.fods")
expect_error(read_fods("~/flat_you_must_not_use_this_path.fods"), NA)
expect_error(read_fods("~/flat_you_must_not_use_this_path_not.fods"))
on.exit(unlink("~/flat_you_must_not_use_this_path.fods"))
})

## default

test_that("Return blank/error if mangled FODS", {
expect_silent(read_fods("../testdata/norows.fods"))
expect_silent(read_fods("../testdata/nocells.fods"))
})

## V2.0.0 behavior: backward compatibility

ori_option <- getOption("readODS.v200") ## probably NULL
options("readODS.v200" = TRUE)

test_that("Return blank/error if mangled FODS v2.0.0", {
expect_warning(read_fods("../testdata/norows.fods"))
expect_warning(read_fods("../testdata/nocells.fods"))
})

options("readODS.v200" = ori_option)
88 changes: 61 additions & 27 deletions tests/testthat/test_read_ods.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,6 @@ test_that("Incorrect Argument", {
expect_error(read_ods(path = "../testdata/sum.ods", row_names = TRUE), "Tibbles do not support")
})

test_that("Single column ODS", {
single_col <- read_ods('../testdata/sum.ods', sheet = 1)
expect_equal(ncol(single_col),1)
expect_equal(colnames(single_col), c("X1"))
expect_warning(read_ods('../testdata/sum.ods', sheet = 1, row_names = TRUE, as_tibble = FALSE), "Cannot make")
})

test_that("Single row ODS", {
expect_warning(single_row <- read_ods('../testdata/onerow.ods', sheet = 1), "Cannot make")
expect_equal(nrow(single_row), 1)
expect_equal(single_row[[1,1]], 1)
})

test_that("Single column range", {
expect_error(read_ods("../testdata/starwars.ods", range="A1:A5"), NA)
})
Expand Down Expand Up @@ -51,15 +38,6 @@ test_that("eating space issue #74", {
expect_equal(df[[1,1]], "A B\nC")
})

test_that("skip", {
expect_silent(x <- read_ods("../testdata/starwars.ods", skip = 0))
expect_equal(nrow(x), 10)
expect_message(x <- read_ods("../testdata/starwars.ods", skip = 1, col_names = FALSE))
expect_equal(nrow(x), 10)
expect_warning(x <- read_ods("../testdata/starwars.ods", skip = 11), "empty sheet")
expect_equal(nrow(x), 0)
})

test_that("Check .name_repair works properly", {
expect_silent(x <- read_ods("../testdata/test_naming.ods", .name_repair = "minimal"))
expect_equal(colnames(x), c("a", "a.1", "Var.3"))
Expand All @@ -84,11 +62,6 @@ test_that("Deals with repeated spaces correctly when fetching only part of sheet
expect_equal(read_ods("../testdata/excel_repeat.ods", range = "A9:B18", col_names = FALSE)[[5,1]], "C")
})

test_that("Warns of empty sheet", {
expect_warning(read_ods("../testdata/empty.ods"))
expect_warning(read_fods("../testdata/empty.fods"))
})

test_that("read with column headers", {
expect_silent(x <- read_ods("../testdata/starwars.ods", col_names = TRUE))
expect_equal(ncol(x), 3)
Expand All @@ -97,3 +70,64 @@ test_that("read with column headers", {
expect_equal(ncol(x), 2)
expect_equal(colnames(x), c("homeworld", "species"))
})

## default single behavior
test_that("Single column ODS", {
single_col <- read_ods('../testdata/sum.ods', sheet = 1)
expect_equal(ncol(single_col),1)
expect_equal(colnames(single_col), c("X1"))
expect_equal(nrow(read_ods('../testdata/sum.ods', sheet = 1, row_names = TRUE, as_tibble = FALSE)), 0)
})

test_that("Single row ODS", {
expect_silent(single_row <- read_ods('../testdata/onerow.ods', sheet = 1))
expect_equal(nrow(single_row), 0)
})

test_that("skip", {
expect_silent(x <- read_ods("../testdata/starwars.ods", skip = 0))
expect_equal(nrow(x), 10)
expect_message(x <- read_ods("../testdata/starwars.ods", skip = 1, col_names = FALSE))
expect_equal(nrow(x), 10)
expect_silent(x <- read_ods("../testdata/starwars.ods", skip = 11))
expect_equal(nrow(x), 0)
})

test_that("No Warning of empty sheet", {
expect_silent(read_ods("../testdata/empty.ods"))
expect_silent(read_fods("../testdata/empty.fods"))
})

## V2.0.0 behavior: backward compatibility

ori_option <- getOption("readODS.v200") ## probably NULL
options("readODS.v200" = TRUE)

test_that("Single column ODS v2.0.0", {
single_col <- read_ods('../testdata/sum.ods', sheet = 1)
expect_equal(ncol(single_col),1)
expect_equal(colnames(single_col), c("X1"))
expect_warning(read_ods('../testdata/sum.ods', sheet = 1, row_names = TRUE, as_tibble = FALSE), "Cannot make")
})

test_that("Single row ODS v2.0.0", {
expect_warning(single_row <- read_ods('../testdata/onerow.ods', sheet = 1), "Cannot make")
expect_equal(nrow(single_row), 1)
expect_equal(single_row[[1,1]], 1)
})

test_that("skip v2.0.0", {
expect_silent(x <- read_ods("../testdata/starwars.ods", skip = 0))
expect_equal(nrow(x), 10)
expect_message(x <- read_ods("../testdata/starwars.ods", skip = 1, col_names = FALSE))
expect_equal(nrow(x), 10)
expect_warning(x <- read_ods("../testdata/starwars.ods", skip = 11), "empty sheet")
expect_equal(nrow(x), 0)
})

test_that("Warns of empty sheet", {
expect_warning(read_ods("../testdata/empty.ods"))
expect_warning(read_fods("../testdata/empty.fods"))
})

options("readODS.v200" = ori_option)