cellranger/ 0000755 0001762 0000144 00000000000 12746005674 012403 5 ustar ligges users cellranger/inst/ 0000755 0001762 0000144 00000000000 12745604030 013346 5 ustar ligges users cellranger/inst/doc/ 0000755 0001762 0000144 00000000000 12745604030 014113 5 ustar ligges users cellranger/inst/doc/cell-references.Rmd 0000644 0001762 0000144 00000004261 12745604030 017620 0 ustar ligges users ---
title: "Classes and methods to deal with cell references"
author: "Jenny Bryan"
date: "`r Sys.Date()`"
output:
rmarkdown::html_vignette:
toc: true
toc_depth: 4
keep_md: true
vignette: >
%\VignetteIndexEntry{Classes and methods to deal with cell references}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
Following Testoft's book Spreadsheet Implementation Technology: Basics and Extensions.
The main class is `ra_ref` which holds a single **r**elative, **a**bsolute, or mixed cell **ref**erence. Two logical indicators, `rowAbs` and `colAbs`, which report whether the row (column) reference is absolute. Also integers `rowRef` and `colRef`, which either hold absolute row and column or, for a relative reference, an offset.
Two other very convenient, but less general forms for holding cell references:
* as a string
- in A1 format: e.g. `B4`, `B$4`, `$B4`, `$B$4` (let's assume found in cell `D5`, shall we?)
- in R1C1 format: e.g. `R[1]C[-2]`, `R4C[-2]`, `R[1]C2`, `R4C2`
* as an absolute row and colum address
`to_string.ra_ref()` converts a single `ra_ref` to character.
`as.ra_ref.character()` converts a single cell reference in string form to a `ra_ref` object.
Note there can be problems converting to/from character, specifically A1 formatted strings, because we don't know the host cell. A relative row or column reference cannot be resolved without knowing the host cell. So this is a source of warnings and `NA`, going both directions.
The `cell_addr` class is for absolute cell addresses. It's a list with two synchronized, equal length integer vectors, `row` and `col`. It could be a data frame or matrix (and mabye it should be?), but it's not. Methods `[`, `[[`, and `length` exist. Note that a single `cell_addr` object could hold many absolute references.
`to_string.cell_addr` converts a `cell_addr` object to character, in a vectorized way. The format `fo` is an argument. Under the hood, this actually converts each individual cell address into an `ra_ref` object, then calls `to_string` on it, and returns them as character vector.
`as.ra_ref.cell_addr` converts a `cell_addr` object to a `ra_ref` object and is NOT vectorized.
WIP!
cellranger/inst/doc/cell-references.html 0000644 0001762 0000144 00000020661 12745604030 020044 0 ustar ligges users
Classes and methods to deal with cell references
Classes and methods to deal with cell references
Jenny Bryan
2016-07-25
Following Testoft’s book Spreadsheet Implementation Technology: Basics and Extensions.
The main class is ra_ref which holds a single relative, absolute, or mixed cell reference. Two logical indicators, rowAbs and colAbs, which report whether the row (column) reference is absolute. Also integers rowRef and colRef, which either hold absolute row and column or, for a relative reference, an offset.
Two other very convenient, but less general forms for holding cell references:
as a string
in A1 format: e.g. B4, B$4, $B4, $B$4 (let’s assume found in cell D5, shall we?)
in R1C1 format: e.g. R[1]C[-2], R4C[-2], R[1]C2, R4C2
as an absolute row and colum address
to_string.ra_ref() converts a single ra_ref to character. as.ra_ref.character() converts a single cell reference in string form to a ra_ref object.
Note there can be problems converting to/from character, specifically A1 formatted strings, because we don’t know the host cell. A relative row or column reference cannot be resolved without knowing the host cell. So this is a source of warnings and NA, going both directions.
The cell_addr class is for absolute cell addresses. It’s a list with two synchronized, equal length integer vectors, row and col. It could be a data frame or matrix (and mabye it should be?), but it’s not. Methods [, [[, and length exist. Note that a single cell_addr object could hold many absolute references.
to_string.cell_addr converts a cell_addr object to character, in a vectorized way. The format fo is an argument. Under the hood, this actually converts each individual cell address into an ra_ref object, then calls to_string on it, and returns them as character vector.
as.ra_ref.cell_addr converts a cell_addr object to a ra_ref object and is NOT vectorized.
WIP!
cellranger/tests/ 0000755 0001762 0000144 00000000000 12515244212 013530 5 ustar ligges users cellranger/tests/testthat.R 0000644 0001762 0000144 00000000100 12515244212 015502 0 ustar ligges users library(testthat)
library(cellranger)
test_check("cellranger")
cellranger/tests/testthat/ 0000755 0001762 0000144 00000000000 12746005674 015405 5 ustar ligges users cellranger/tests/testthat/reference/ 0000755 0001762 0000144 00000000000 12712114434 017327 5 ustar ligges users cellranger/tests/testthat/reference/ra_list.rds 0000644 0001762 0000144 00000000321 12712114434 021472 0 ustar ligges users Sm
0%/ <:pWSMN0,
{)M^J 0n\+4 Wh%ZFVD$_qcX@8\tyי:zZG=|F;uc8ceѓ6Ghx Y$ӥ=w*!uiSLUW
3 cellranger/tests/testthat/test-A1-R1C1-utils.R 0000644 0001762 0000144 00000001014 12716427703 020542 0 ustar ligges users context("A1, R1C1 detection and parsing")
A1 <- c("A1", "$A1", "A$1", "$A$1", "a1")
R1C1 <- c("R1C1", "R1C[-1]", "R[-1]C1", "R[-1]C[9]")
test_that("A1 refs are detected as such and R1C1 are not", {
expect_true(all(is_A1(A1)))
expect_false(any(is_R1C1(A1)))
})
test_that("R1C1 refs are detected as such and A1 are not", {
expect_true(all(is_R1C1(R1C1)))
expect_false(all(is_A1(R1C1)))
})
test_that("ambiguous refs are detected as both A1 and R1C1", {
expect_true(is_A1("RC3"))
expect_true(is_R1C1("RC3"))
})
cellranger/tests/testthat/test-letter-number-conversion.R 0000644 0001762 0000144 00000001334 12712114434 023442 0 ustar ligges users context("letter <--> number conversion")
test_that("Column letter converts to correct column number", {
expect_equal(letter_to_num("A"), 1L)
expect_equal(letter_to_num("AB"), 28L)
expect_equal(letter_to_num(
c("A", "AH", NA, "", "ABD", "XFD")),
c( 1L, 34L, NA, NA, 732L, 16384L))
expect_error(letter_to_num(1:5))
expect_error(letter_to_num(factor(LETTERS)))
})
test_that("Column number converts to correct column letter", {
expect_equal(num_to_letter(1), "A")
expect_equal(num_to_letter(28), "AB")
expect_equal(num_to_letter(
c( 34, NA, 0, -4, 732, 16384, 4.8)),
c("AH", NA, NA, NA, "ABD", "XFD", "D"))
expect_error(num_to_letter("hi there"))
expect_error(num_to_letter("100"))
})
cellranger/tests/testthat/test-ra-ref-class.R 0000644 0001762 0000144 00000011456 12721413515 020761 0 ustar ligges users context("ra_ref class")
test_that("ra_ref constructor raises error for bad inputs", {
expect_error(ra_ref(1:2))
expect_error(ra_ref(row_abs = "A1"))
})
test_that("ra_ref constructor rejects absolute references < 1", {
expect_error(ra_ref(row_ref = -1))
expect_error(ra_ref(col_ref = -2))
})
test_that("ra_ref constructor is not changing", {
ra_list <- list(
ra_ref(),
ra_ref(2, TRUE, 3, FALSE),
ra_ref(4, FALSE, 5, TRUE),
ra_ref(6, FALSE, 6, FALSE),
ra_ref(sheet = "a sheet", file = "filename.xlsx")
)
expect_equal_to_reference(ra_list, test_path("reference", "ra_list.rds"))
## THE FIRST TIME make sure wd is tests/testthat and do this:
#expect_equal_to_reference(ra_list, file.path("reference", "ra_list.rds"))
})
test_that("ra_ref is converted to string", {
expect_identical(to_string(ra_ref()), "R1C1")
expect_identical(to_string(ra_ref(), fo = "A1"), "$A$1")
expect_identical(to_string(ra_ref(), fo = "A1", strict = FALSE), "A1")
expect_identical(to_string(ra_ref(2, TRUE, 3, FALSE)), "R2C[3]")
expect_identical(to_string(ra_ref(4, FALSE, 5, TRUE)), "R[4]C5")
expect_identical(to_string(ra_ref(6, FALSE, -6, FALSE)), "R[6]C[-6]")
expect_identical(to_string(ra_ref(6, FALSE, -6, FALSE, "a sheet")),
"'a sheet'!R[6]C[-6]")
## special case when rel ref offset is 0 --> no square brackets
expect_identical(to_string(ra_ref(0, FALSE)), "RC1")
expect_identical(to_string(ra_ref(4, TRUE, 0, FALSE)), "R4C")
expect_identical(to_string(ra_ref(0, FALSE, 0, FALSE)), "RC")
})
test_that("file and sheet qualified cell ref strings work", {
expect_identical(as.ra_ref("Sheet1!$D$4"),
ra_ref(4, TRUE, 4, TRUE, "Sheet1"))
expect_identical(as.ra_ref("[filename.xlsx]'a sheet'!R1C1"),
ra_ref(sheet = "a sheet", file = "filename.xlsx"))
## worksheet name contains exclamation marks
expect_identical(as.ra_ref("'Gabe!!'!$D$4"),
ra_ref(4, TRUE, 4, TRUE, "Gabe!!"))
})
test_that("relative ra_refs become NA when converted to A1 formatted string", {
expect_warning(
expect_identical(to_string(ra_ref(2, TRUE, 3, FALSE), fo = "A1"),
NA_character_))
expect_warning(
expect_identical(to_string(ra_ref(2, FALSE, 3, TRUE), fo = "A1"),
NA_character_))
expect_warning(
expect_identical(to_string(ra_ref(-2, FALSE, 3, FALSE), fo = "A1"),
NA_character_))
})
test_that("list of ra_ref's is converted to character", {
rar_list <-
list(ra_ref(), ra_ref(2, TRUE, 5, TRUE), ra_ref(2, FALSE, 5, TRUE))
expect_identical(to_string_v(rar_list), c("R1C1", "R2C5", "R[2]C5"))
expect_warning(
expect_identical(to_string_v(rar_list, fo = "A1"), c("$A$1", "$E$2", NA))
)
})
test_that("rel refs in A1 formatted refs give NA in ra_ref", {
expect_warning(expect_identical(as.ra_ref("A$4"),
ra_ref(4, TRUE, NA, FALSE)))
expect_warning(expect_identical(as.ra_ref("$A4"),
ra_ref(NA, FALSE, 1, TRUE)))
expect_warning(expect_identical(as.ra_ref("A4"),
ra_ref(NA, FALSE, NA, FALSE)))
})
test_that("A1 formatted rel ref string --> absolutized ra_ref if strict = FALSE", {
expect_identical(as.ra_ref("A4", strict = FALSE), ra_ref(4, TRUE, 1, TRUE))
})
test_that("strings that give cell range are not converted to ra_ref", {
expect_error(as.ra_ref("A4:B9"))
})
test_that("valid cell ref string is converted to an ra_ref object", {
expect_identical(as.ra_ref("R[1]C[-4]"), ra_ref(1, FALSE, -4, FALSE))
expect_identical(as.ra_ref("$C$6"), ra_ref(6, TRUE, 3, TRUE))
## special case when rel ref offset is 0 --> no square brackets
expect_warning(x <- as.ra_ref("RC1"))
## omfg RC1 is actually ambiguous
expect_identical(x, ra_ref(0, FALSE, 1, TRUE))
expect_identical(as.ra_ref("RC1", fo = "R1C1"), ra_ref(0, FALSE))
expect_identical(as.ra_ref("R4C"), ra_ref(4, TRUE, 0, FALSE))
})
test_that("ra_ref --> string --> ra_ref round trips work", {
roundtrip <- function(x, fo = NULL)
expect_identical(x, to_string(as.ra_ref(x, fo), fo))
roundtrip("R[1]C[-4]")
roundtrip("RC1", "R1C1")
roundtrip("R4C")
roundtrip("R[-2]C8")
roundtrip("$A$1", "A1")
})
test_that("vectorized version of as.ra_ref.character works", {
input <- c("$A$1", "$F$14", "B$4", "D9")
output <- list(ra_ref(), ra_ref(row_ref = 14, col_ref = 6),
ra_ref(4, col_ref = NA, col_abs = FALSE),
ra_ref(row_ref = 9, col_ref = 4))
output <- stats::setNames(output, input)
expect_warning(
expect_identical(as.ra_ref_v(input, strict = FALSE), output)
)
})
test_that("vectorized version of as.ra_ref.cell_addr works", {
input <- cell_addr(1:3, 1)
output <- list(ra_ref(), ra_ref(row_ref = 2), ra_ref(row_ref = 3))
expect_identical(as.ra_ref_v(input), output)
})
cellranger/tests/testthat/test-A1-R1C1-conversion.R 0000644 0001762 0000144 00000003125 12716670517 021577 0 ustar ligges users context("A1 <--> R1C1 conversion")
test_that("A1 format converts to R1C1 format", {
expect_identical(A1_to_R1C1("$AB$10"), "R10C28")
expect_identical(A1_to_R1C1(c("$A$1", "$ZZ$100")),
c("R1C1", "R100C702"))
})
test_that("A1 relative and mixed references do not get converted", {
expect_warning(x <- A1_to_R1C1("A1"))
expect_identical(x, NA_character_)
expect_warning(x <- A1_to_R1C1(c("A1", "A$1", "$A1", "$A$1")))
expect_identical(x, c(NA_character_, NA_character_, NA_character_, "R1C1"))
})
test_that("strict = FALSE treats relative and mixed A1 references as absolute", {
expect_identical(A1_to_R1C1("A1", strict = FALSE), "R1C1")
expect_warning(
expect_identical(
A1_to_R1C1(c("A1", "A$1", "$A1", "$A$1"), strict = FALSE),
c("R1C1", NA, NA, "R1C1"))
)
})
test_that("R1C1 notation converts to A1 notation", {
expect_identical(R1C1_to_A1("R10C28"), "$AB$10")
expect_identical(R1C1_to_A1("R10C28", strict = FALSE), "AB10")
expect_identical(R1C1_to_A1(c("R1C1", "R100C702")), c("$A$1", "$ZZ$100"))
})
test_that("R1C1 relative and mixed references do not get converted", {
expect_warning(x <- R1C1_to_A1("RC"))
expect_identical(x, NA_character_)
expect_warning(x <- R1C1_to_A1(c("R[1]C[1]", "RC[1]", "R[-2]C")))
expect_identical(x, rep_len(NA_character_, 3))
})
test_that("invalid input is not converted A1 to R1C1", {
expect_error(A1_to_R1C1(1:5))
expect_error(A1_to_R1C1(factor(LETTERS)))
})
test_that("invalid input is not converted R1C1 to A1", {
expect_error(R1C1_to_A1(1:5))
expect_error(R1C1_to_A1(factor(LETTERS)))
})
cellranger/tests/testthat/test-cell-specification.R 0000644 0001762 0000144 00000015676 12712630205 022244 0 ustar ligges users context("cell specification")
test_that("Cell range is converted to a cell_limit object and vice versa", {
rgA1 <- "A1:C4"
rgRC <- "R1C1:R4C3"
rgCL <- cell_limits(ul = c(1, 1), lr = c(4, 3))
expect_equal(as.cell_limits(rgA1), rgCL)
expect_equal(as.cell_limits(rgRC), rgCL)
expect_equal(as.range(rgCL), rgRC)
expect_equal(as.range(rgCL, fo = "A1"), rgA1)
rgA1sheet <- "sheet!A1:C4"
rgRCsheet <- "sheet!R1C1:R4C3"
rgCLwsn <- cell_limits(ul = c(1, 1), lr = c(4, 3), sheet = "sheet")
expect_equal(as.cell_limits(rgA1sheet), rgCLwsn)
expect_equal(as.cell_limits(rgRCsheet), rgCLwsn)
expect_equal(as.range(rgCLwsn), rgRCsheet)
expect_equal(as.range(rgCLwsn, sheet = FALSE), rgRC)
expect_equal(as.range(rgCLwsn, fo = "A1"), rgA1sheet)
rgA1 <- "E7"
rgA1A1 <- "E7:E7"
rgRC <- "R7C5"
rgRCRC <- "R7C5:R7C5"
rgCL <- cell_limits(ul = c(7, 5), lr = c(7, 5))
expect_equal(as.cell_limits(rgA1), rgCL)
expect_equal(as.cell_limits(rgRC), rgCL)
expect_equal(as.cell_limits(rgA1A1), rgCL)
expect_equal(as.cell_limits(rgRCRC), rgCL)
expect_equal(as.range(rgCL), rgRCRC)
expect_equal(as.range(rgCL, fo = "A1"), rgA1A1)
rgA1sheet <- "sheet!E7"
rgA1A1sheet <- "sheet!E7:E7"
rgRCsheet <- "sheet!R7C5"
rgRCRCsheet <- "sheet!R7C5:R7C5"
rgCLsheet <- cell_limits(ul = c(7, 5), lr = c(7, 5), sheet = "sheet")
expect_equal(as.cell_limits(rgA1sheet), rgCLsheet)
expect_equal(as.cell_limits(rgRCsheet), rgCLsheet)
expect_equal(as.cell_limits(rgA1A1sheet), rgCLsheet)
expect_equal(as.cell_limits(rgRCRCsheet), rgCLsheet)
expect_equal(as.range(rgCLsheet), rgRCRCsheet)
expect_equal(as.range(rgCLsheet, fo = "A1"), rgA1A1sheet)
rgCL <- cell_limits(ul = c(NA, 1), lr = c(4, NA))
expect_true(is.na(as.range(rgCL)))
})
test_that("Whitespace-containing sheet names gain/lose single quotes", {
x <- cell_limits(ul = c(1, 1), lr = c(4, 3), sheet = "aaa bbb")
expect_identical(as.range(x), "'aaa bbb'!R1C1:R4C3")
expect_identical(as.cell_limits("'aaa bbb'!R1C1:R4C3"), x)
})
test_that("Bad cell ranges throw errors", {
expect_warning(expect_error(as.cell_limits("eggplant")))
expect_warning(expect_error(as.cell_limits("A:B10")))
expect_warning(expect_error(as.cell_limits(":B10")))
expect_error(as.cell_limits("A1:R3C3"))
expect_error(as.cell_limits("A1:B2:C3"))
expect_warning(expect_error(as.cell_limits("14:17")))
expect_error(as.cell_limits(14:17))
expect_error(as.cell_limits(B2:D9))
expect_error(cell_limits(ul = c(-1, 1), lr = c(3, 4)))
expect_error(cell_limits(ul = c(0, 1), lr = c(3, 4)))
expect_error(cell_limits(ul = c(1, 4), lr = c(3, 1)))
})
test_that("Degenerate, all-NA input is tolerated", {
cl <- cell_limits()
expect_is(cl, "cell_limits")
expect_is(cl$ul, "integer")
cl2 <- cell_limits(c(NA, NA))
expect_identical(cl, cl2)
cl3 <- cell_limits(lr = c(NA, NA))
expect_identical(cl, cl3)
})
test_that("as.cell_limits can operate on NULL input", {
expect_identical(as.cell_limits(NULL), cell_limits())
})
test_that("cell_limits objects inherit from list", {
expect_is(cell_limits(), "list")
})
test_that("Row-only specifications work", {
expect_identical(cell_rows(c(NA, NA)), cell_limits())
expect_identical(cell_rows(c(NA, 3)), cell_limits(lr = c(3, NA)))
expect_identical(cell_rows(c(7, NA)), cell_limits(c(7, NA)))
expect_identical(cell_rows(c(3, NA, 10)), cell_limits(c(3, NA), c(10, NA)))
expect_identical(cell_rows(c(10, NA, 3)), cell_limits(c(3, NA), c(10, NA)))
expect_identical(cell_rows(4:16), cell_limits(c(4, NA), c(16, NA)))
expect_error(cell_rows(c(7, 2)))
})
test_that("Column-only specifications work", {
expect_identical(cell_cols(c(NA, NA)), cell_limits())
expect_identical(cell_cols(c(NA, 3)), cell_limits(lr = c(NA, 3)))
expect_identical(cell_cols(c(7, NA)), cell_limits(c(NA, 7)))
expect_identical(cell_cols(c(3, NA, 10)), cell_limits(c(NA, 3), c(NA, 10)))
expect_identical(cell_cols(c(10, NA, 3)), cell_limits(c(NA, 3), c(NA, 10)))
expect_identical(cell_cols(4:16), cell_limits(c(NA, 4), c(NA, 16)))
expect_error(cell_cols(c(7, 2)))
expect_identical(cell_cols("B:D"), cell_limits(c(NA, 2), c(NA, 4)))
expect_identical(cell_cols(c("C", "ZZ")), cell_limits(c(NA, 3), c(NA, 702)))
expect_identical(cell_cols(c("C", NA)), cell_limits(c(NA, 3)))
expect_error(cell_cols("Z:M"))
expect_error(cell_cols(c("Z", "M")))
})
test_that("Print method works", {
expect_output(print(cell_limits(c(NA, 7), c(3, NA))),
"", fixed = TRUE)
expect_output(print(cell_limits(c(NA, 7), c(3, NA), "a sheet")),
"", fixed = TRUE)
})
test_that("dim method works", {
expect_equivalent(dim(as.cell_limits("A1")), c(1, 1))
expect_equivalent(dim(as.cell_limits("A1:F10")), c(10, 6))
expect_equivalent(dim(cell_limits(c(1, 2), c(1, 5))), c(1, 4))
expect_equivalent(dim(cell_limits(c(NA, 2), c(1, 5))), c(1, 4))
expect_equivalent(dim(cell_limits(c(NA, 2), c(NA, 5))), c(NA_integer_, 4))
expect_equivalent(dim(cell_limits(c(1, 1))), c(NA_integer_, NA_integer_))
})
test_that("Cell limits can be specified via anchor", {
## no input
expect_identical(anchored(), as.cell_limits("A1"))
expect_identical(anchored(anchor = "R4C2", dim = c(8, 2)),
cell_limits(c(4, 2), c(11, 3)))
expect_identical(anchored(anchor = "A1", dim = c(3, 3), col_names = FALSE),
cell_limits(c(1, 1), c(3, 3)))
expect_identical(anchored(anchor = "A1", dim = c(3, 3), col_names = TRUE),
cell_limits(c(1, 1), c(4, 3)))
## 2-dimensional input
input <- head(iris)
expect_identical(anchored(anchor = "R3C7", input = input),
cell_limits(c(3, 7), c(9, 11)))
expect_identical(anchored(anchor = "R3C7", input = input, col_names = TRUE),
cell_limits(c(3, 7), c(9, 11)))
expect_identical(anchored(anchor = "R3C7", input = input, col_names = FALSE),
cell_limits(c(3, 7), c(8, 11)))
## dim should have no effect here
expect_identical(anchored(anchor = "R3C7", input = input, dim = c(2,2)),
cell_limits(c(3, 7), c(9, 11)))
## 1-dimensional input
input <- LETTERS[1:8]
expect_identical(anchored(anchor = "B5", input = input),
cell_limits(c(5, 2), c(12, 2)))
expect_identical(anchored(anchor = "B5", input = input, byrow = TRUE),
cell_limits(c(5, 2), c(5, 9)))
## dim and col_names should have no effect here
expect_identical(anchored(anchor = "B5", input = input, dim = c(5,5)),
cell_limits(c(5, 2), c(12, 2)))
expect_identical(anchored(anchor = "B5", input = input, col_names = TRUE),
cell_limits(c(5, 2), c(12, 2)))
expect_error(anchored(1))
expect_warning(expect_error(anchored("A")))
expect_error(anchored("A1:B10"))
expect_error(anchored(dim = "eggplant"))
expect_error(anchored(dim = 1:3))
expect_error(anchored(input = head(iris, 2),
col_names = as.character(length(iris))))
})
cellranger/tests/testthat/test-cell-addr-class.R 0000644 0001762 0000144 00000007625 12716427703 021447 0 ustar ligges users context("cell_addr class")
test_that("cell_addr constructor requires row and col and checks lengths", {
expect_error(cell_addr(1))
expect_error(cell_addr(1:2, 1:3))
})
test_that("cell_addr constructor rejects row, column < 1", {
expect_error(cell_addr(1, -1))
expect_error(cell_addr(-1, 1))
})
test_that("cell_addr constructor works for single cell", {
ca <- cell_addr(3, 7)
expect_is(ca, "cell_addr")
expect_identical(ca$row, 3L)
expect_identical(ca$col, 7L)
})
test_that("cell_addr constructor works for multiple cells", {
ca <- cell_addr(c(3, 10), c(7, 2))
expect_is(ca, "cell_addr")
expect_identical(ca$row, c(3L, 10L))
expect_identical(ca$col, c(7L, 2L))
})
test_that("cell_addr constructor recycles length 1 row or col", {
ca <- cell_addr(1:3, 6)
expect_is(ca, "cell_addr")
expect_identical(ca$row, 1:3)
expect_identical(ca$col, rep_len(6L, 3))
})
test_that("cell_addr constructor accepts NAs (and is not picky about type)", {
ca <- cell_addr(NA, NA)
expect_is(ca, "cell_addr")
expect_identical(ca$row, NA_integer_)
expect_identical(ca$col, NA_integer_)
})
test_that("cell_addr `[` indexing works", {
ca <- cell_addr(1:3, 6)
expect_identical(ca[c(1, 3)], cell_addr(c(1L, 3L), 6L))
expect_identical(ca[-2], cell_addr(c(1L, 3L), 6L))
expect_identical(ca[c(TRUE, FALSE, TRUE)], cell_addr(c(1L, 3L), 6L))
})
test_that("cell_addr `[[` indexing works", {
ca <- cell_addr(1:3, 6)
expect_identical(ca[[3]], cell_addr(3L, 6L))
expect_error(ca[[-2]])
})
test_that("cell_addr length method works", {
ca <- cell_addr(1:3, 6)
expect_length(ca, 3L)
})
test_that("row and column extraction work for cell_addr objects", {
ca <- cell_addr(1:3, 6)
expect_identical(addr_row(ca), 1:3)
expect_identical(addr_col(ca), rep_len(6L, 3))
})
test_that("cell_addr objects can be converted to ra_ref", {
expect_identical(as.ra_ref(cell_addr(2, 5)),
ra_ref(row_ref = 2, col_ref = 5))
})
test_that("cell_addr objects can be converted to string", {
expect_identical(to_string(cell_addr(3, 8)), "R3C8")
expect_identical(to_string(cell_addr(3, 8), fo = "A1"), "$H$3")
expect_identical(to_string(cell_addr(3, 8), fo = "A1", strict = FALSE), "H3")
})
test_that("valid ra_ref objects can be converted to cell_addr", {
expect_identical(as.cell_addr(ra_ref()), cell_addr(1, 1))
expect_identical(as.cell_addr(ra_ref(2, TRUE, 5, TRUE)), cell_addr(2, 5))
})
test_that("ra_ref objects w/ NAs become cell_addr objects with NAs", {
expect_warning(expect_identical(as.cell_addr(ra_ref(2, FALSE, 5, FALSE)),
cell_addr(NA, NA)))
expect_warning(expect_identical(as.cell_addr(ra_ref(2, TRUE, 5, FALSE)),
cell_addr(2, NA)))
expect_warning(expect_identical(as.cell_addr(ra_ref(2, FALSE, 5, TRUE)),
cell_addr(NA, 5)))
})
test_that("valid cell ref strings can be converted to cell_addr", {
expect_identical(as.cell_addr("$D$12"), cell_addr(12, 4))
expect_identical(as.cell_addr("R4C3"), cell_addr(4, 3))
})
test_that("relative refs in cell ref strings create NAs in cell_addr", {
expect_warning(expect_identical(as.cell_addr("$F2"), cell_addr(NA, 6)))
expect_warning(expect_identical(as.cell_addr("F$2"), cell_addr(2, NA)))
expect_warning(expect_identical(as.cell_addr("R4C[3]"), cell_addr(4, NA)))
expect_warning(expect_identical(as.cell_addr("RC"), cell_addr(NA, NA)))
})
test_that("relative cell ref strings convert to cell_addr if strict = FALSE", {
expect_identical(as.cell_addr("F2", strict = FALSE), as.cell_addr("$F$2"))
})
test_that("a vector of cell ref strings is converted to cell_addr", {
x <- as.cell_addr(c("$D$12", "$C$4"))
expect_identical(x, cell_addr(row = c(12L, 4L), col = c(4L, 3L)))
y <- lapply(c("$D$12", "$C$4"), as.cell_addr)
## strip cell_addr class so its indexing methods don't mess with transpose
y <- lapply(y, unclass)
expect_equivalent(x, transpose(y))
})
cellranger/NAMESPACE 0000644 0001762 0000144 00000002240 12716427703 013616 0 ustar ligges users # Generated by roxygen2: do not edit by hand
S3method("[",cell_addr)
S3method("[[",cell_addr)
S3method(addr_col,cell_addr)
S3method(addr_row,cell_addr)
S3method(as.cell_addr,character)
S3method(as.cell_addr,ra_ref)
S3method(as.cell_addr_v,character)
S3method(as.cell_addr_v,list)
S3method(as.cell_limits,"NULL")
S3method(as.cell_limits,cell_limits)
S3method(as.cell_limits,character)
S3method(as.ra_ref,cell_addr)
S3method(as.ra_ref,character)
S3method(as.ra_ref_v,cell_addr)
S3method(as.ra_ref_v,character)
S3method(dim,cell_limits)
S3method(length,cell_addr)
S3method(print,cell_addr)
S3method(print,cell_limits)
S3method(print,ra_ref)
S3method(to_string,cell_addr)
S3method(to_string,ra_ref)
S3method(to_string_v,cell_addr)
S3method(to_string_v,list)
export(A1_to_R1C1)
export(R1C1_to_A1)
export(addr_col)
export(addr_row)
export(anchored)
export(as.cell_addr)
export(as.cell_addr_v)
export(as.cell_limits)
export(as.ra_ref)
export(as.ra_ref_v)
export(as.range)
export(cell_addr)
export(cell_cols)
export(cell_limits)
export(cell_rows)
export(guess_fo)
export(is_A1)
export(is_R1C1)
export(letter_to_num)
export(num_to_letter)
export(ra_ref)
export(to_string)
export(to_string_v)
cellranger/NEWS.md 0000644 0001762 0000144 00000002774 12745602530 013504 0 ustar ligges users # cellranger 1.1.0
Rebooting to support parsing of spreadsheet formulas and cell references as they appear in unevaluated expressions. This work is still in progress but a CRAN update is required now to update a test for testthat v1.0.x.
Package is beginning to implement classes and methods related to cell location and reference from 'Spreadsheet Implementation Technology' by Peter Sestoft, MIT Press, 2014.
New classes:
* `cell_addr`: one or more absolute cell addresses
* `ra_ref`: single absolute, relative, or mixed cell reference
# cellranger 1.0.0
* The two components of a `cell_limits` object now correspond NOT to row and column limits, but rather to the upper left and lower right cells of the rectangle. See #6. It was too confusing to have different conventions for the object and its print method.
* If the maximum row or column is specified, but the minimum is not, then we automatically set the associated minimum to 1, instead of leaving as `NA`.
* The `header` argument of `anchored()` has been renamed to `col_names`, for greater consistency with [`readr`](https://github.com/hadley/readr), [`readxl`](https://github.com/hadley/readxl), and [`googlesheets`](https://github.com/jennybc/googlesheets/).
* Added a `NULL` method for `as.cell_limits` generic so that `as.cell_limits(NULL)` returns default, degenerate cell limits, i.e. the min and max for rows and columns are uniformly `NA`.
* A `cell_limits` object now inherits from "list".
# cellranger 0.1.0
* Initial CRAN release
cellranger/R/ 0000755 0001762 0000144 00000000000 12740704604 012575 5 ustar ligges users cellranger/R/anchor.R 0000644 0001762 0000144 00000006600 12712114434 014167 0 ustar ligges users #' Specify cell limits via an anchor cell
#'
#' Specify the targetted cell rectangle via an upper left anchor cell and the
#' rectangle's row and column extent. The extent can be specified directly via
#' \code{dims} or indirectly via the \code{input} object. Specification via
#' \code{input} anticipates a write operation into the spreadsheet. If
#' \code{input} is one-dimensional, the \code{byrow} argument controls whether
#' the rectangle will extend down from the anchor or to the right. If
#' \code{input} is two-dimensional, the \code{col_names} argument controls
#' whether cells will be reserved for column or variable names. If
#' \code{col_names} is unspecified, default behavior is to set it to \code{TRUE}
#' if \code{input} has columns names and \code{FALSE} otherwise.
#'
#' @param anchor character, specifying the upper left cell in "A1" or "R1C1"
#' notation
#' @param dim integer vector, of length two, holding the number of rows and
#' columns of the targetted rectangle; ignored if \code{input} is provided
#' @param input a one- or two-dimensioanl input object, used to determine the
#' extent of the targetted rectangle
#' @param col_names logical, indicating whether a row should be reserved for the
#' column or variable names of a two-dimensional input; if omitted, will be
#' determined by checking whether \code{input} has column names
#' @param byrow logical, indicating whether a one-dimensional input should run
#' down or to the right
#'
#' @return a \code{\link{cell_limits}} object
#'
#' @examples
#' anchored()
#' as.range(anchored())
#' dim(anchored())
#'
#' anchored("Q24")
#' as.range(anchored("Q24"))
#' dim(anchored("Q24"))
#'
#' anchored(anchor = "R4C2", dim = c(8, 2))
#' as.range(anchored(anchor = "R4C2", dim = c(8, 2)))
#' as.range(anchored(anchor = "R4C2", dim = c(8, 2)), fo = "A1")
#' dim(anchored(anchor = "R4C2", dim = c(8, 2)))
#'
#' (input <- head(iris))
#' anchored(input = input)
#' as.range(anchored(input = input))
#' dim(anchored(input = input))
#'
#' anchored(input = input, col_names = FALSE)
#' as.range(anchored(input = input, col_names = FALSE))
#' dim(anchored(input = input, col_names = FALSE))
#'
#' (input <- LETTERS[1:8])
#' anchored(input = input)
#' as.range(anchored(input = input))
#' dim(anchored(input = input))
#'
#' anchored(input = input, byrow = TRUE)
#' as.range(anchored(input = input, byrow = TRUE))
#' dim(anchored(input = input, byrow = TRUE))
#'
#' @export
anchored <- function(anchor = "A1",
dim = c(1L, 1L), input = NULL,
col_names = NULL, byrow = FALSE) {
anchorCL <- as.cell_limits(anchor)
stopifnot(dim(anchorCL) == c(1L, 1L), isTOGGLE(col_names), isTOGGLE(byrow))
if(is.null(input)) {
stopifnot(length(dim) == 2L)
input_extent <- as.integer(dim)
if(is.null(col_names)) {
col_names <- FALSE
}
} else {
if(is.null(dim(input))) { # input is 1-dimensional
col_names <- FALSE
input_extent <- c(length(input), 1L)
if(byrow) {
input_extent <- rev(input_extent)
}
} else { # input is 2-dimensional
stopifnot(length(dim(input)) == 2L)
if(is.null(col_names)) {
col_names <- !is.null(colnames(input))
}
input_extent <- dim(input)
}
}
if(col_names) {
input_extent[1] <- input_extent[1] + 1
}
cell_limits(ul = anchorCL$ul,
lr = anchorCL$lr + input_extent - 1)
}
cellranger/R/A1-to-from-RC.R 0000644 0001762 0000144 00000010605 12721413510 015036 0 ustar ligges users ## vectorized over x and always returns list
## strict = FALSE --> pure relative references treated as absolute
## example: F4 treated like $F$4
A1_to_ra_ref <- function(x, strict = TRUE) {
y <- rematch::re_match(.cr$A1_ncg_rx, x)
## y is character matrix, rows are elements of x, cols are pieces of the ref
## input D$56 gives this row
## row_abs = "$" row_ref = "56" col_abs = "" col_ref = "D"
## presence of "$" decoration in original row or col ref --> absolute
if (!strict) { # absolutize pure relative refs; mixed refs not changed
rel <- y[ , "col_abs"] == "" & y[ , "row_abs"] == ""
y[rel, c("col_abs", "row_abs")] <- "$"
}
row_not_abs <- y[ , "row_abs"] != "$"
y[row_not_abs, "row_ref"] <- NA
col_not_abs <- y[ , "col_abs"] != "$"
y[col_not_abs, "col_ref"] <- NA
mapply(ra_ref,
stats::setNames(y[ , "row_ref"], y[ , ".match"]), y[ , "row_abs"] == "$",
letter_to_num(y[ , "col_ref"]), y[ , "col_abs"] == "$",
SIMPLIFY = FALSE)
}
## vectorized over x and always returns list
R1C1_to_ra_ref <- function(x) {
y <- rematch::re_match(.cr$R1C1_ncg_rx, x)
## y is character matrix, rows are elements of x, cols are pieces of the ref
## input R[-4]C7 gives this row
## row_abs = "[" row_ref = "-4" col_abs = "" col_ref = "7"
## presence of square brackets `[x]` --> relative
## EXCEPT when row or column reference is empty, e.g., RC, RCx, RxC
## which means "this row or column" --> offset is 0 and ref is relative
row_ref_missing <- y[ , "row_ref"] == ""
y[row_ref_missing, "row_abs"] <- "["
y[row_ref_missing, "row_ref"] <- "0"
col_ref_missing <- y[ , "col_ref"] == ""
y[col_ref_missing, "col_abs"] <- "["
y[col_ref_missing, "col_ref"] <- "0"
mapply(ra_ref,
stats::setNames(y[ , "row_ref"], y[ , ".match"]), y[ , "row_abs"] == "",
y[ , "col_ref"], y[ , "col_abs"] == "",
SIMPLIFY = FALSE)
}
#' Convert cell reference strings from A1 to R1C1 format
#'
#' Convert cell reference strings from A1 to R1C1 format. Strictly speaking,
#' this only makes sense for absolute references, such as \code{"$B$4"}. Why?
#' Because otherwise, we'd have to know the host cell of the reference. Set
#' \code{strict = FALSE} to relax and treat pure relative references, like
#' (\code{"B4"}), as if they are absolute. Mixed references, like
#' (\code{"B$4"}), will always return \code{NA}, no matter the value of
#' \code{strict}.
#'
#' @param x character vector of cell references in A1 format
#' @template param-strict
#'
#' @return character vector of absolute cell references in R1C1 format
#'
#' @examples
#' A1_to_R1C1("$A$1")
#' A1_to_R1C1("A1") ## raises a warning, returns NA
#' A1_to_R1C1("A1", strict = FALSE) ## unless strict = FALSE
#' A1_to_R1C1(c("A1", "B$4")) ## raises a warning, includes an NA, because
#' A1_to_R1C1(c("A1", "B$4"), strict = FALSE) ## mixed ref always returns NA
#' @export
A1_to_R1C1 <- function(x, strict = TRUE) {
stopifnot(is.character(x), all(is_A1(x)))
y <- unname(A1_to_ra_ref(x, strict = strict))
not_abs <- vapply(y, is_not_abs_ref, logical(1))
if (any(not_abs)) {
warning("Mixed or relative cell references found ... NAs generated",
call. = FALSE)
}
vapply(y, to_string, character(1))
}
#' Convert R1C1 positioning notation to A1 notation
#'
#' Convert cell reference strings from R1C1 to A1 format. This only makes sense
#' for absolute references, such as \code{"R4C2"}. Why? Because otherwise, we'd
#' have to know the host cell of the reference. Relative and mixed references,
#' like (\code{"R[3]C[-1]"} and \code{"R[1]C5"}), will therefore return
#' \code{NA}.
#'
#' @param x vector of cell positions in R1C1 notation
#' @template param-strict
#'
#' @return character vector of absolute cell references in A1 notation
#'
#' @examples
#' R1C1_to_A1("R1C1")
#' R1C1_to_A1("R10C52", strict = FALSE)
#' R1C1_to_A1(c("R1C1", "R10C52", "RC4", "R[-3]C[9]"))
#' @export
R1C1_to_A1 <- function(x, strict = TRUE) {
stopifnot(is.character(x), all(is_R1C1(x)))
y <- unname(R1C1_to_ra_ref(x))
abs <- vapply(y, is_abs_ref, logical(1))
if (any(!abs)) {
warning("Ambiguous cell references ... NAs generated", call. = FALSE)
y[!abs] <- lapply(y[!abs], function(x) {
ra_ref(row_ref = NA, row_abs = NA, col_ref = NA, col_abs = NA,
sheet = x$sheet, file = x$file)
})
}
vapply(y, to_string, character(1), fo = "A1", strict = strict)
}
cellranger/R/utils.R 0000644 0001762 0000144 00000003026 12716640620 014061 0 ustar ligges users char0_to_NA <- function(x) if (length(x) < 1) NA_character_ else x
isFALSE <- function(x) identical(x, FALSE)
isTOGGLE <- function(x) is.null(x) || isTRUE(x) || isFALSE(x)
isTRUE_v <- function(x) !is.na(x) & x
## https://twitter.com/kevin_ushey/status/710223546929119232
transpose <- function(list) do.call(Map, c(c, list))
## from
## https://github.com/hadley/purrr/blob/9534c29411f4ec262995498b0c2a78d0a619eae4/R/utils.R#L149-L155
## among other places
`%||%` <- function(x, y) if (is.null(x)) y else x
add_single_quotes <- function(x) {
if (grepl("\\s+", x)) {
x <- paste0("'", x, "'")
}
x
}
remove_single_quotes <- function(x) gsub("^'|'$", "", x)
rel_abs_format <- function(is_abs, rc_ref, fo = c("R1C1", "A1")) {
fo <- match.arg(fo)
if (fo == "A1") return(if (isTRUE_v(is_abs)) "$" else "")
## R1C1 case:
if (isTRUE_v(is_abs)) return(rc_ref)
## unfortunate convention where R and C are used instead of R[0] and C[0]
if (!is.na(rc_ref) && rc_ref == 0) "" else paste0("[", rc_ref, "]")
}
is_abs_ref <- function(x) {
stopifnot(inherits(x, "ra_ref"))
isTRUE(x$row_abs) && isTRUE(x$col_abs)
}
is_rel_ref <- function(x) {
stopifnot(inherits(x, "ra_ref"))
isFALSE(x$row_abs) && isFALSE(x$col_abs)
}
is_not_abs_ref <- function(x) {
stopifnot(inherits(x, "ra_ref"))
!isTRUE(x$row_abs) || !isTRUE(x$col_abs)
}
absolutize <- function(x) {
stopifnot(inherits(x, "ra_ref"))
x$row_abs <- x$col_abs <- TRUE
x
}
relativize <- function(x) {
stopifnot(inherits(x, "ra_ref"))
x$row_abs <- x$col_abs <- FALSE
x
}
cellranger/R/ra-ref.R 0000644 0001762 0000144 00000017161 12716673402 014106 0 ustar ligges users #' ra_ref class
#'
#' The \code{ra_ref} class is used to represent a single relative, absolute, or
#' mixed cell reference, presumably found in a formula. When \code{row_abs} is
#' \code{TRUE}, it means that \code{row_ref} identifies a specific row in an
#' absolute sense. When \code{row_abs} is \code{FALSE}, it means that
#' \code{row_ref} holds a positive, zero, or negative offset relative to the
#' address of the cell containing the formula that contains the associated cell
#' reference. Ditto for \code{col_abs} and \code{col_ref}.
#'
#' A \code{ra_ref} object can also store the name of a sheet and a file, though
#' these will often be \code{NA}. A cell reference in a formula can potentially
#' be qualified like this: \code{[my_workbook.xlxs]Sheet1!R2C3}. In Testoft
#' (2014), he creates an entirely separate class for this, a \code{cell_ref},
#' which consists of a sheet- and file-ignorant \code{ra_ref} object and a sheet
#' reference (he doesn't allow formulas to refer to other files). I hope I
#' don't regret choosing a different path.
#'
#' @param row_ref integer, row or row offset
#' @param row_abs logical indicating whether \code{row_ref} is absolute or
#' relative
#' @param col_ref integer, column or column offset
#' @param col_abs logical indicating whether \code{col_ref} is absolute or
#' relative
#' @param sheet the name of a sheet (a.k.a. worksheet or tab)
#' @param file the name of a file (a.k.a. workbook)
#'
#' @return a \code{ra_ref} object
#' @export
#'
#' @template reference-sestoft
#'
#' @examples
#' ra_ref()
#' ra_ref(row_ref = 3, col_ref = 2)
#' ra_ref(row_ref = 10, row_abs = FALSE, col_ref = 3, col_abs = TRUE)
#' ra_ref(sheet = "a sheet")
ra_ref <- function(row_ref = 1L,
row_abs = TRUE,
col_ref = 1L,
col_abs = TRUE,
sheet = NA_character_,
file = NA_character_) {
row_ref <- as.integer(row_ref)
col_ref <- as.integer(col_ref)
stopifnot(length(row_ref) == 1L, length(row_abs) == 1L,
length(col_ref) == 1L, length(col_abs) == 1L,
is.logical(row_abs), is.logical(col_abs),
is.character(sheet), is.character(file),
length(sheet) == 1, length(file) == 1)
if ( (isTRUE(row_abs) && isTRUE(row_ref < 1)) ||
(isTRUE(col_abs) && isTRUE(col_ref < 1)) ) {
stop("Absolute row or column references must be >= 1:\n",
" row_abs = ", row_abs, ", row_ref = ", row_ref, "\n",
" col_abs = ", col_abs, ", col_ref = ", col_ref, "\n",
call. = FALSE)
}
structure(list(row_ref = row_ref, row_abs = row_abs,
col_ref = col_ref, col_abs = col_abs,
sheet = sheet, file = file),
class = c("ra_ref", "list"))
}
#' Print ra_ref object
#'
#' @param x an object of class \code{\link{ra_ref}}
#'
#' @template param-fo
#' @template param-ddd
#'
#' @examples
#' (rar <- ra_ref(3, TRUE, 1, TRUE))
#' print(ra_ref(), fo = "A1")
#'
#' @export
print.ra_ref <- function(x, fo = c("R1C1", "A1"), ...) {
fo <- match.arg(fo)
ra_part <- c(`TRUE` = "abs", `FALSE` = "rel", `NA` = "NA")
row_ra <- ra_part[as.character(x$row_abs)]
col_ra <- ra_part[as.character(x$col_abs)]
sheet_part <- paste0(" sheet: ", add_single_quotes(x$sheet), "\n")
sheet_part <- if (is.na(x$sheet)) "" else sheet_part
cat("\n")
cat(" row: ", x$row_ref, " (", row_ra, ")\n",
" col: ", x$col_ref, " (", col_ra, ")\n",
sheet_part, sep = "")
## no printing of file name ... wait til I see it needed IRL
cat(" ", to_string(x, fo = fo), "\n", sep = "")
}
#' Convert to a ra_ref object
#'
#' Convert various representations of a cell reference into an object of class
#' \code{\link{ra_ref}}.
#' \itemize{
#' \item \code{as.ra_ref} is NOT vectorized and therefore requires the input to
#' represent exactly one cell, i.e. be of length 1.
#' \item \code{as.ra_ref_v} accepts input of length >= 1 and returns a list of
#' \code{\link{ra_ref}} objects.
#' }
#'
#' @param x one or more cell references, as a character vector or
#' \code{\link{cell_addr}} object
#' @template param-ddd
#'
#' @return a \code{\link{ra_ref}} object, in the case of \code{as.ra_ref}, or a
#' list of them, in the case of \code{as.ra_ref_v}
#' @name as.ra_ref
NULL
#' @rdname as.ra_ref
#' @export
as.ra_ref <- function(x, ...) UseMethod("as.ra_ref")
#' @rdname as.ra_ref
#' @export
as.ra_ref_v <- function(x, ...) UseMethod("as.ra_ref_v")
#' @rdname as.ra_ref
#' @template param-fo
#' @template param-strict
#'
#' @examples
#' ## as.ra_ref.character()
#' as.ra_ref("$F$2")
#' as.ra_ref("R[-4]C3")
#' as.ra_ref("B4")
#' as.ra_ref("B4", strict = FALSE)
#' as.ra_ref("B$4")
#'
#' ## this is actually ambiguous! is format A1 or R1C1 format?
#' as.ra_ref("RC2")
#' ## format could be specified in this case
#' as.ra_ref("RC2", fo = "R1C1")
#' as.ra_ref("RC2", fo = "A1", strict = FALSE)
#'
#' @export
as.ra_ref.character <- function(x, fo = NULL, strict = TRUE, ...) {
stopifnot(is.character(x))
if (length(x) > 1) {
stop("Input must have length 1. Maybe you want the vectorized as.ra_ref_v()?")
}
as.ra_ref_v(x, fo = fo, strict = strict)[[1]]
}
#' @rdname as.ra_ref
#' @export
#' @examples
#' ## as.ra_ref_v.character()
#' cs <- c("$A$1", "Sheet1!$F$14", "Sheet2!B$4", "D9")
#' \dontrun{
#' ## won't work because as.ra_ref requires length one input
#' as.ra_ref(cs)
#' }
#' ## use as.ra_ref_v instead
#' as.ra_ref_v(cs, strict = FALSE)
as.ra_ref_v.character <- function(x, fo = NULL, strict = TRUE, ...) {
parsed <- rematch::re_match(.cr$string_rx, x)
colnames(parsed) <- c("input", "file", "sheet", "ref", "invalid")
is_range <- grepl(":", parsed[ , "ref"])
if (any(is_range)) {
stop("Cell ranges not allowed here.\n", call. = FALSE)
}
if (is.null(fo)) {
fo <- unique(guess_fo(parsed[ , "ref"]))
if ("R1C1" %in% fo && "A1" %in% fo) {
## TODO? be willing to handle a mix of A1 and R1C1 refs
stop("Cell references aren't uniformly A1 or R1C1 format:\n",
call. = FALSE)
}
if (anyNA(fo) && length(fo) > 1) {
## (A1, NA) --> A1, (R1C1, NA) --> R1C1, NA --> NA
fo <- fo[!is.na(fo)]
}
}
if (identical(fo, "A1")) {
rar <- A1_to_ra_ref(parsed[ , "ref"], strict = strict)
if (anyNA(vapply(rar, `[[`, integer(1), "row_ref")) ||
anyNA(vapply(rar, `[[`, integer(1), "col_ref"))) {
warning("Non-absolute A1-formatted reference ... NAs generated",
call. = FALSE)
}
} else { ## catches fo = "R1C1" and fo = NA
rar <- R1C1_to_ra_ref(parsed[ , "ref"])
}
has_sheet <- nzchar(parsed[ , "sheet"])
rar[has_sheet] <- mapply(function(x, sheet) {x$sheet <- sheet; x},
rar[has_sheet], parsed[has_sheet, "sheet"],
SIMPLIFY = FALSE)
has_file <- nzchar(parsed[ , "file"])
rar[has_file] <- mapply(function(x, file) {x$file <- file; x},
rar[has_file], parsed[has_file, "file"],
SIMPLIFY = FALSE)
rar
}
#' @rdname as.ra_ref
#' @export
#' @examples
#' ## as.ra_ref.cell_addr
#' ca <- cell_addr(2, 5)
#' as.ra_ref(ca)
as.ra_ref.cell_addr <- function(x, ...) {
stopifnot(length(x) == 1L)
ra_ref(row_ref = addr_row(x), row_abs = if (is.na(addr_row(x))) NA else TRUE,
col_ref = addr_col(x), col_abs = if (is.na(addr_row(x))) NA else TRUE)
}
#' @rdname as.ra_ref
#' @export
#' @examples
#' ## as.ra_ref_v.cell_addr()
#'
#' ca <- cell_addr(1:3, 1)
#' \dontrun{
#' ## won't work because as.ra_ref methods not natively vectorized
#' as.ra_ref(ca)
#' }
#' ## use as.ra_ref_v instead
#' as.ra_ref_v(ca)
as.ra_ref_v.cell_addr <- function(x, ...) {
mapply(ra_ref, row_ref = addr_row(x), col_ref = addr_col(x), SIMPLIFY = FALSE)
}
cellranger/R/to-string.R 0000644 0001762 0000144 00000010353 12716427703 014655 0 ustar ligges users #' Get string representation of cell references
#'
#' Convert various representations of a cell reference to character
#' \itemize{
#' \item \code{to_string} is not necessarily vectorized. For example, when the
#' the input is of class \code{\link{ra_ref}}, it must of be of length one.
#' However, to be honest, this will actually work for \code{\link{cell_addr}},
#' even when length > 1.
#' \item \code{to_string_v} is guaranteed to be vectorized. In particular, input
#' can be a \code{\link{cell_addr}} of length >= 1 or a list of
#' \code{\link{ra_ref}} objects.
#' }
#' If either the row or column reference is relative, note that, in general,
#' it's impossible to convert to an "A1" formatted string. We would have to know
#' "relative to what?".
#'
#' @param x a suitable representation of a cell or cell area reference: a single
#' \code{\link{ra_ref}} object or a list of them or a \code{\link{cell_addr}}
#' object
#' @template param-fo
#' @template param-strict
#' @template param-sheet
#' @template param-ddd
#'
#' @return a character vector
#' @name to_string
NULL
#' @rdname to_string
#' @export
to_string <-
function(x, fo = c("R1C1", "A1"),
strict = TRUE, sheet = NULL, ...) UseMethod("to_string")
#' @rdname to_string
#' @export
to_string_v <-
function(x, fo = c("R1C1", "A1"),
strict = TRUE, sheet = NULL, ...) UseMethod("to_string_v")
#' @rdname to_string
#' @examples
#' ## exactly one ra_ref --> string
#' to_string(ra_ref())
#' to_string(ra_ref(), fo = "A1")
#' to_string(ra_ref(), fo = "A1", strict = FALSE)
#' to_string(ra_ref(row_ref = 3, col_ref = 2))
#' to_string(ra_ref(row_ref = 3, col_ref = 2, sheet = "helloooo"))
#' (mixed_ref <- ra_ref(row_ref = 10, row_abs = FALSE, col_ref = 3))
#' to_string(mixed_ref)
#'
#' ## this will raise warning and generate NA, because row reference is
#' ## relative and format is A1
#' to_string(mixed_ref, fo = "A1")
#'
#' @export
to_string.ra_ref <- function(x, fo = c("R1C1", "A1"),
strict = TRUE, sheet = NULL, ...) {
if (any(vapply(x[c("row_ref", "row_abs", "col_ref", "col_abs")],
is.na, logical(1)))) return(NA_character_)
fo <- match.arg(fo)
sheet <- sheet %||% !is.na(x$sheet)
if (fo == "A1") {
if (!isTRUE(x$row_abs) || !isTRUE(x$col_abs)) {
warning("Only absolute references can be converted to an A1 formatted ",
"string ... NAs generated", call. = FALSE)
return(NA_character_)
}
if (!strict) {
x <- relativize(x)
}
ref_string <-
paste0(rel_abs_format(x$col_abs, fo = "A1"), num_to_letter(x$col_ref),
rel_abs_format(x$row_abs, fo = "A1"), x$row_ref)
} else {
ref_string <- paste0("R", rel_abs_format(x$row_abs, x$row_ref),
"C", rel_abs_format(x$col_abs, x$col_ref))
}
if (sheet) {
ref_string <- paste(add_single_quotes(x$sheet), ref_string, sep = "!")
}
## no support to put file name in the string ... wait til I see it needed IRL
ref_string
}
#' @rdname to_string
#' @examples
#' ## a list of ra_ref's --> character vector
#' ra_ref_list <-
#' list(ra_ref(), ra_ref(2, TRUE, 5, TRUE), ra_ref(2, FALSE, 5, TRUE))
#' to_string_v(ra_ref_list)
#'
#' @export
to_string_v.list <- function(x, fo = c("R1C1", "A1"),
strict = TRUE, sheet = NULL, ...) {
stopifnot(all(vapply(x, inherits, logical(1), what = "ra_ref")))
vapply(x, to_string, character(1), fo = fo, strict = strict, sheet = sheet)
}
#' @rdname to_string
#' @examples
#' ## cell_addr --> string
#' (ca <- cell_addr(3, 8))
#' to_string(ca)
#' to_string(ca, fo = "A1")
#'
#' (ca <- cell_addr(1:4, 3))
#' to_string(ca)
#' to_string(ca, fo = "A1")
#' @export
to_string.cell_addr <- function(x, fo = c("R1C1", "A1"),
strict = TRUE, sheet = FALSE, ...) {
fo <- match.arg(fo)
ra_ref_list <- mapply(ra_ref, row_ref = addr_row(x), col_ref = addr_col(x),
SIMPLIFY = FALSE)
vapply(ra_ref_list, to_string, character(1), fo = fo,
strict = strict, sheet = sheet)
}
#' @rdname to_string
#' @examples
#' ## explicitly go from cell_addr, length > 1 --> character vector
#' (ca <- cell_addr(1:4, 3))
#' to_string_v(ca)
#' to_string_v(ca, fo = "A1")
#' @export
to_string_v.cell_addr <- to_string.cell_addr
cellranger/R/letter-to-from-num.R 0000644 0001762 0000144 00000003472 12713652746 016414 0 ustar ligges users #' Convert between letter and integer representations of column IDs
#'
#' Convert "A1"-style column IDs from a letter representation to an integer,
#' e.g. column A becomes 1, column D becomes 4, etc. Or go the other way around.
#'
#' \itemize{
#' \item Google Sheets have up to 300 columns (column KN).
#' \item Excel 2010 spreadsheets have up to 16,384 columns (column XFD).
#' \item ZZ is column 702.
#' \item ZZZ is column 18,278 (no known spreadsheet actually goes that high).
#' }
#' @name letter-num-conversion
#'
#' @param x a character vector of "A1" style column IDs (case insensitive)
#' @param y a vector of integer column IDs
#' @return a vector of column IDs, either character or integer
NULL
#' @rdname letter-num-conversion
#' @examples
#' letter_to_num('Z')
#' letter_to_num(c('AA', 'ZZ', 'ABD', 'ZZZ'))
#' letter_to_num(c(NA, ''))
#' @export
letter_to_num <- function(x) {
stopifnot(is.character(x))
x <- strsplit(toupper(x), '')
x <- lapply(x, char0_to_NA)
x <- lapply(x, match, table = LETTERS)
x <- lapply(x, function(z) sum((26 ^ rev(seq_along(z) - 1)) * z))
as.integer(x)
}
#' @rdname letter-num-conversion
#' @examples
#' num_to_letter(28)
#' num_to_letter(900)
#' num_to_letter(18278)
#' num_to_letter(c(25, 52, 900, 18278))
#' num_to_letter(c(NA, 0, 4.8, -4))
#' @export
num_to_letter <- function(y) {
stopifnot(is.numeric(y))
# fcn to express column number in this weird form of base 26
jfun <- function(div) {
if (is.na(div)) {
return(NA_character_)
}
ret <- integer()
while (div > 0) {
remainder <- ((div - 1) %% 26) + 1
ret <- c(remainder, ret)
div <- (div - remainder) %/% 26
}
paste(LETTERS[ret], collapse = "")
}
ret <- vapply(y, jfun, character(1))
## 0 becomes "", so we set that to NA here
ifelse(ret == "", NA_character_, ret)
}
cellranger/R/A1-R1C1-regex-utils.R 0000644 0001762 0000144 00000006221 12721412735 016074 0 ustar ligges users .cr <- new.env(parent = emptyenv())
## for validating single cell references
.cr$A1_rx <- "^\\$?[A-Za-z]{1,3}\\$?[0-9]{1,5}$"
.cr$R1C1_rx <- "^R\\[?\\-?[0-9]*\\]?C\\[?\\-?[0-9]*\\]?$"
#' Test cell reference strings
#'
#' Test cell reference strings for a specific format.
#'
#' @param x character vector of cell reference strings
#'
#' @return a logical vector
#' @name is_A1
#' @examples
#' is_A1("A1")
#' is_R1C1("A1")
#' is_R1C1("R4C12")
#'
#' x <- c("A1", "$A4", "$b$12", "RC1", "R[-4]C9", "R5C3")
#' data.frame(x, is_A1(x), is_R1C1(x))
NULL
#' @describeIn is_A1 A1 format, case insenstive; relative, absolute, or mixed
#' @export
is_A1 <- function(x) grepl(.cr$A1_rx, x)
#' @describeIn is_A1 R1C1 format; relative, absolute, or mixed
#' @export
is_R1C1 <- function(x) grepl(.cr$R1C1_rx, x)
#' Guess cell reference string format
#'
#' Guess if cell references are in R1C1 or A1 format.
#'
#' @param x character vector of cell reference strings
#' @param fo default to assume if format is ambiguous
#'
#' @return character vector consisting of \code{R1C1}, \code{A1}, or \code{NA}
#' @export
#'
#' @examples
#' A1 <- c("A1", "$A1", "A$1", "$A$1", "a1")
#' guess_fo(A1)
#' R1C1 <- c("R1C1", "R1C[-1]", "R[-1]C1", "R[-1]C[9]")
#' guess_fo(R1C1)
#'
#' guess_fo("RC2")
#' guess_fo("12")
#' guess_fo(12)
guess_fo <- function(x, fo = c("R1C1", "A1")) {
fo <- match.arg(fo)
is_R1C1 <- is_R1C1(x)
is_A1 <- is_A1(x)
out <- ifelse(is_R1C1, "R1C1", ifelse(is_A1, "A1", NA_character_))
both <- is_R1C1 & is_A1
neither <- is.na(out)
if (any(both)) {
out[both] <- fo
## OMFG this can actually happen. Example: RCx
warning("Not clear if cell reference is in A1 or R1C1 format. Example:\n",
x[both][1], "\nDefaulting to ", fo,
call. = FALSE)
}
if (any(neither)) {
warning("Cell reference follows neither the A1 nor R1C1 format. Example:\n",
x[neither][1], "\nNAs generated.",
call. = FALSE)
}
out
}
## for parsing single cell references
.cr$A1_ncg_rx <-
paste0("(?P\\$?)(?P[A-Za-z]{1,3})",
"(?P\\$?)(?P[0-9]+)")
.cr$R1C1_ncg_rx <-
paste0("^R(?P\\[?)(?P[0-9\\-]*)(?:\\]?)",
"C(?P\\[?)(?P[0-9\\-]*)(?:\\]?)$")
## for parsing cell (area) references that are possibly qualified by
## file and/or worksheet name
.cr$filename_rx = "(?:^\\[([^\\]]+)\\])?"
.cr$worksheetname_rx <- "(?:'?([^']+)'?!)?"
.cr$ref_rx <- "([a-zA-Z0-9:\\-$\\[\\]]+)"
.cr$string_rx <- sprintf("^(?:%s%s%s|(.*))$", .cr$filename_rx,
.cr$worksheetname_rx, .cr$ref_rx)
parse_ref_string <- function(x, fo = NULL) {
parsed <- as.list(rematch::re_match(.cr$string_rx, x)[1, , drop = TRUE])
names(parsed) <- c("input", "file", "sheet", "ref", "invalid")
parsed$ref_v <- unlist(strsplit(parsed$ref, ":"))
stopifnot(length(parsed$ref_v) %in% 1:2)
if (is.null(fo)) {
fo_v <- guess_fo(parsed$ref_v)
parsed$fo <- unique(fo_v)
if (length(parsed$fo) > 1) {
stop("Cell references aren't uniformly A1 or R1C1 format:\n",
parsed$ref, call. = FALSE)
}
} else {
parsed$fo <- match.arg(fo, c("R1C1", "A1"))
}
parsed
}
cellranger/R/cell-addr.R 0000644 0001762 0000144 00000012336 12721413533 014551 0 ustar ligges users #' cell_addr class
#'
#' The \code{cell_addr} class is used to hold the absolute row and column
#' location for one or more cells. An object of class \code{cell_addr} is a list
#' with two components of equal length, named \code{row} and \code{col},
#' consisting of integers greater than or equal to one or \code{NA}. This is in
#' contrast to the \code{\link{ra_ref}} class, which holds a representation of a
#' single absolute, relative, or mixed cell reference from, e.g., a formula.
#'
#' @param row integer. Must be the same length as \code{col} or of length one,
#' which will be recycled to the length of \code{col}.
#' @param col integer. Same deal as for \code{row}.
#'
#' @return a \code{cell_addr} object
#' @export
#'
#' @template reference-sestoft
#'
#' @examples
#' cell_addr(4, 3)
#' (ca <- cell_addr(1:4, 3))
#' ca[2:3]
#' ca[[4]]
#' length(ca)
cell_addr <- function(row, col) {
## this way we don't have to require NA_integer_ which is annoying
## integer conversion was going to happen anyway
row <- as.integer(row)
col <- as.integer(col)
stopifnot(length(row) > 0, length(col) > 0)
if (length(row) > 1 && length(col) > 1) {
stopifnot(length(row) == length(col))
} else {
n <- max(length(row), length(col))
row <- rep_len(row, n)
col <- rep_len(col, n)
}
neg <- isTRUE_v(row < 1) | isTRUE_v(col < 1)
if (any(neg)) {
## data.frame > tibble here because want original row names (number, here)
out <- data.frame(row, col)[neg, ,drop = FALSE]
printed_x <- utils::capture.output(print(out))
stop("cell_addr objects require absolute row and column, must be >= 1:\n",
paste(printed_x, collapse = "\n"), call. = FALSE)
}
structure(list(row = row, col = col), class = "cell_addr")
}
#' @export
print.cell_addr <- function(x, ..., n = NULL) {
cat("\n")
print(tibble::trunc_mat(tibble::as_data_frame(unclass(x)), n = n))
cat("\n")
invisible(x)
}
#' @export
`[.cell_addr` <-
function(x, i) cell_addr(row = addr_row(x)[i], col = addr_col(x)[i])
#' @export
`[[.cell_addr` <-
function(x, i) cell_addr(row = addr_row(x)[[i]], col = addr_col(x)[[i]])
#' @export
length.cell_addr <- function(x) length(addr_row(x))
#' Get row from cell location or reference
#'
#' @param x a suitable representation of cell(s) or a cell area reference
#' @template param-ddd
#'
#' @return integer vector
#' @export
addr_row <- function(x, ...) UseMethod("addr_row")
#' Get column from cell location or reference
#'
#' @param x a suitable representation of cell(s) or a cell area reference
#' @template param-ddd
#'
#' @return integer vector
#' @export
addr_col <- function(x, ...) UseMethod("addr_col")
#' @describeIn addr_row Method for \code{\link{cell_addr}} objects
#' (ca <- cell_addr(1:4, 3))
#' addr_row(ca)
#' @export
addr_row.cell_addr <- function(x, ...) x$row
#' @describeIn addr_col Method for \code{\link{cell_addr}} objects
#' (ca <- cell_addr(1:4, 3))
#' addr_col(ca)
#' @export
addr_col.cell_addr <- function(x, ...) x$col
#' Convert to a cell_addr object
#'
#' Convert various representations of a cell reference into an object of class
#' \code{\link{cell_addr}}. Recall that \code{\link{cell_addr}} objects hold
#' absolute row and column location, so \code{\link{ra_ref}} objects or cell
#' reference strings with relative or mixed references will raise a warning and
#' generate \code{NA}s.
#'
#' @param x a cell reference
#' @template param-ddd
#'
#' @return a \code{\link{cell_addr}} object
#' @name as.cell_addr
NULL
#' @rdname as.cell_addr
#' @export
as.cell_addr <- function(x, ...) UseMethod("as.cell_addr")
#' @rdname as.cell_addr
#' @export
as.cell_addr_v <- function(x, ...) UseMethod("as.cell_addr_v")
#' @rdname as.cell_addr
#' @export
#' @examples
#' as.cell_addr(ra_ref())
#' rar <- ra_ref(2, TRUE, 5, TRUE)
#' as.cell_addr(rar)
#' ## mixed reference
#' rar <- ra_ref(2, FALSE, 5, TRUE)
#' as.cell_addr(rar)
as.cell_addr.ra_ref <- function(x, ...) {
if (!isTRUE(x$row_abs) || !isTRUE(x$col_abs)) {
warning("Non-absolute references found ... NAs generated", call. = FALSE)
if (!isTRUE(x$row_abs)) {
x$row_ref <- NA
}
if (!isTRUE(x$col_abs)) {
x$col_ref <- NA
}
}
cell_addr(row = x$row_ref, col = x$col_ref)
}
#' @rdname as.cell_addr
#' @examples
#' ra_ref_list <-
#' list(ra_ref(), ra_ref(2, TRUE, 5, TRUE), ra_ref(2, FALSE, 5, TRUE))
#' as.cell_addr_v(ra_ref_list)
#' @export
as.cell_addr_v.list <- function(x, ...) {
stopifnot(all(vapply(x, inherits, logical(1), what = "ra_ref")))
ca_list <- lapply(x, as.cell_addr)
cell_addr(row = vapply(ca_list, addr_row, integer(1)),
col = vapply(ca_list, addr_col, integer(1)))
}
#' @rdname as.cell_addr
#' @template param-fo
#' @template param-strict
#' @export
#' @examples
#' as.cell_addr("$D$12")
#' as.cell_addr("R4C3")
#' as.cell_addr(c("$C$4", "$D$12"))
#' as.cell_addr("$F2")
#' as.cell_addr("R[-4]C3")
#' as.cell_addr("F2", strict = FALSE)
as.cell_addr.character <- function(x, fo = NULL, strict = TRUE, ...) {
suppressWarnings(
## one warning is enough -- let as.cell_addr take care of it in next step
ra_ref_list <- as.ra_ref_v(x, fo = fo, strict = strict)
)
as.cell_addr_v(ra_ref_list)
}
#' @rdname as.cell_addr
#' @export
as.cell_addr_v.character <- as.cell_addr.character
cellranger/R/cellranger-package.r 0000644 0001762 0000144 00000000246 12515277634 016501 0 ustar ligges users #' cellranger
#'
#' Helper functions to work with spreadsheets and the "A1:D10" style of cell
#' range specification.
#'
#' @name cellranger
#' @docType package
NULL
cellranger/R/cell-limits.R 0000644 0001762 0000144 00000013017 12716427703 015145 0 ustar ligges users #' Create a cell_limits object
#'
#' A \code{cell_limits} object is a list with three components:
#'
#' \itemize{
#' \item \code{ul} vector specifying upper left cell of target rectangle, of
#' the form \code{c(ROW_MIN, COL_MIN)}
#' \item \code{lr} vector specifying lower right cell of target rectangle, of
#' the form \code{c(ROW_MAX, COL_MAX)}
#' \item \code{sheet} string specifying worksheet name, which may be
#' \code{NA}, meaning it's unspecified
#' }
#'
#' A value of \code{NA} in \code{ul} or \code{lr} means the corresponding limit
#' is left unspecified. Therefore a verbose way to specify no limits at all
#' would be \code{cell_limits(c(NA, NA), c(NA, NA))}. If the maximum row or
#' column is specified but the associated minimum is not, then the minimum is
#' set to 1.
#'
#' When specified via character, cell references can be given in A1 or R1C1
#' notation and must be interpretable as absolute references. For A1, this means
#' either both row and column are annotated with a dollar sign \code{$} or
#' neither is. So, no mixed references, like \code{B$4}. For R1C1, this means no
#' square brackets, like \code{R[-3]C[3]}.
#'
#' @param ul vector identifying upper left cell of target rectangle
#' @param lr vector identifying lower right cell of target rectangle
#' @param sheet string containing worksheet name, optional
#' @param x input to convert into a \code{cell_limits} object
#'
#' @return a \code{cell_limits} object
#'
#' @examples
#' cell_limits(c(1, 3), c(1, 5))
#' cell_limits(c(NA, 7), c(3, NA))
#' cell_limits(c(NA, 7))
#' cell_limits(lr = c(3, 7))
#'
#' cell_limits(c(1, 3), c(1, 5), "Sheet1")
#' cell_limits(c(1, 3), c(1, 5), "Spaces are evil")
#'
#' dim(as.cell_limits("A1:F10"))
#'
#' @export
cell_limits <- function(ul = c(NA_integer_, NA_integer_),
lr = c(NA_integer_, NA_integer_),
sheet = NA_character_) {
stopifnot(length(ul) == 2L, length(lr) == 2L,
length(sheet) == 1L, is.character(sheet))
ul <- as.integer(ul)
lr <- as.integer(lr)
NA_or_pos <- function(x) is.na(x) | x > 0
stopifnot(all(NA_or_pos(ul)))
stopifnot(all(NA_or_pos(lr)))
if (is.na(ul[1]) && !is.na(lr[1])) ul[1] <- 1L
if (is.na(ul[2]) && !is.na(lr[2])) ul[2] <- 1L
rows <- c(ul[1], lr[1])
cols <- c(ul[2], lr[2])
if (!anyNA(rows)) stopifnot(rows[1] <= rows[2])
if (!anyNA(cols)) stopifnot(cols[1] <= cols[2])
structure(list(ul = ul, lr = lr, sheet = sheet),
class = c("cell_limits", "list"))
}
#' @export
print.cell_limits <- function(x, ...) {
ul <- ifelse(is.na(x$ul), "-", as.character(x$ul))
lr <- ifelse(is.na(x$lr), "-", as.character(x$lr))
sheet <- if (is.na(x$sheet)) "" else paste0(" in '", x$sheet, "'")
cat("\n",
sep = "")
}
#' @rdname cell_limits
#' @export
dim.cell_limits <- function(x) c(x$lr[1] - x$ul[1], x$lr[2] - x$ul[2]) + 1
#' @rdname cell_limits
#' @template param-ddd
#' @export
as.cell_limits <- function(x, ...) UseMethod("as.cell_limits")
#' @rdname cell_limits
#' @export
as.cell_limits.cell_limits <- function(x, ...) x
#' @rdname cell_limits
#' @export
as.cell_limits.NULL <- function(x, ...) cell_limits()
#' @rdname cell_limits
#' @template param-fo
#' @examples
#' as.cell_limits("A1")
#' as.cell_limits("$Q$24")
#' as.cell_limits("A1:D8")
#' as.cell_limits("R5C11")
#' as.cell_limits("R2C3:R6C9")
#' as.cell_limits("Sheet1!R2C3:R6C9")
#' as.cell_limits("'Spaces are evil'!R2C3:R6C9")
#'
#' \dontrun{
#' ## explicitly mixed A1 references won't work
#' as.cell_limits("A$2")
#' ## mixed or relative R1C1 references won't work
#' as.cell_limits("RC[4]")
#' }
#' @export
as.cell_limits.character <- function(x, fo = NULL, ...) {
stopifnot(length(x) == 1L)
parsed <- parse_ref_string(x, fo = fo)
if (is.na(parsed$fo)) {
stop("Can't guess format of this cell reference:\n", parsed$ref,
call. = FALSE)
}
## parsed$ref_v has length 1 or 2, depending on whether input was a range
if (parsed$fo == "A1") {
rar_list <- A1_to_ra_ref(parsed$ref_v, strict = FALSE)
} else {
rar_list <- R1C1_to_ra_ref(parsed$ref_v)
}
not_abs <- vapply(rar_list, is_not_abs_ref, logical(1))
if (any(not_abs)) {
stop("Mixed or relative cell references aren't allowed:\n",
parsed$ref, call. = FALSE)
}
## if single cell input --> duplicate that thing!
rar_list <- rep_len(rar_list, 2)
cell_limits(
ul = rar_list[[1]][c("row_ref", "col_ref")],
lr = rar_list[[2]][c("row_ref", "col_ref")],
sheet = if (parsed$sheet == '') NA_character_ else parsed$sheet
)
}
#' Convert a cell_limits object to a cell range
#'
#' @param x a cell_limits object
#' @template param-fo
#' @template param-strict
#' @template param-sheet
#'
#' @return length one character vector holding a cell range
#'
#' @examples
#' rgCL <- cell_limits(ul = c(1, 2), lr = c(7, 6))
#' as.range(rgCL)
#' as.range(rgCL, fo = "A1")
#'
#' rgCL_ws <- cell_limits(ul = c(1, 2), lr = c(7, 6), sheet = "A Sheet")
#' as.range(rgCL_ws)
#' as.range(rgCL_ws, fo = "A1")
#' @export
as.range <- function(x, fo = c("R1C1", "A1"), strict = FALSE, sheet = NULL) {
stopifnot(inherits(x, "cell_limits"), isTOGGLE(strict), isTOGGLE(sheet))
fo <- match.arg(fo)
if (anyNA(unlist(x[c("ul", "lr")]))) return(NA_character_)
ca <- cell_addr(c(x$ul[1], x$lr[1]), c(x$ul[2], x$lr[2]))
range <- paste(to_string(ca, fo = fo, strict = strict), collapse = ":")
sheet <- sheet %||% !is.na(x$sheet)
if (sheet) {
range <- paste(add_single_quotes(x$sheet), range, sep = "!")
}
range
}
cellranger/R/cell-rows-cell-cols.R 0000644 0001762 0000144 00000004517 12541112271 016501 0 ustar ligges users #' Specify cell limits only for rows
#'
#' How does this differ from \code{\link{cell_limits}}? Here the input can have
#' length greater than 2, i.e. the rows can be specified as \code{1:n}. If the
#' length is greater than 2, both the min and max are taken with \code{NA.rm =
#' TRUE}. Note it is not possible to request non-contiguous rows, i.e. rows 1,
#' 2, and 5. In this case, the requested rows will run from the minimum of 1 to
#' the maximum of 5.
#'
#' @param x numeric vector of row limits; if length greater than two, min and
#' max will be taken with \code{NA.rm = TRUE}
#'
#' @return a \code{\link{cell_limits}} object
#'
#' @examples
#' cell_rows(c(NA, 3))
#' cell_rows(c(7, NA))
#' cell_rows(4:16)
#' cell_rows(c(3, NA, 10))
#'
#' dim(cell_rows(1:5))
#'
#' @export
cell_rows <- function(x) {
if(all(is.na(x))) {
return(cell_limits())
}
stopifnot(is.numeric(x))
if (length(x) != 2L) {
x <- range(x, na.rm = TRUE)
}
cell_limits(as.integer(c(x[1], NA)), as.integer(c(x[2], NA)))
}
#' Specify cell limits only for columns
#'
#' How does this differ from \code{\link{cell_limits}}? Two ways. First, the
#' input can have length greater than 2, i.e. the columns can be specified as
#' \code{1:n}. If the length is greater than 2, both the min and max are taken
#' with \code{NA.rm = TRUE}. Note it is not possible to request non-contiguous
#' columns, i.e. columns 1, 2, and 5. In this case, the requested columns will
#' run from the minimum of 1 to the maximum of 5. Second, the input can be given
#' in the letter-based format spreadsheets use to label columns.
#'
#' @param x vector of column limits; if character, converted to numeric; if
#' length greater than two, min and max will be taken with \code{NA.rm = TRUE}
#'
#' @return a \code{\link{cell_limits}} object
#'
#' @examples
#' cell_cols(c(NA, 3))
#' cell_cols(c(7, NA))
#' cell_cols(4:16)
#' cell_cols(c(3, NA, 10))
#'
#' cell_cols("C:G")
#' cell_cols(c("B", NA))
#' cell_cols(LETTERS)
#'
#' @export
cell_cols <- function(x) {
if(all(is.na(x))) {
return(cell_limits())
}
stopifnot(is.numeric(x) || is.character(x))
if(is.character(x)) {
if(length(x) == 1L) {
x <- strsplit(x, ":")[[1]]
}
x <- letter_to_num(x)
}
if (length(x) != 2L) {
x <- range(x, na.rm = TRUE)
}
cell_limits(as.integer(c(NA, x[1])), as.integer(c(NA, x[2])))
}
cellranger/vignettes/ 0000755 0001762 0000144 00000000000 12745604030 014401 5 ustar ligges users cellranger/vignettes/cell-references.Rmd 0000644 0001762 0000144 00000004261 12712114434 020104 0 ustar ligges users ---
title: "Classes and methods to deal with cell references"
author: "Jenny Bryan"
date: "`r Sys.Date()`"
output:
rmarkdown::html_vignette:
toc: true
toc_depth: 4
keep_md: true
vignette: >
%\VignetteIndexEntry{Classes and methods to deal with cell references}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
Following Testoft's book Spreadsheet Implementation Technology: Basics and Extensions.
The main class is `ra_ref` which holds a single **r**elative, **a**bsolute, or mixed cell **ref**erence. Two logical indicators, `rowAbs` and `colAbs`, which report whether the row (column) reference is absolute. Also integers `rowRef` and `colRef`, which either hold absolute row and column or, for a relative reference, an offset.
Two other very convenient, but less general forms for holding cell references:
* as a string
- in A1 format: e.g. `B4`, `B$4`, `$B4`, `$B$4` (let's assume found in cell `D5`, shall we?)
- in R1C1 format: e.g. `R[1]C[-2]`, `R4C[-2]`, `R[1]C2`, `R4C2`
* as an absolute row and colum address
`to_string.ra_ref()` converts a single `ra_ref` to character.
`as.ra_ref.character()` converts a single cell reference in string form to a `ra_ref` object.
Note there can be problems converting to/from character, specifically A1 formatted strings, because we don't know the host cell. A relative row or column reference cannot be resolved without knowing the host cell. So this is a source of warnings and `NA`, going both directions.
The `cell_addr` class is for absolute cell addresses. It's a list with two synchronized, equal length integer vectors, `row` and `col`. It could be a data frame or matrix (and mabye it should be?), but it's not. Methods `[`, `[[`, and `length` exist. Note that a single `cell_addr` object could hold many absolute references.
`to_string.cell_addr` converts a `cell_addr` object to character, in a vectorized way. The format `fo` is an argument. Under the hood, this actually converts each individual cell address into an `ra_ref` object, then calls `to_string` on it, and returns them as character vector.
`as.ra_ref.cell_addr` converts a `cell_addr` object to a `ra_ref` object and is NOT vectorized.
WIP!
cellranger/vignettes/img/ 0000755 0001762 0000144 00000000000 12712114434 015153 5 ustar ligges users cellranger/vignettes/img/cellranger-classes01.png 0000644 0001762 0000144 00000070226 12712114434 021602 0 ustar ligges users PNG
IHDR pHYs zTXtmxGraphModel MUV,YL5MDӋEJ6Ι\wu߿K>3eޯ7yCm8붍o?
8uX~;`
a/!6UKr5pnpC|w"YTۿ*$?/~0ax%ZcGPC7긜?)` ^jRrW憰xt
wXxʚ00lePeA;EIn>ۡ,hHI2Rߠo`-bfJ('7SgC:ؼC#F5#+a W!IЀ8{m[GOS6jR$ֽfu
]\A!W:Ի,x]i0|Ha8鬺d'h8g7֖2;;:i4|R>F6UV6tk;Qq1veժP9:kvc1(^`3쇁cVwo<l-鹭:ʋʗت~#ri/@#XS
~#m\RF6J(u-fjސ
bB8Ƞȅ ~zR-`X{!3*Ĭ)1*ۋ|
_d9'::Ť293=ٸy&cb~ޓL[# (Zxd|Fb+~A]R=ylȦ5W2}f5ˏpXKƪ7!:d _cb@7R|jỌ)Np#{`0UFJiJ%! \>yRKu:̎
/sTA^:Cq~B39e>w+y5pO@_0a'm[3NC ,GWgdDTUNC6o?7
[*(
ru+3ѐSf^f'^S;K_^##Lϋ'Nf#KfGMp#=۸X
jPo^MuXH_+,&KcO$'QΘsJPg\<D#NW8`eNa%n"aw0~eLFZz*` ${xs01+R09\z#r0_*`5!vyzfF$7*v&q6"yc~)1Aj58R[O sO|ؠ8?tD,b ϸ)v\WK\2X`rfLeNG_j>=A m{Dxc~SPٲhReQugPݖ?
ȿ\ IDATx{\Ul1-t u,
D+``nbeBv7nmi~Tkaa!
%l
h\ؔIalA."A
3y0s>stJ)Bt_
-B ǜvB%)]<-BqBtf#zn
R
!NFřϋ}B?LE>IA!D
$LE>IA!DH8yObPueG'I1(:gB>/BIR
!Nv:;zz!Zϋ}OzB~y5##>ݠe%ekMo7F'>+Σ!D322(躲MpY/ِWEBN+z5"~t[#pA!YPFhvC$d.NFA\BNN?l[;:(!9*etPs=!!)8:2\5%\4["hFA!hXMfMJ.L#$=bsetX=.Ki&3#ҹuP!Ʃ80-kQ.1ݘ
=Sn0`c2GyP/Brs8jv,^+!;>W\V)PF Td$LؘګE0o3Bd]уBvy3=HAͶ%[8s1z lpwL2[CGap[!Š4BtBE7%Ȏ;`0Q@mm=եdr_^Ö!DǤB8Oz`cи+ydQ1Nlz s.sBΒbP!`ug-"\5@︙x@y3R
:8ރK}I#INKlZ9ilBeZXыtJ)_5Juت1xx zpa G:0!<%ϧB|O
$BY^z;|[wM8M>~߁,=LA!8^A,ZB.kB!cR
!BǤB!85BNQ!
!BǤB!84{"tR!$B!8I1(BqbP!<&ŠB!yLA!haV!DbPqce?:)KN{ÖG _P
%n=M
R2Bs@6%Lpϐ8FOh`AWS\rNӴs"c#+:H)MFr,9{{KʲTt:R
j([F{T?*`#5* mN&$d; v
#q /I
%44fޯ
@ĆĦPX3iZ%gR)sI\zD}!pEʲ].ۊQBⵑ DKa`/c0He+oWw)ƦP pP2Οk !D)!*3'ST~m{[ֿ= cQ*! BիHq9J)\ǘJ5)#(u2I^֪JkQr*R9 *8!CYrU|0
CP٦1*+X5ZfD,l1I-nUʚmRʔ9!NX*+)R*AZ"ʒzF_Tq+)+_Yr
PF9+n8eOEH!Dunw6qYJ)
cj*osTZQȎSUJpCŪ4MA*jppWLRJ)..K3JT}II՟S2$Y.JMj*`eWUejlU$"&I
Z-Q٨0T)ߐbP>I(H qMI澙#$5L=?f8PCqn7M2;j5d
(Âm|DYqXζmB䯘`HȦJ*+KN2BJ2瞆*/u]b8Ϡ%ÇMp 4bzxqd4Q[E+9{^iM7Dw)L!Ύ@"Ą{^"c;]EZ˄Q:xw5n10$C9KlnLGTPy'/)Ĉ;g1/}
k0xGh><0=|:68v@vfu?x)P^{u1ElplvkW!JFNͤ͡" "4|IYԱfI"Uy 8TL^c$r:`̵Ӡj#[>>Ȝ˜y+8(;[%ډ%i(gn 1RK|@^O0=v~c12,am4וD@{sOMɼIFjtMSypiB,'}SG`2 q>!&HMSF>DX\m;/ڻ`3d'2oz I,zᚾ68ILKjqm6Cawq`;1hImrp8D{ t[/?}":BI-&>~hI|ՅHZΛEffOy1f}/u!8;r͠B"TXsɧO =E,!yDF;zB
!BǤB!8I1(BqbP!<&ŠB!yLA!BB!1)B!cR
!BǤB!8I1(BqbP!9 D!ŠBs?@xx8?O
eٲev賤BJVEӑRPsI{!uO||A?ĭ]:0.B{ӟu]_LDD^^^p
deevSBllX u8U