5

Is there an R function for parsing INI like configuration files?

While searching I only found this discussion.

Community
  • 1
  • 1
Karsten W.
  • 17,826
  • 11
  • 69
  • 103
  • Can you rephrase your question? SO is not suited for searching tools. – Roman Luštrik Jan 11 '14 at 16:29
  • 2
    You didnt look very hard: http://r.789695.n4.nabble.com/Read-Windows-like-INI-files-into-R-data-structure-td827353.html – Spacedman Jan 11 '14 at 16:38
  • Thanks @Spacedman for pointing to the r-help discussion. I did not use nabble for a long time and searched only SO. – Karsten W. Jan 11 '14 at 16:48
  • @KarstenW., I think it would be acceptable to copy the r-help answer here as an answer (with attribution). – Ben Bolker Jan 11 '14 at 17:23
  • There are proper libraries for these things, and it might be worth building a package against. Or use YAML, JSON, ... – Dirk Eddelbuettel Jan 11 '14 at 17:34
  • Note Dirk is likely talking about C (and C++) libraries here, and integrating them with R via something like Rcpp. Might be a nice exercise for a newbie wanting to do R/C++ integration. – Spacedman Jan 11 '14 at 20:02
  • @KarstenW. next time try searching google - it was the first hit for " R ini file" – Spacedman Jan 11 '14 at 20:02
  • As @Spacedman and dirkeddelbuettel says, create a package that wraps a C++ library. See http://stackoverflow.com/questions/6175502/how-to-parse-ini-file-with-boost for inspiration – Andrie Aug 12 '15 at 13:01

1 Answers1

2

Here is an answer that was given to exact the same question on r-help in 2007 (thanks to @Spacedman for pointing this out):

Parse.INI <- function(INI.filename) 
{ 
  connection <- file(INI.filename) 
  Lines  <- readLines(connection) 
  close(connection) 

  Lines <- chartr("[]", "==", Lines)  # change section headers 

  connection <- textConnection(Lines) 
  d <- read.table(connection, as.is = TRUE, sep = "=", fill = TRUE) 
  close(connection) 

  L <- d$V1 == ""                    # location of section breaks 
  d <- subset(transform(d, V3 = V2[which(L)[cumsum(L)]])[1:3], 
                           V1 != "") 

  ToParse  <- paste("INI.list$", d$V3, "$",  d$V1, " <- '", 
                    d$V2, "'", sep="") 

  INI.list <- list() 
  eval(parse(text=ToParse)) 

  return(INI.list) 
} 

Actually, I wrote a short and presumably buggy function (i.e. not covering all corner cases) which works for me now:

read.ini <- function(x) {
    if(length(x)==1 && !any(grepl("\\n", x))) lines <- readLines(x) else lines <- x
    lines <- strsplit(lines, "\n", fixed=TRUE)[[1]]
    lines <- lines[!grepl("^;", lines) & nchar(lines) >= 2]  # strip comments & blank lines
    lines <- gsub("\\r$", "", lines)
    idx <- which(grepl("^\\[.+\\]$", lines))
    if(idx[[1]] != 1) stop("invalid INI file. Must start with a section.")

    res <- list()
    fun <- function(from, to) {
        tups <- strsplit(lines[(from+1):(to-1)], "[ ]*=[ ]*")
        for (i in 1:length(tups)) 
            if(length(tups[[i]])>2) tups[[i]] <- c(tups[[i]][[1]], gsub("\\=", "=", paste(tail(tups[[i]],-1), collapse="=")))
        tups <- unlist(tups)
        keys <- strcap(tups[seq(from=1, by=2, length.out=length(tups)/2)])
        vals <- tups[seq(from=2, by=2, length.out=length(tups)/2)]
        sec <- strcap(substring(lines[[from]], 2, nchar(lines[[from]])-1))
        res[[sec]] <<- setNames(vals, keys)
    }
    mapply(fun, idx, c(tail(idx, -1), length(lines)+1))
    return(res)
}

where strcap is a helper function that capitalizes a string:

strcap <- function(s) paste(toupper(substr(s,1,1)), tolower(substring(s,2)), sep="")

There are also some C solutions for this, like inih or libini that might be useful. I did not try them out, though.

Karsten W.
  • 17,826
  • 11
  • 69
  • 103
  • I would advise you to use the `return` argument of the inner `fun` function in stead of using global assignment. Right now you code is a bit of a hybrid between a `for` loop and an `apply` loop. – Paul Hiemstra Jan 11 '14 at 19:53
  • When using `return`, how can I set the name of the entry in the result? I mean how do I return the value of `sec` in the code above? – Karsten W. Jan 11 '14 at 20:14