##
## $Id: excelUtils.R 3386 2003-05-16 15:28:47Z duncan $
##
## TODO:
## row.names for importing/exporting data.frames
## dates/currency, general converters
## error checking
"exportDataFrame" <-
function(df, at, ...)
## export the data.frame "df" into the location "at" (top,left cell)
## output the occupying range.
## TODO: row.names, more error checking
{
nc <- dim(df)[2]
if(nc<1) stop("data.frame must have at least one column")
r1 <- at$Row() ## 1st row in range
c1 <- at$Column() ## 1st col in range
c2 <- c1 + nc - 1 ## last col (*not* num of col)
ws <- at[["Worksheet"]]
## headers
hdrRng <- ws$Range(ws$Cells(r1, c1), ws$Cells(r1, c2))
hdrRng[["Value"]] <- names(df)
## data
rng <- ws$Cells(r1+1, c1) ## top cell to put 1st column
for(j in seq(from = 1, to = nc)){
cat("Column", j, "\n")
exportVector(df[,j], at = rng, ...)
rng <- rng$Next() ## next cell to the right
}
invisible(ws$Range(ws$Cells(r1, c1), ws$Cells(r1 + nrow(df), c2)))
}
"exportVector" <-
function(obj, at = NULL, byrow = FALSE, ...)
## coerce obj to a simple (no attributes) vector and export to
## the range specified at "at" (can refer to a single starting cell);
## byrow = TRUE puts obj in one row, otherwise in one column.
## How should we deal with unequal of ranges and vectors? Currently
## we stop, modulo the special case when at refers to the starting cell.
## TODO: converters (currency, dates, etc.)
{
n <- length(obj)
if(n<1) return(at)
d <- c(at$Rows()$Count(), at$Columns()$Count())
N <- prod(d)
if(N==1 && n>1){ ## at refers to the starting cell
r1c1 <- c(at$Row(), at$Column())
r2c2 <- r1c1 + if(byrow) c(0,n-1) else c(n-1, 0)
ws <- at[["Worksheet"]]
at <- ws$Range(ws$Cells(r1c1[1], r1c1[2]),
ws$Cells(r2c2[1], r2c2[2]))
}
else if(n != N)
stop("range and length(obj) differ")
## currently we can only export primitives...
if(class(obj) %in% c("logical", "integer", "numeric", "character"))
obj <- as.vector(obj) ## clobber attributes
else
obj <- as.character(obj) ## give up -- coerce to chars
## here we create a C-level COM safearray
d <- if(byrow) c(1, n) else c(n,1)
objref <- .Call("R_create2DArray", matrix(obj, nrow=d[1], ncol=d[2]))
at[["Value"]] <- objref
invisible(at)
}
"importDataFrame" <-
function(rng = NULL, wks = NULL, n.guess = 5, dateFun = as.chron.excelDate)
## Create a data.frame from the range rng or from the "Used range" in
## the worksheet wks. The excel data is assumed to be a "database" (sic)
## excel of primitive type (and possibly time/dates).
## We guess at the type of each "column" by looking at the first
## n.guess entries ... but it is only a very rough guess.
{
if(is.null(rng) && is.null(wks))
stop("need to specify either a range or a worksheet")
if(is.null(rng))
rng <- wks$UsedRange() ## actual region
else
wks <- rng[["Worksheet"]] ## need to query rng for its class
n.areas <- rng$Areas()$Count() ## must have only one region
if(n.areas!=1)
stop("data must be in a contigious block of cells")
c1 <- rng$Column() ## first col
c2 <- rng$Columns()$Count() ## last col, provided contiguous region
r1 <- rng$Row() ## first row
r2 <- rng$Rows()$Count() ## last row, provided contiguous region
## headers
n.hdrs <- rng$ListHeaderRows()
if(n.hdrs==0)
hdr <- paste("V", seq(form=1, to=c2-c1+1), sep="")
else if(n.hdrs==1)
hdr <- unlist(rng$Rows()$Item(r1)$Value2())
else { ## collapse multi-row headers
h <- vector("list", c2-c1+1) ## list by column
r <- rng$Range(rng$Cells(r1,c1), rng$Cells(r1+n.hdrs-1, c2))
jj <- 1
for(j in seq(from=c1, to=c2)){
h[[jj]] <- unlist(r$Columns(j)$Value2()[[1]])
jj <- jj+1
}
hdr <- sapply(h, paste, collapse=".")
}
r1 <- r1 + n.hdrs
## Data region
d1 <- wks$Cells(r1, c1)
d2 <- wks$Cells(r2, c2)
dataCols <- wks$Range(d1, d2)$Columns()
out <- vector("list", length(hdr))
for(j in seq(along = out)){
f1 <- dataCols$Item(j)
f2 <- f1$Value2()[[1]]
f <- unlist(lapply(f2, function(x) if(is.null(x)) NA else x))
cls <- guessExcelColType(f1)
out[[j]] <- if(cls=="logical") as.logical(f) else f
}
names(out) <- make.names(hdr)
as.data.frame(out)
}
"guessExcelColType" <-
function(colRng, n.guess = 5, hint = NULL)
## colRng points to an range object corresponding to one excel column
## e.g., colRng = rng$Columns()$Item("H")
## TODO: currently we return one of "logical", "numeric", "character"
## need to add "SCOMIDispatch"
{
wf <- colRng[["Application"]][["WorksheetFunction"]]
S.avail <- c("logical", "numeric", "integer", "character")
## we should get the following from the Excel type library
fmt <- colRng[["NumberFormat"]]
num.fmt <- c("general", "number", "currency", "accounting",
"percentage", "fraction", "scientific")
fld <- colRng$Rows()
n <- fld$Count()
k <- min(n.guess, n)
cls <- character(k)
c1 <- colRng$Cells(1,1)
c2 <- colRng$Cells(k,1)
for(i in 1:k){
x <- fld$Item(i)
if(wf$IsText(x))
cls[i] <- "character"
else if(wf$IsNumber(x)) {
if(tolower(fmt) %in% num.fmt)
cls[i] <- "numeric"
else
cls[i] <- "character"
}
else if(wf$IsLogical(x))
cls[i] <- "logical"
else if(wf$IsNA(x))
cls[i] <- "NA"
else
cls[i] <- "character"
}
## if not all rows agree, use character type
cls <- cls[cls %in% S.avail]
if(length(cls)==0 || length(unique(cls))>1)
return("character")
else
return(cls[1])
}
"as.chron.excelDate" <-
function(xlsDate, date1904 = FALSE)
{
if(date1904){
orig <- c(month=12, day=31, year=1903)
off <- 0
}
else {
orig <- c(month=12, day=31, year=1899)
off <- 1
}
chron(xlsDate - off, origin = c(month=12, day = 31, year = 1899))
}
"as.excelData.chron" <-
function(chronDate, date1904 = FALSE)
{
if(date1904){
orig <- c(month=12, day=31, year=1903)
off <- 0
}
else {
orig <- c(month=12, day=31, year=1899)
off <- 1
}
if(any(origin(chronDate)!=orig))
origin(chronDate) <- orig
as.numeric(chronDate) + off
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.