R/rk-internal.R

Defines functions writeRequire replaceJSFor replaceJSIf uncurl replaceJSOperators check.JS.lines js.try.scan force.i18n check.i18n rk.register.options rk.check.options get.optionIDs get.rkh.prompter set.rk.env get.rk.env dependenciesCompatWrapper paste.JS.optionsset paste.JS.var paste.JS.options paste.JS.array paste.JS.ite clean.name check.type valid.parent valid.child modif.validity check.ID get.authors get.by.role XML2dependencies XML2person node.soup ID.prefix get.JS.vars camelCode check.optionset.tags get.IDs filter.relevant.tags get.single.tags checkCreateFiles rk.noquote indent trim.n trim child.list stripXML stripCont auto.ids generator.info

# Copyright 2010-2018 Meik Michalke <meik.michalke@hhu.de>
#
# This file is part of the R package rkwarddev.
#
# rkwarddev is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# rkwarddev is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with rkwarddev.  If not, see <http://www.gnu.org/licenses/>.

# collate voodoo
#' @include rk.comment.R
#' @import XiMpLe rkward

# set up an internal environment, e.g. for prompter settings or indentation
.rkdev.env <- new.env()
# another environment for use with rk.local()
.rk.local.env <- new.env()

# internal functions for the rk.* functions

## wrapper for paste0() needed?
if(isTRUE(R_system_version(getRversion()) < 2.15)){
  # if this is an older R version, we need a wrapper function for paste0()
  # which was introduced with R 2.15 as a more efficient shortcut to paste(..., sep="")
  paste0 <- function(..., collapse=NULL){
    return(paste(..., sep="", collapse=collapse))
  }
} else {}


## function generator.info()
# info message
generator.info <- function(script=NULL){
  genInfo <- paste0(
    "this code was generated using the rkwarddev package.\n",
    "perhaps don't make changes here, but in the rkwarddev script instead!"
  )
  if(!is.null(script)){
    genInfo <- paste0(genInfo, "\n\nlook for a file called: ", script)
  } else {}
  return(rk.comment(genInfo))
} ## end function generator.info()


## function auto.ids()
auto.ids <- function(identifiers, prefix=NULL, suffix=NULL, chars=8){
  identifiers <- gsub("[[:space:]]*[^[0-9A-Za-z]]*", "", identifiers)
  id.names <- ifelse(nchar(identifiers) > 8, abbreviate(identifiers, minlength=chars), identifiers)
  # check for uniqueness
  if(any(duplicated(id.names))){
    warning("IDs are not unique, please check!")
  } else {}
  ids <- paste0(prefix, id.names, suffix)
  return(ids)
} ## end function auto.ids()


## function stripCont()
# get slots out of certain container objects
stripCont <- function(obj, get="printout"){
  if(inherits(obj, "rk.plot.opts")){
    # if this is a plot options object, extract the XML slot
    # and discard the rest
    obj <- slot(obj, get)
  } else {}
  return(obj)
}
## end function stripCont()


## function stripXML()
# get XML node out of container objects
stripXML <- function(obj){
  return(stripCont(obj, get="XML"))
}
## end function stripXML()


## function child.list()
# convenience function to let single children be provided without list()
# 'empty' can be used to make sure a tag is non-empty without actual value
# this function also reduces rk.plot.opts objects to their XiMpLe.node slot
child.list <- function(children, empty=TRUE){
  if(is.XiMpLe.node(children)){
    children <- list(children)
  } else {
    # if already a list, check if it's a list in a list and get it out
    if(inherits(children, "list") & length(children) == 1){
      if(inherits(children[[1]], "list")){
        children <- children[[1]]
      } else {}
    } else if(identical(children, list()) & !isTRUE(empty)){
      children <- list("")
    } else {}
    children <- lapply(children, function(this.child){
        stripXML(this.child)
      })
  }
  return(children)
} ## end function child.list()


## function trim()
# cuts off space at start and end of a character string
trim <- function(char){
  char <- gsub("^[[:space:]]*", "", char)
  char <- gsub("[[:space:]]*$", "", char)
  return(char)
} ## end function trim()


## function trim.n()
# cuts off newline at start and end of a character string
trim.n <- function(char){
  char <- gsub("^([\n]*)", "", char)
  char <- gsub("([\n]*)$", "", char)
  return(char)
} ## end function trim.n()


## function indent()
# will create tabs to format the output
indent <- function(level, by=rk.get.indent()){
  paste(rep(by, max(0, level-1)), collapse="")
} ## end function indent()


## function rk.noquote()
# use noquote() in a slightly different manner:
# if "text" is a noquote object, returns "noquote(\"text\")"
rk.noquote <- function(text){
  if(inherits(text, "noquote")){
    return(paste0("noquote(", qp(paste0(text)), ")"))
  } else {
    return(text)
  }
} ## end function rk.noquote()


## function checkCreateFiles()
# used by rk.plugin.skeleton()
checkCreateFiles <- function(file.name, ow, action=NULL){
  if(all(file.exists(file.name), as.logical(ow)) | !file.exists(file.name)){
    return(TRUE)
  } else {
    if(!is.null(action)){
      action <- paste0(action, ": ")
    } else {}
    warning(paste0(action, "Skipping existing file ", file.name, "."), call.=FALSE)
    return(FALSE)
  }
} ## end function checkCreateFiles()


## function get.single.tags()
get.single.tags <- function(XML.obj, drop=NULL){
  # determine if we need to read a file or process an XiMpLe object
  if(any(is.XiMpLe.doc(XML.obj), is.XiMpLe.node(XML.obj))){
    single.tags <- trim(unlist(strsplit(pasteXML(XML.obj, shine=1, indent.by=""), split="\n")))
  } else if(!is.null(XML.obj)){
    xml.raw <- paste(readLines(XML.obj), collapse=" ")
    single.tags <- XiMpLe:::XML.single.tags(xml.raw, drop=drop)
  } else {
    return(NULL)
  }
  names(single.tags) <- NULL

  return(single.tags)
} ## end function get.single.tags()


## function filter.relevant.tags()
# filters XML tags and returns a list of only relevant tags.
# - single.tags: either character vector of single XML tags or a list of XiMpLe nodes.
# - relevant.tags: character vector with tag names to scan for
filter.relevant.tags <- function(single.tags, relevant.tags){
  cleaned.tags <- list()
  for(this.tag in child.list(single.tags)){
    if(is.XiMpLe.node(this.tag)){
      this.tag.name <- XMLName(this.tag)
    } else {
      this.tag.name <- tolower(XiMpLe:::XML.tagName(this.tag))
    }
    if(this.tag.name %in% relevant.tags){
      cleaned.tags[length(cleaned.tags)+1] <- this.tag
    } else {}
  }
  return(cleaned.tags)
}
## end function filter.relevant.tags()


## function get.IDs()
# scans XML tags for defined IDs, returns a matrix with columns "id" and "abbrev",
# and optional "tag". "abbrev" is mostly used for the JavaScript variable name.
# 'single.tags' can also contain XiMpLe.node objects
get.IDs <- function(single.tags, relevant.tags, add.abbrev=FALSE, tag.names=FALSE, only.checkable=FALSE){

  # filter for relevant tags
  single.tags <- filter.relevant.tags(single.tags=single.tags, relevant.tags=relevant.tags)
  cleaned.tags <- list()
  for(this.tag in child.list(single.tags)){
    if(is.XiMpLe.node(this.tag)){
      this.tag.name <- XMLName(this.tag)
      if("id" %in% names(XMLAttrs(this.tag))){
        if(isTRUE(only.checkable) & this.tag.name %in% "frame"){
          if("checkable" %in% names(XMLAttrs(this.tag))){
            if(identical(XMLAttrs(this.tag)[["checkable"]], "true")){
              cleaned.tags[length(cleaned.tags)+1] <- this.tag
            } else {}
          } else {}
        } else {
          cleaned.tags[length(cleaned.tags)+1] <- this.tag
        }
      } else {}
    } else {
      this.tag.name <- tolower(XiMpLe:::XML.tagName(this.tag))
      # we're only interested in entries with an ID
      if("id" %in% names(XiMpLe:::parseXMLAttr(this.tag))){
        if(isTRUE(only.checkable) & this.tag.name %in% "frame"){
          if("checkable" %in% names(XiMpLe:::parseXMLAttr(this.tag))){
            if(identical(XiMpLe:::parseXMLAttr(this.tag)[["checkable"]], "true")){
              cleaned.tags[length(cleaned.tags)+1] <- this.tag
            } else {}
          } else {}
        } else {
          cleaned.tags[length(cleaned.tags)+1] <- this.tag
        }
      } else {}
    }
  }

  ids <- t(sapply(cleaned.tags, function(this.tag){
        if(is.XiMpLe.node(this.tag)){
          this.tag.name <- XMLName(this.tag)
          this.tag.id.abbrev <- this.tag.id <- check.ID(this.tag)
        } else {
          this.tag.name <- XiMpLe:::XML.tagName(this.tag)
          this.tag.id.abbrev <- this.tag.id <- XiMpLe:::parseXMLAttr(this.tag)[["id"]]
        }
        # take care of one special case: optionsets
        # they need the set ID to access the value from the dialog,
        # but to be able to use only the optioncolumn in rkwaddev scripts
        # as reference, the JavaScript variable must be generated from the
        # column ID alone.
        if(identical(this.tag.name, "optioncolumn")){
          this.tag.id <- check.ID(this.tag.id, search.environment=TRUE)
          # for safety, prefix the column ID with a constant
          this.tag.id.abbrev <- paste0("ocol_", this.tag.id.abbrev)
        } else {}

        if(isTRUE(add.abbrev)){
          this.tag.id.abbrev <- paste0(ID.prefix(this.tag.name), this.tag.id.abbrev)
        } else {}
      if(isTRUE(tag.names)){
        return(c(id=this.tag.id, abbrev=this.tag.id.abbrev, tag=this.tag.name))
      } else {
        return(c(id=this.tag.id, abbrev=this.tag.id.abbrev))
      }
    }
  ))
  rownames(ids) <- NULL

  # do a check if all IDs are really unique
  if("id" %in% names(ids)){
    multiple.id <- duplicated(ids[,"id"])
    if(any(multiple.id)){
      warning(paste0("IDs are not unique:\n  ", paste(ids[multiple.id,"id"], collapse=", "), "\n  Expect errors!"))
    } else {}
  }

  return(ids)
} ## end function get.IDs()

## function check.optionset.tags()
# XML.obj may be a character string (file name) or XiMpLe object.
# this function will check if <optionset> nodes are present
# and return a possibly corrected result of get.single.tags(),
# where "corrected" means: optioncolumns internally will gain an
# attribute "setid" with the respective set ID, and the rest of the
# set is discarded.
# this extra attribute is evaluated by get.IDs().
check.optionset.tags <- function(XML.obj, drop=NULL){
  # if this is not a XiMpLe object, transform the file into one
  if(!is.XiMpLe.node(XML.obj) && !is.XiMpLe.doc(XML.obj)){
    XML.obj <- parseXMLTree(XML.obj, drop=drop)
  } else {}
  # first get a list of all optionsets
  optionset.nodes <- child.list(XMLScan(XML.obj, "optionset"))
  # are there any?
  if(!is.null(optionset.nodes)){
    for (thisNode in optionset.nodes){
      optioncolumn.nodes <- child.list(XMLScan(thisNode, "optioncolumn"))
      # register column and set IDs internally
      rk.register.options(optioncolumn.nodes, parent.node=thisNode)
    }
  } else {}
  result <- get.single.tags(XML.obj=XML.obj, drop=drop)
  return(result)
} ## end function check.optionset.tags()


## function camelCode()
# changes the first letter of each string
# (except for the first one) to upper case
camelCode <- function(words){

  words <- as.vector(unlist(sapply(words, function(cur.word){
      unlist(strsplit(cur.word, split="[._]"))
    })))

  new.words <- sapply(words[-1], function(cur.word){
    word.vector <- unlist(strsplit(cur.word, split=""))
    word.vector[1] <- toupper(word.vector[1])
    word.new <- paste(word.vector, collapse="")
    return(word.new)
  })

  results <- paste0(words[1], paste(new.words, collapse=""))

  return(results)
} ## end function camelCode()


## function get.JS.vars()
# see 60_JS.getters.default.R for definition of JS.getters.default and JS.getters.modif.default 
#   <tag id="my.id" ...>
# in XML will become
#   var my.id = getValue("my.id");
get.JS.vars <- function(JS.var, XML.var=NULL, tag.name=NULL, JS.prefix="", names.only=FALSE, modifiers=NULL, default=FALSE, join="",
  getter="getValue", guess.getter=FALSE, check.modifiers=TRUE, search.environment=FALSE, append.modifier=TRUE, methods=""){
  # check for XiMpLe nodes
  JS.var <- check.ID(JS.var)
  have.XiMpLe.var <- FALSE
  if(!is.null(XML.var)){
    if(is.XiMpLe.node(XML.var)){
      have.XiMpLe.var <- TRUE
      tag.name <- XMLName(XML.var)
    } else if(is.null(tag.name)){
      # hm, not a XiMpLe object and no known tag name :-/
      # if this is simply a character string, the tag name will become ""
      tag.name <- XMLName(XMLChildren(parseXMLTree(XML.var, object=TRUE))[[1]])
    } else {}

    # check validity of modifiers value
    if(!is.null(modifiers)){
      if(identical(modifiers, "all")){
        if(tag.name %in% names(all.valid.modifiers)){
          modifiers <- all.valid.modifiers[[tag.name]]
        } else {
          modifiers <- NULL
        }
      } else {
        if(identical(tag.name, "")){
          modif.tag.name <- "all"
        } else {
          modif.tag.name <- tag.name
        }
        if(isTRUE(check.modifiers)){
          modifiers <- modifiers[modif.validity(modif.tag.name,
            modifier=child.list(modifiers), warn.only=TRUE, bool=TRUE)]
        } else {}
      }
    } else {}

    # check for getter guessing
    if(isTRUE(guess.getter)){
      # see 60_JS.getters.default.R for definition of JS.getters.default
      if(tag.name %in% names(JS.getters.default)){
        # special case: is a <checkbox> has a value other than
        # "true" or "false", it's probably supposed to be fetched
        # as string, not boolean
        if(isTRUE(have.XiMpLe.var) && identical(tag.name, "checkbox") &&
          any(!c(XMLAttrs(XML.var)[["value"]], XMLAttrs(XML.var)[["value_unchecked"]]) %in% c("true","false"))){
          getter <- "getString"
        } else {
          # check if a modifier is given and we have a default for it
          # modifiers were probably checked already
          ## TODO: currently this only works for one modifier of if all
          ## modifiers are fine with the same getter; maybe "getter"
          ## should become a vector like "modifiers"
          # see 60_JS.getters.default.R for definition of JS.getters.modif.default
          if(!is.null(modifiers) && any(modifiers %in% names(JS.getters.modif.default))){
            # find all matching modifiers
            getter.modifs <- modifiers[modifiers %in% names(JS.getters.modif.default)]
            all.getters <- unique(unlist(JS.getters.modif.default[getter.modifs]))
            if(length(all.getters) > 1){
              warning("For the modifiers you specified, different getter functions were found. Only using the first one!", call.=FALSE)
              getter <- all.getters[1]
            } else {
              getter <- all.getters
            }
          } else {
            getter <- JS.getters.default[[tag.name]]
          }
        }
      } else {}
    } else {
      # if guess.getters is off but we're dealing with <matrix> or <optionset>,
      # throw in a warning:
      if(tag.name %in% c("matrix", "optioncolumn") && identical(getter, "getValue")){
        warning(paste0("Your plugin contains the <", tag.name, "> element, but 'guess.getter' is off. ",
          "Using the default getValue() on this node might cause problems!"), call.=FALSE)
      } else {}
    }
    XML.var <- check.ID(XML.var, search.environment=search.environment)
  } else {
    XML.var <- check.ID(JS.var, search.environment=search.environment)
  }

  if(is.null(JS.prefix)){
    JS.prefix <- ""
  } else {}

  if(isTRUE(names.only)){
    results <- c()
    if(is.null(modifiers) || isTRUE(default)){
      results <- camelCode(c(JS.prefix, JS.var))
    } else {}
    if(!is.null(modifiers)){
      results <- c(results,
        sapply(modifiers, function(this.modif){camelCode(c(JS.prefix, JS.var, this.modif))})
      )
    } else {}
  } else {
    if(is.null(modifiers)){
       modifiers <- list()
    } else {}
    results <- new("rk.JS.var",
      JS.var=JS.var,
      XML.var=XML.var,
      prefix=JS.prefix,
      modifiers=as.list(modifiers),
      default=default,
      append.modifier=append.modifier,
      join=join,
      getter=getter,
      methods=methods)
  }

  return(results)
} ## end function get.JS.vars()


## function ID.prefix()
ID.prefix <- function(initial, abbr=TRUE, length=3, dot=FALSE){
  if(isTRUE(abbr)){
    prfx <- abbreviate(initial, minlength=length, strict=TRUE)
  } else {
    # currently empty, but can later be used to define fixed abbreviations
    prfx <- NULL
  }
  if(isTRUE(dot)){
    prfx <- paste0(prfx, ".")
  } else {
    prfx <- paste0(prfx, "_")
  }
  return(prfx)
} ## end function ID.prefix()


## function node.soup()
# pastes the nodes as XML, only alphanumeric characters, e.g. to generate auto-IDs
node.soup <- function(nodes){
  the.soup <- paste0(unlist(sapply(child.list(nodes), function(this.node){
      if(is.XiMpLe.node(this.node)){
        return(gsub("[^[:alnum:]]", "", pasteXML(this.node, shine=0)))
      } else {
        stop(simpleError("Nodes must be of class XiMpLe.node!"))
      }
    })), collapse="")
  return(the.soup)
} ## end function node.soup()


## function XML2person()
# extracts the person/author info from XML "about" nodes
XML2person <- function(node, eval=FALSE){
    if(is.XiMpLe.node(node)){
      # check if this is *really* a about section, otherwise die of boredom
      if(!identical(XMLName(node), "about")){
        stop(simpleError("I don't know what this is, but 'about' is not an about section!"))
      } else {}
    } else {
      stop(simpleError("'about' must be a XiMpLe.node, see ?rk.XML.about()!"))
    }
  make.vector <- function(value){
    if(grepl(",", value)){
      value <- paste0("c(\"", paste(trim(unlist(strsplit(value, ","))), collapse="\", \""), "\")")
    } else {
      value <- paste0("\"", value, "\"")
    }
    return(value)
  }
  all.authors <- c()
  for (this.child in XMLChildren(node)){
    if(identical(XMLName(this.child), "author")){
      attrs <- XMLAttrs(this.child)
      given <- make.vector(attrs[["given"]])
      family <- make.vector(attrs[["family"]])
      if(!is.null(attrs[["email"]])){
        email <- paste0(", email=", make.vector(attrs[["email"]]))
      } else {
        email <- ""
      }
      role <- make.vector(attrs[["role"]])
      this.author <- paste0("person(given=", given, ", family=", family, email, ", role=", role, ")")
      all.authors[length(all.authors) + 1] <- this.author
    } else {}
  }
  if(length(all.authors) > 1){
    all.authors <- paste0("c(", paste(all.authors, collapse=", "), ")")
  } else {}
  if(isTRUE(eval)){
    all.authors <- eval(parse(text=all.authors))
  } else {}
  return(all.authors)
} ## end function XML2person()


## function XML2dependencies()
# extracts the package dependencies info from XML "about"/"dependencies" nodes
# in "suggest" mode only suggestions will be returned, in "depends" mode only dependencies.
# suggest=TRUE: Depends: R & RKWard; Suggests: packages
# suggest=FALSE: Depends: R & RKWard & packages; suggests: none
XML2dependencies <- function(node, suggest=TRUE, mode="suggest"){
  if(!isTRUE(suggest) && identical(mode, "suggest")){
    return("")
  } else {}
  if(is.XiMpLe.node(node)){
    # check if this is *really* a about section, otherwise die of boredom
    if(!XMLName(node) %in% c("about", "dependencies")){
      # are these perhaps commented out? then just quit silently
      if(XMLName(node) %in% "!--"){
        return("")
      } else {
        stop(simpleError("Please provide a valid about or dependencies section!"))
      }
    } else {}
  } else {
    stop(simpleError("'about' and/or 'dependencies' must be XiMpLe.nodes, see ?rk.XML.about() and ?rk.XML.dependencies()!"))
  }
  got.deps <- XMLScan(node, "dependencies")
  if(!is.null(got.deps)){
    deps.packages <- list()
    # first see if RKWard and R versions are given
    deps.RkR <- XMLAttrs(got.deps)
    deps.RkR.options  <- names(deps.RkR)
    R.min <- ifelse("R_min_version" %in% deps.RkR.options, paste0(">= ", deps.RkR[["R_min_version"]]), "")
    R.max <- ifelse("R_max_version" %in% deps.RkR.options, paste0("< ", deps.RkR[["R_max_version"]]), "")
    R.version.indices <- sum(!identical(R.min, ""), !identical(R.max, ""))
    if(R.version.indices > 0 & identical(mode, "depends")){
      deps.packages[[length(deps.packages) + 1]] <- paste0("R (", R.min, ifelse(R.version.indices > 1, ", ", ""), R.max, ")")
    } else {}
    Rk.min <- ifelse("rkward_min_version" %in% deps.RkR.options, paste0(">= ", deps.RkR[["rkward_min_version"]]), "")
    Rk.max <- ifelse("rkward_max_version" %in% deps.RkR.options, paste0("< ", deps.RkR[["rkward_max_version"]]), "")
    Rk.version.indices <- sum(!identical(Rk.min, ""), !identical(Rk.max, ""))
    if(Rk.version.indices > 0 && identical(mode, "depends")){
      deps.packages[[length(deps.packages) + 1]] <- paste0("rkward (", Rk.min, ifelse(Rk.version.indices > 1, ", ", ""), Rk.max, ")")
    } else {}
    check.deps.pckg <- sapply(XMLChildren(got.deps), function(this.child){identical(XMLName(this.child), "package")})
    if(any(check.deps.pckg) && ((isTRUE(suggest) && identical(mode, "suggest")) | !isTRUE(suggest))){
      deps.packages[[length(deps.packages) + 1]] <- paste(sapply(which(check.deps.pckg), function(this.pckg){
          this.pckg.dep <- XMLAttrs(XMLChildren(got.deps)[[this.pckg]])
          pckg.options <- names(this.pckg.dep)
          pckg.name <- this.pckg.dep[["name"]]
          pckg.min <- ifelse("min" %in% pckg.options, paste0(">= ", this.pckg.dep[["min"]]), "")
          pckg.max <- ifelse("max" %in% pckg.options, paste0("< ", this.pckg.dep[["max"]]), "")
          version.indices <- sum(!identical(pckg.min, ""), !identical(pckg.max, ""))
          if(version.indices > 0){
            pckg.version <- paste0(" (", pckg.min, ifelse(version.indices > 1, ", ", ""), pckg.max, ")")
          } else {
            pckg.version <- ""
          }
          return(paste0(pckg.name, pckg.version))
        }), collapse=", ")
    } else {}
    results <- paste(unlist(deps.packages), collapse=", ")
  } else {
    results <- ""
  }
  return(results)
} ## end function XML2dependencies()


## function get.by.role()
# filters a vector with person objects by roles
get.by.role <- function(persons, role="aut"){
  role.filter <- function(x){is.null(r <- x$role) | role %in% r}
  filtered.persons <- Filter(role.filter, persons)
  return(filtered.persons)
} ## end function get.by.role()


## function get.authors()
get.authors <- function(description, maintainer=TRUE, contributor=FALSE, copyright=FALSE){
  if("Authors@R" %in% names(description)){
    gotPersons <- TRUE
    authorsFromDescription <- description[["Authors@R"]]
  } else if("Author@R" %in% names(description)){
    gotPersons <- TRUE
    authorsFromDescription <- description[["Author@R"]]
  } else {
    gotPersons <- FALSE
  }
  
  if(isTRUE(gotPersons)){
    got.aut <- paste(format(get.by.role(eval(parse(text=authorsFromDescription))), include=c("given", "family")), collapse=", ")
    got.cre <- ifelse(isTRUE(maintainer),
      paste(format(get.by.role(eval(parse(text=authorsFromDescription)), role="cre"), include=c("given", "family", "email")), collapse=", "),
      "")
    got.ctb <- ifelse(isTRUE(contributor),
      paste(format(get.by.role(eval(parse(text=authorsFromDescription)), role="ctb"), include=c("given", "family")), collapse=", "),
      "")
    got.cph <- ifelse(isTRUE(copyright),
      paste(format(get.by.role(eval(parse(text=authorsFromDescription)), role="cph"), include=c("given", "family")), collapse=", "),
      "")
  } else {
    got.aut <- description[["Author"]]
    got.cre <- ifelse(isTRUE(maintainer),
      description[["Maintainer"]],
      "")
    # contributors should already be named in got.aut
    got.ctb <- ""
    got.cph <- ""
  }
  got.cre.clean <- gsub("<([^@]*)@([^>]*)>", "\\\\email{\\1@@\\2}", gsub("\n[[:space:]]*", "\n#' ", got.cre))
  # append contributors
  if(isTRUE(contributor) && got.ctb != ""){
    got.aut <- paste0(got.aut, ", with contributions from ", got.ctb)
  } else {}
  gotAuthors <- list(aut=got.aut, cre=got.cre, cre.clean=got.cre.clean, ctb=got.ctb, cph=got.cph)
  return(gotAuthors)
} ## end function get.authors()


## function check.ID()
# - node: a XiMpLe.node to search for an ID
# - search.environment: if TRUE, the internal environment is searched for the ID
#     as well; a use case for this is IDs of options, which need their parent IDs as well;
#     see get.optionIDs() below
# - env.get: the ID type to fetch from the environment, if search.environment=TRUE
check.ID <- function(node, search.environment=FALSE, env.get="XML"){
  if(is.list(node)){
    return(sapply(node, check.ID))
  } else {}

  if(is.XiMpLe.node(node)){
    node.ID <- XMLAttrs(node)[["id"]]
    if(isTRUE(search.environment)){
      optionIDs <- get.optionIDs()[[node.ID]]
      node.ID <- ifelse(is.null(optionIDs), node.ID, optionIDs[[env.get]])
    } else {}
  } else if(is.character(node)){
    node.ID <- node
    if(isTRUE(search.environment)){
      optionIDs <- get.optionIDs()[[node.ID]]
      node.ID <- ifelse(is.null(optionIDs), node.ID, optionIDs[[env.get]])
    } else {}
  } else {
    stop(simpleError("Can't find an ID!"))
  }

  if(is.null(node.ID)){
    warning("ID is NULL!")
  } else {}

  names(node.ID) <- NULL

  return(node.ID)
} ## end function check.ID()


## function modif.validity()
# checks if a modifier is valid for an XML node, if source is XiMpLe.node
# if bool=FALSE, returns the modifier or ""
# modifier can take multiple modifiers at once
modif.validity <- function(source, modifier, ignore.empty=TRUE, warn.only=TRUE, bool=TRUE){
  if(identical(modifier, "") & isTRUE(ignore.empty)){
    if(isTRUE(bool)){
      return(TRUE)
    } else {
      return(modifier)
    }
  } else {}

  if(is.XiMpLe.node(source)){
    tag.name <- XMLName(source)
    # certain elemens/embedded plugins can have all sorts of modifiers
    if(tag.name %in% c("embed", "external", "switch")){
      if(isTRUE(bool)){
        return(TRUE)
      } else {
        return(modifier)
      }
    } else {}
  } else if(identical(source, "all")){
    tag.name <- "<any tag>"
  } else {
    tag.name <- source
  }

  if(tag.name %in% names(all.valid.modifiers)){
    valid.modifs <- c(all.valid.modifiers[["all"]], all.valid.modifiers[[tag.name]])
  } else if(identical(tag.name, "<any tag>")){
    valid.modifs <- unique(unlist(all.valid.modifiers))
  } else {
    valid.modifs <- c(all.valid.modifiers[["all"]])
  }

  invalid.modif <- !unlist(modifier) %in% valid.modifs
  if(any(invalid.modif)){
    warnErrMsg <- paste0("Some modifier you provided is invalid for '", tag.name, "' and was ignored: \"",
      paste(modifier[invalid.modif], collapse="\", \""), "\"\n\n",
      "Known modifiers for '", tag.name, "' nodes are:\n  \"", paste0(unlist(modifiers(obj=tag.name)[[tag.name]]), collapse="\", \""), "\"\n\n",
      "For a list of all valid modifiers call modifiers(\"", tag.name, "\")")
    if(isTRUE(warn.only)){
      warning(warnErrMsg, call.=FALSE)
      if(isTRUE(bool)){
        return(!invalid.modif)
      } else {
        return("")
      }
    } else {
      stop(simpleError(warnErrMsg))
    }
  } else {
    if(isTRUE(bool)){
      return(!invalid.modif)
    } else {
      return(modifier)
    }
  }
} ## end function modif.validity()


## function valid.child()
# - parent: character string, name of the parent node
# - children: (list of) XiMpLe.node objects, child nodes to check
# - warn: warning or stop?
# - section: an optional name for the section for the warning/error
#   (if it shouldn't be the parent name)
# - node names: can alternatively be given instead of 'children', as character vector
valid.child <- function(parent, children, warn=FALSE, section=parent, node.names=NULL){
  if(is.null(node.names)){
    # check the node names and allow only valid ones
    node.names <- unlist(sapply(child.list(children), function(this.child){
        # if this is a plot options object, by default extract the XML slot
        # and discard the rest
        this.child <- stripXML(this.child)

        if(is.XiMpLe.node(this.child)){
          this.child.name <- XMLName(this.child)
          if(identical(this.child.name, "")){
            # special case: empty node name; this is used to combine
            # comments with the node they belong to, so rather check
            # the children of this special node
            return(unlist(sapply(XMLChildren(this.child), XMLName)))
          } else {
            return(this.child.name)
          }
        } else {
          stop(simpleError(paste0("Invalid object for ", section, " section, must be of class XiMpLe.node, but got class ", class(this.child), "!")))
        }
      }))
  } else {}

  invalid.sets <- !node.names %in% all.valid.children[[parent]]
  if(any(invalid.sets)){
    return.message <- paste0("Invalid XML nodes for ", section, " section: ", paste(node.names[invalid.sets], collapse=", "))
    if(isTRUE(warn)){
      warning(return.message)
      return(FALSE)
    } else {
      stop(simpleError(return.message))
    }
  } else {
    return(TRUE)
  }
} ## end function valid.child()


## function valid.parent()
# checks if a node is what it's supposed to be
# - parent: character string, name of the parent node
# - node: a XiMpLe.node object to check
# - warn: warning or stop?
# - see: name of the function to check docs for
# - arg.name: optional argument name of a function where valid.parent() is called from,
#     e.g. if an object is given via "cbox" but checked for "checkbox"
valid.parent <- function(parent, node, warn=FALSE, see=NULL, arg.name=NULL, comment.ok=FALSE){
  if(is.XiMpLe.node(node)){
    node.name <- XMLName(node)
    if(identical(node.name, parent)){
      return(TRUE)
    } else {
      if(isTRUE(comment.ok) & identical(node.name, "!--")){
        return(TRUE)
      } else {}
      if(is.null(arg.name)){
        arg.name <- parent
      } else {}
      return.message <- paste0("I don't know what this is, but '", arg.name, "' is not a <", parent, "> section!")
      if(isTRUE(warn)){
        warning(return.message)
        return(FALSE)
      } else {
        stop(simpleError(return.message))
      }
    }
  } else {
    stop(simpleError(
        paste0("'", parent, "' must be a XiMpLe.node",
          if(!is.null(see)){paste0(", see ?", see)},
          "!"))
      )
  }
} ## end function valid.parent()


## function check.type()
check.type <- function(value, type, var.name, warn.only=TRUE){
  if(inherits(value, type)){
    return(invisible(NULL))
  } else {
    msg.text <- paste0(sQuote(var.name), " should be of type ", type, "!")
    if(isTRUE(warn.only)){
      warning(msg.text)
    } else {
      stop(simpleError(msg.text))
    }
  }
} ## end function check.type()


## function clean.name()
clean.name <- function(name, message=TRUE){
  name.orig <- name
  name <- gsub("[[:space:]]*[^[:alnum:]_.]*", "", name)
  if(!identical(name.orig, name)){
    if(isTRUE(message)){
      message(paste0("For file names ", sQuote(name.orig), " was renamed to ", sQuote(name), "."))
    } else {}
  } else {}
  return(name)
} ## end function clean.name()



## function paste.JS.ite()
# condensed: prints a single clause in the form "(cond) ? <true> : <false>"
paste.JS.ite <- function(object, level=1, indent.by=rk.get.indent(), recurse=FALSE, empty.e=rk.get.empty.e(), condensed=FALSE){
  stopifnot(inherits(object, "rk.JS.ite"))
  if(isTRUE(condensed)){
    main.indent <- scnd.indent <- ""
    if(nchar(slot(object, "thenJS")) > 0) {
      thenJS <- paste0(" ? ", gsub("^[[:space:]]*|[[:space:]]*$", "", slot(object, "thenJS")))
    } else {
      stop(simpleError("failed to write a condensed 'if' statement (JavaScript), because 'then' case is missing!"))
    }
    if(nchar(slot(object, "elseJS")) > 0) {
      # chop off beginning indent strings, otherwiese they ruin the code layout
      elseJS <- paste0(" : ", gsub("^[[:space:]]*|[[:space:]]*$", "", slot(object, "elseJS")))
    } else {
      elseJS <- ""
    }
    result <- paste0("(", slot(object, "ifJS"), ")", thenJS, elseJS, collapse="")
  } else {
    # check indentation
    main.indent <- indent(level, by=indent.by)
    scnd.indent <- indent(level+1, by=indent.by)

    # if this is not a single "if" but an "else if", do not indent
    if(isTRUE(recurse)){
      ifJS <- paste0("if(", slot(object, "ifJS"), ") {\n")
    } else {
      ifJS <- paste0(main.indent, "if(", slot(object, "ifJS"), ") {\n")
    }

    if(nchar(slot(object, "thenJS")) > 0) {
      # chop off beginning indent strings, otherwiese they ruin the code layout
      thenJS.clean <- gsub(paste0("^", indent.by, "*"), "", slot(object, "thenJS"))
      thenJS <- paste0(scnd.indent, thenJS.clean, "\n", main.indent, "}")
    } else {
      # if there is another rk.JS.ite object, call with recursion
      if(length(slot(object, "thenifJS")) == 1){
        thenJS <- paste0(paste.JS.ite(slot(object, "thenifJS")[[1]], level=level+1, indent.by=indent.by), "\n", main.indent, "}")
      } else {}
    }

    if(nchar(slot(object, "elseJS")) > 0) {
      # chop off beginning indent strings, otherwiese they ruin the code layout
      elseJS.clean <- gsub(paste0("^", indent.by, "*"), "", slot(object, "elseJS"))
      elseJS <- paste0(" else {\n", scnd.indent, elseJS.clean, "\n", main.indent, "}")
    } else {
      # if there is another rk.JS.ite object, call with recursion
      if(length(slot(object, "elifJS")) == 1){
        elseJS <- paste0(" else ", paste.JS.ite(slot(object, "elifJS")[[1]], level=level, indent.by=indent.by, recurse=TRUE))
      } else {
        if(isTRUE(empty.e)){
          # close for sure with an empty "else"
          elseJS <- " else {}"
        } else {
          elseJS <- NULL
        }
      }
    }

    result <- paste0(ifJS, thenJS, elseJS, collapse="")
  }

  return(result)
} ## end function paste.JS.ite()


## function paste.JS.array()
# opt.sep: the separator that comes *before* the option that is set, in the resulting code
paste.JS.array <- function(object, level=2, indent.by=rk.get.indent(), funct=NULL, opt.sep=NULL){
  stopifnot(inherits(object, "rk.JS.arr"))
  # check indentation
  main.indent <- indent(level, by=indent.by)
  scnd.indent <- indent(level+1, by=indent.by)

  arr.name  <- slot(object, "arr.name")
  opt.name  <- slot(object, "opt.name")
  variables <- slot(object, "variables")
  quote     <- slot(object, "quote")
  option    <- slot(object, "option")
  if(is.null(funct)){
    funct <- slot(object, "funct")
  } else {}
  if(is.null(funct) | identical(funct, "")){
    funct.start <- ""
    funct.end <- ""
  } else {
    funct.start <- paste0(funct, "(")
    funct.end <- ")"
  }
  if(is.null(opt.sep)){
    opt.sep <- slot(object, "opt.sep")
    if(is.null(opt.sep)){
      opt.sep <- ", "
    } else {}
  } else {}
  
  JS.array <- paste0(
    main.indent, "// define the array ", arr.name, " for values of R option \"", option, "\"\n",
    main.indent, "var ", arr.name, " = new Array();\n",
    main.indent, arr.name, ".push(",
    paste(variables, collapse=", "), ");\n",
    main.indent, "// clean array ", arr.name, " from empty strings\n",
    main.indent, arr.name, " = ", arr.name, ".filter(String);\n",
    main.indent, "// set the actual variable ", opt.name,
    ifelse(identical(option, ""), "", paste0(" for R option \"", option)),
    ifelse(identical(funct, ""), "\"", paste0("=", funct, "()\"")), "\n",
    main.indent, "if(", arr.name, ".length > 0) {\n",
    scnd.indent, "var ", opt.name, " = \"", opt.sep,
    ifelse(identical(option, ""), "", paste0(option, "=")),
    ifelse(isTRUE(quote),
      paste0(funct.start, "\\\"\" + ", arr.name, ".join(\"\\\", \\\"\") + \"\\\"",funct.end,"\";\n"),
      paste0(funct.start, "\" + ", arr.name, ".join(\", \") + \"",funct.end,"\";\n")
    ),
    main.indent, "} else {\n",
    scnd.indent, "var ", opt.name, " = \"\";\n",
    main.indent, "}\n")

  return(JS.array)
} ## end function paste.JS.array()


## function paste.JS.options()
# opt.sep: the separator that comes *before* the option that is set, in the resulting code
paste.JS.options <- function(object, level=2, indent.by=rk.get.indent(), array=NULL, funct=NULL, opt.sep=NULL){
  stopifnot(inherits(object, "rk.JS.opt"))
  # check indentation
  main.indent <- indent(level, by=indent.by)
  scnd.indent <- indent(level+1, by=indent.by)

  variable  <- slot(object, "var.name")
  option    <- slot(object, "opt.name")
  arr.name  <- camelCode(c("arr", variable))
  collapse  <- slot(object, "collapse")
  ifs       <- slot(object, "ifs")
  if(is.null(array)){
    array  <- slot(object, "array")
  } else {}
  if(is.null(funct)){
    funct <- slot(object, "funct")
  } else {}
  if(is.null(funct) | identical(funct, "")){
    funct.start <- ""
    funct.end <- ""
  } else {
    funct.start <- paste0(funct, "(")
    funct.end <- ")"
  }
  if(is.null(opt.sep)){
    opt.sep <- slot(object, "opt.sep")
    if(is.null(opt.sep)){
      opt.sep <- ", "
    } else {}
  } else {}

  # a function to add the object stuff to ite objects
  add.opts <- function(this.ite, collapse, array){
    if(isTRUE(array)){
      slot(this.ite, "thenJS") <- paste0(arr.name, ".push(", slot(this.ite, "thenJS"),");")
      if(length(slot(this.ite, "elseJS")) == 1){
        slot(this.ite, "elseJS") <- paste0(arr.name, ".push(", slot(this.ite, "elseJS"),");")
      } else {}
    } else {
      slot(this.ite, "thenJS") <- paste0(variable, " += ", collapse, slot(this.ite, "thenJS"),";")
      if(length(slot(this.ite, "elseJS")) == 1){
        slot(this.ite, "elseJS") <- paste0(variable, " += ", collapse, slot(this.ite, "elseJS"),";")
      } else {}
    }
    if(length(slot(this.ite, "elifJS")) == 1){
      slot(this.ite, "elifJS") <- list(add.opts(slot(this.ite, "elifJS")[[1]], collapse=collapse, array=array))
    } else {}
    if(length(slot(this.ite, "thenifJS")) == 1){
      slot(this.ite, "thenifJS") <- list(add.opts(slot(this.ite, "thenifJS")[[1]], collapse=collapse, array=array))
    } else {}
    return(this.ite)
  }

  # the object class makes sure this is a list of rk.JS.ite objects
  ifs.pasted <- sapply(1:length(ifs), function(thisIf.num){
    thisIf <- ifs[[thisIf.num]]
    # skip the first collapse
    if(thisIf.num > 1){
      this.collapse <- collapse
    } else {
      this.collapse <- ""
    }
    paste.JS.ite(add.opts(thisIf, collapse=this.collapse, array=array), level=level, indent.by=indent.by)
  })

#return(ifs.pasted)

  JS.options <- paste0(
    if(isTRUE(array)){
      paste0(
        main.indent, "// define the array ", arr.name, " for values of R option \"", option, "\"\n",
        main.indent, "var ", arr.name, " = new Array();\n")
    } else {
      paste0(main.indent, "var ", variable, " = \"\";\n")
    },
    paste0(ifs.pasted, collapse="\n"), "\n",
    if(isTRUE(array)){
      paste0(
        main.indent, "// clean array ", arr.name, " from empty strings\n",
        main.indent, arr.name, " = ", arr.name, ".filter(String);\n",
        main.indent, "// set the actual variable ", variable, " with all values for R option \"", option, "\"\n",
        main.indent, "if(", arr.name, ".length > 0) {\n",
        scnd.indent, "var ", variable, " = \"", opt.sep,
        ifelse(identical(option, ""), "", paste0(option, "=")),
        funct.start, "\" + ", arr.name, ".join(\", \") + \"",funct.end,"\";\n",
        main.indent, "} else {\n",
        scnd.indent, "var ", variable, " = \"\";\n",
        main.indent, "}\n")
    } else {})

  return(JS.options)
} ## end function paste.JS.options()


## function paste.JS.var()
# append.modifier: if a modifier is given, should that become part of the variable name? this is mostly
#   important for "checkbox", which has "state" as default modifier, but using the checkbox object will not
#   notice this. works only for the first modifier given.
# var: if FALSE, the variable is assumed to be already defined (globally?) and "var " will be omitted
paste.JS.var <- function(object, level=2, indent.by=rk.get.indent(), JS.prefix=NULL, modifiers=NULL, default=NULL, append.modifier=NULL,
  join=NULL, getter=NULL, names.only=FALSE, check.modifiers=FALSE, var=TRUE, methods=""){
  # paste several objects
  results <- unlist(sapply(slot(object, "vars"), function(this.obj){
      paste.JS.var(this.obj,
          level=level,
          indent.by=indent.by,
          JS.prefix=JS.prefix,
          modifiers=modifiers,
          default=default,
          append.modifier=append.modifier,
          join=join,
          getter=getter,
          names.only=names.only,
          check.modifiers=check.modifiers,
          var=var,
          methods=methods)}))
  if(!isTRUE(names.only) & !is.null(results)){
    results <- paste(results, collapse="\n")
  }
  if(!isTRUE(names.only)){
    results <- paste(results, collapse="")
  } else {}

  stopifnot(inherits(object, "rk.JS.var"))
  # check indentation
  main.indent <- indent(level, by=indent.by)

  JS.var        <- slot(object, "JS.var")
  XML.var       <- slot(object, "XML.var")
  if(is.null(JS.prefix)){
    JS.prefix   <- slot(object, "prefix")
  } else {}
  if(is.null(modifiers)){
    modifiers   <- slot(object, "modifiers")
  } else {}
  if(is.null(default)){
    default     <- slot(object, "default")
  } else {}
  if(is.null(append.modifier)){
    append.modifier  <- slot(object, "append.modifier")
  } else {}
  if(is.null(join)){
    join        <- slot(object, "join")
  } else {}
  if(is.null(getter)){
    getter      <- slot(object, "getter")
  } else {}
  if(identical(methods, "")){
    methods.code <- slot(object, "methods")
  } else {
    methods.code <- paste0(methods, collapse="")
  }

  if(!identical(join, "")){
    join.code <- paste0(".split(\"\\n\").join(\"", join, "\")")
  } else {
    join.code <- ""
  }

  # only paste something if there's variables outside the 'vars' slot
  if(length(nchar(JS.var)) > 0 & length(nchar(XML.var)) > 0){
    if(length(modifiers) == 0 | isTRUE(default)){
      if(isTRUE(names.only)){
        results <- c(results, camelCode(c(JS.prefix, JS.var)))
      } else {
        results <- paste0(main.indent, ifelse(isTRUE(var), "var ", ""), camelCode(c(JS.prefix, JS.var)), " = ", getter, "(\"", XML.var, "\")", join.code, methods.code, ";")
      }
    } else {}
    if(length(modifiers) > 0){
      if(isTRUE(check.modifiers)){
        # check modifiers
        modifiers <- modifiers[modif.validity(source="all", modifier=modifiers, ignore.empty=TRUE, warn.only=TRUE, bool=TRUE)]
      } else {}
      modif.results <- sapply(1:length(modifiers), function(this.modif.num){
          this.modif <- modifiers[[this.modif.num]]
          if(isTRUE(append.modifier) || this.modif.num > 1){
            this.name <- camelCode(c(JS.prefix, JS.var, this.modif))
          } else {
            this.name <- camelCode(c(JS.prefix, JS.var))
          }
          if(isTRUE(names.only)){
            return(this.name)
          } else {
            return(paste0(main.indent, ifelse(isTRUE(var), "var ", ""), this.name,
              " = ", getter, "(\"", XML.var, ".", this.modif, "\")", join.code, methods.code, ";"))
          }
        })
      if(identical(results, "")){
        results <- modif.results
      } else {
        results <- c(results, modif.results)
      }
    }
  } else {}

  if(isTRUE(names.only)){
    results <- c(results)
  } else {
    results <- paste(results, collapse="\n")
  }
  
  return(results)
} ## end function paste.JS.var()


## function paste.JS.optionsset()
paste.JS.optionsset <- function(object, level=2, indent.by=rk.get.indent()){
  stopifnot(inherits(object, "rk.JS.oset"))
  # check indentation
  main.indent <- indent(level, by=indent.by)
  scnd.indent <- indent(level+1, by=indent.by)
  thrd.indent <- indent(level+2, by=indent.by)

  vars <- slot(object, "vars")
  loopvar <- slot(object, "loopvar")
  columns <- slot(object, "columns")
  body <- slot(object, "body")
  collapse <- slot(object, "collapse")

  if(length(slot(vars, "vars")) > 0 | length(slot(vars, "JS.var")) > 0 ){
    paste.vars <- paste.JS.var(vars, level=level, indent.by=indent.by)
  } else {
    paste.vars <- c()
  }

  # if there's no body, we don't need a loop
  if(length(body) > 0){
    ## the for loop body
    for.head <- paste0(main.indent, "for (var ", loopvar, " = 0; ", loopvar, " < ", id(columns[[1]]), ".length; ++", loopvar, "){")

    paste.body <- sapply(body, function(bodyPart){
        rk.paste.JS(bodyPart, level=level+1, indent.by=indent.by)
      })
    # replace the column IDs with indexed ones
    for (thisCol in sapply(columns, id)){
      paste.body <- gsub(
        paste0("([^[:alnum:]]+|^)", thisCol, "([^[:alnum:]]+|$)"),
        paste0("\\1", thisCol, "[", loopvar, "]\\2"),
        paste.body, perl=TRUE)
    }

    for.foot <- paste0(
      scnd.indent, "if(", loopvar, " + 1 < ", id(columns[[1]]), ".length) {\n",
      thrd.indent, "echo(\"", collapse, "\");\n",
      scnd.indent, "}\n",
      main.indent, "}"
    )
    
    results <- paste(c(paste.vars, for.head, paste.body, for.foot), collapse="\n")
  } else {
    results <- paste.vars
  }
  return(results)
} ## end function paste.JS.optionsset()


## function dependenciesCompatWrapper()
# with RKWard 0.6.1, the dependencies will no longer be a part of <about>
# this wrapper takes both, "about" and "dependencies" arguments,
# splits dependencies off and returns both in a list
dependenciesCompatWrapper <- function(dependencies, about, hints=FALSE){
  if(!is.null(about)){
    # check if this is *really* a about section
    valid.parent("about", node=about, see="rk.XML.about")
    # check for <dependencies> in <about>; is NULL if not found
    # this will only be used if dependencies is NULL
    deps.in.about <- XMLScan(about, "dependencies")
    if(!is.null(deps.in.about)){
      warning("<dependencies> inside <about> is deprecated, use the 'dependencies' argument instead!")
      # remove the misplaced node
      XMLScan(about, "dependencies") <- NULL
    }
  } else {
    if(isTRUE(hints)){
      about <- XMLNode("!--", XMLNode("about", ""))
    } else {}
    deps.in.about <- NULL
  }

  # initialize results list
  results <- list(about=about)

  if(!is.null(dependencies)){
    # check if this is *really* a dependencies section
    valid.parent("dependencies", node=dependencies, see="rk.XML.dependencies", comment.ok=TRUE)
    results[["dependencies"]] <- dependencies
  } else if(is.XiMpLe.node(deps.in.about)){
    results[["dependencies"]] <- deps.in.about
  } else if(isTRUE(hints)){
    dependencies.XML <- XMLNode("!--", XMLNode("dependencies", ""))
    results[["dependencies"]] <- dependencies.XML
  } else {
    results[["dependencies"]] <- NULL
  }
  return(results)
} ## end function dependenciesCompatWrapper()


## function get.rk.env()
# generic function to query the internal environment and declare a desired object, if not present yet
get.rk.env <- function(name, value=list()){
  if(exists(name, envir=.rkdev.env, inherits=FALSE)){
    this.env <- as.list(.rkdev.env)[[name]]
  } else {
    assign(name, value, envir=.rkdev.env)
    this.env <- value
  }
  return(this.env)
} ## end function get.rk.env()


## function set.rk.env()
# generic function to write to the internal environment
set.rk.env <- function(name, value){
  assign(name, value, envir=.rkdev.env)
  return(invisible(NULL))
} ## end function set.rk.env()


## function get.rkh.prompter()
# returns either an empty list or the contents of rkh.prompter from the internal enviroment 
get.rkh.prompter <- function(){
  rkh.prompter <- get.rk.env("rkh.prompter", value=list())
  return(rkh.prompter)
} ## end function get.rkh.prompter()


## function get.optionIDs()
# returns either an empty list or the contents of rkh.prompter from the internal enviroment 
get.optionIDs <- function(){
  optionIDs <- get.rk.env("optionIDs", value=list())
  return(optionIDs)
} ## end function get.optionIDs()


## function rk.check.options()
# - options: a list, containig either named vectors in the form of
#       label=c(val=NULL, chk=FALSE, i18n=NULL)
#     or an "option" node of class XiMpLe.node
# - parent: the parent node type, e.g. "radio"
rk.check.options <- function(options, parent){
  num.opt <- length(options)
  all.options <- sapply(1:num.opt, function(this.num){
      if(is.XiMpLe.node(options[[this.num]])){
        # check the node names and allow only valid ones
        valid.child(parent, children=options[[this.num]])
        return(options[[this.num]])
      } else {
        if("val" %in% names(options[[this.num]])){
          value <- options[[this.num]][["val"]]
        } else {
          value <- NULL
        }
        if("chk" %in% names(options[[this.num]])){
          checked <- isTRUE(as.logical(options[[this.num]][["chk"]]))
        } else {
          checked <- FALSE
        }
        if("i18n" %in% names(options[[this.num]])){
          i18n <- options[[this.num]][["i18n"]]
        } else {
          i18n <- NULL
        }
        return(
          rk.XML.option(
            label=names(options)[[this.num]],
            val=value,
            chk=checked,
            id.name=NULL,
            i18n=i18n
          )
        )
      }
    })
  # see to it that only one options is "checked"
  is.checked <- sapply(all.options, function(this.opt){
      return(!is.null(XMLScanDeep(this.opt, find="checked")))
    })
  if(sum(is.checked) > 1){
    stop(simpleError("you defined options where more than one is 'checked' -- this is wrong!"))
  } else {}
  return(all.options)
}
## end function rk.check.options()


## function rk.register.options()
# - options: a list, containig either named vectors in the form of
#       label=c(val=NULL, chk=FALSE)
#     or an "option" node of class XiMpLe.node; only the latter will be
#     searched for IDs
# - parent.node: full parent XiMpLe.node option IDs will be registered in
#     an internal environment, which makes it easier to fetch a directly
#     usable ID (because it has to be prefixed with the parent ID)
rk.register.options <- function(options, parent.node){
  num.opt <- length(options)
  all.options <- sapply(1:num.opt, function(this.num){
    if(is.XiMpLe.node(options[[this.num]])){
      opt.id <- XMLAttrs(options[[this.num]])[["id"]]
      if(!is.null(opt.id)){
        # save ID with parents
        optionIDs <- get.optionIDs()
        thisID <- c(XML=opt.id, JS=id(options[[this.num]]))
        parentID <- c(XML=id(parent.node, js=FALSE), JS=id(parent.node))
        optionIDs[[opt.id]] <- list(
          XML=paste(parentID[["XML"]], thisID[["XML"]], sep="."),
          JS=paste(parentID[["JS"]], thisID[["JS"]], sep="."),
          parent=parentID
        )
        set.rk.env("optionIDs", value=optionIDs)
        } else {}
      } else {}
    })
}
## end function rk.register.options()


## function check.i18n()
# checks for additional i18n info in XiMpLe nodes. returns either an appended or altered list of
# attributes, or a XiMpLe node with an i18n comment
# i18n: either a list with possible named elements "context" or "comment",
#   or a charcter string (for wich it is assumed to describe a context),
#   or FALSE; if the latter, "label" will be renamed to "noi18n_label", "title" to "noi18n_title"
# attrs: a list of previously defined attributes
# comment: if TRUE, returns a pseudo node (with name "") containing a comment node and the original node,
#   else a list of attributes
check.i18n <- function(i18n=NULL, attrs=list(), node=NULL, comment=FALSE){
  if(isTRUE(comment)){
    result <- node
  } else {
    result <- attrs
  }
  if(is.null(i18n)){
    return(result)
  } else {
    if(is.list(i18n)){
      if(!all(names(i18n) %in% c("comment", "context"))){
        stop(simpleError("i18n: only elements named \"comment\" or \"context\" are supported!"))
      } else {}
      if(isTRUE(comment)){
        if("comment" %in% names(i18n)){
          if(!is.XiMpLe.node(node)){
            stop(simpleError("i18n: to add a \"comment\" to a node, an XML node must be present!"))
          } else {}
          result <- XMLNode("", rk.i18n.comment(i18n[["comment"]]), node)
        } else {}
      } else {
        if("context" %in% names(i18n)){
          result[["i18n_context"]] <- i18n[["context"]]
        } else{}
      }
    } else if(is.character(i18n) & length(i18n) == 1 & !isTRUE(comment)){
      result[["i18n_context"]] <- i18n[[1]]
    } else if(is.logical(i18n) & !isTRUE(i18n) & !isTRUE(comment)){
      if("label" %in% names(result)){
        names(result)[names(result) == "label"] <- "noi18n_label"
      } else {}
      if("title" %in% names(result)){
        names(result)[names(result) == "title"] <- "noi18n_title"
      } else {}
    } else {}
  }
  return(result)
} ## end function check.i18n()


## function force.i18n
# ensures that obj is encapsulated in i18n() in the output
force.i18n <- function(obj){
  stopifnot(length(obj) == 1)
  if(inherits(obj, "rk.JS.i18n")){
    result <- slot(obj, "value")
  } else if(is.character(obj)){
    result <- paste0("i18n(\"", obj, "\")")
  } else {
    stop(simpleError(paste0("force.i18n: don't know how to deal with an object of class ", class(obj), "!")))
  }
  return(result)
} ## end function force.i18n


## function js.try.scan()
# called in rk.plugin.component()
js.try.scan <- function(XML.plugin, scan, js, guess.getter, unused.vars=FALSE){
  if("var" %in% scan){
    if(!isTRUE(unused.vars) & any(!is.null(js[["calculate"]]), !is.null(js[["printout"]]))){
      script <- paste(paste(js[["calculate"]], collapse=" "), paste(js[["printout"]], collapse=" "))
    } else {
      script <- NULL
    }
    var.scanned <- rk.JS.scan(XML.plugin, guess.getter=guess.getter, mode="vars", script=script)
    if(!is.null(var.scanned)){
      js[["variables"]] <- paste0(
        ifelse(is.null(js[["variables"]]), "", paste0(js[["variables"]], "\n")),
        var.scanned)
    } else {}
  } else {}
  if("preview" %in% scan){
    if(!is.character(js[["preview"]])){
        preview.scanned <- rk.JS.scan(XML.plugin, guess.getter=guess.getter, mode="preview")
        if(identical(preview.scanned, "")){
        js[["preview"]] <- FALSE
        } else {
        js[["preview"]] <- TRUE
        }
    } else {}
  } else {}
  if("saveobj" %in% scan){
    saveobj.scanned <- rk.JS.saveobj(XML.plugin, preview=(isTRUE(js[["preview"]]) | is.character(js[["preview"]])))
    if(!is.null(saveobj.scanned)){
      js[["printout"]] <- paste(js[["printout"]], saveobj.scanned, sep="\n")
    } else {}
  } else {}
  return(js)
} ## end function js.try.scan()


## function check.JS.lines()
# called by rk.JS.scan()
check.JS.lines <- function(relevant.tags, single.tags, add.abbrev, js, indent.by, guess.getter,
  tag.names=TRUE, modifiers=NULL, only.checkable=FALSE, append.modifier=TRUE, script=NULL, unused.vars=FALSE, result=NULL){

  JS.id <- get.IDs(single.tags=single.tags, relevant.tags=relevant.tags, add.abbrev=add.abbrev,
    tag.names=tag.names, only.checkable=only.checkable)

  if("id" %in% colnames(JS.id)){
    if(isTRUE(js)){
      # now
      #   <tag id="my.id" ...>
      # will become
      #   var my.id = getValue("my.id");
      # 
      # we'll first get a list of rk.JS.var objects to be able so check for
      # their appearance in the script body
      result.vars <- lapply(
        1:nrow(JS.id),
        function(this.id){
          return(get.JS.vars(
            JS.var=JS.id[this.id,"abbrev"],
            XML.var=JS.id[this.id,"id"],
            tag.name=JS.id[this.id,"tag"],
            modifiers=modifiers,
            append.modifier=append.modifier,
            guess.getter=guess.getter))
        }
      )
      if(all(!is.null(script), !isTRUE(unused.vars))){
        result.vars <- lapply(
          result.vars,
          function(thisVar){
            checkVar <- paste.JS.var(object=thisVar, names.only=TRUE)
            # see if the variable is mentioned anywhere in the script code
            # we see a valid use if the varaible appears without other characters or numbers
            # directly before or after it (which hints to being a different variable)
            if(grepl(paste0("(^|[^[:alnum:]])", checkVar, "([^[:alnum:]]|$)"), script)){
              return(thisVar)
            } else {
              return(NULL)
            }
          }
        )
        # get rid of NULLs
        result.vars <- Filter(Negate(is.null), result.vars)
      } else {}
      result <- paste(
        result,
        paste(
          unlist(sapply(
            result.vars,
            rk.paste.JS,
            level=2, indent.by=indent.by,
            USE.NAMES=FALSE
          )),
          collapse="\n"
        ),
        sep="\n", collapse="\n"
      )
    } else {
      result <- c(result, JS.id[,"id"])
      names(result) <- NULL
    }
  } else {}
  return(result)
} ## end function check.JS.lines()


## JS.operators
# a complilation of operators we would like to fetch from R calls and 
# substitute with character equivalents for JS code
JS.operators <- c(
  "+", "-", "*", "/", "%",
  "++", "--", "=", "+=", "-=", "*=", "/=", "%=",
  "==", "===", "!=", "!==", ">", "<", ">=", "<=",
  "!", "||", "&&"
) ## end JS.operators
# currently not working: "%", "++", "--", "=", "+=", "-=", "*=", "/=", "%=", "===", "!==", "!"


## function replaceJSOperators
# takes arbitrary R code and tries to replace R operators with character strings.
# makes it possible to use these operators in calls like id() without the need
# for quoting them
replaceJSOperators <- function(..., call="id"){
  dotlist <- eval(substitute(alist(...)))
  dotlist <- lapply(
    dotlist,
    function(thisItem){
      # operators like ">" or "|" are represented as call objects
      # with the operator as first argument (name).
      # there can also be calls nested in calls so we need to test this recursively
      if(inherits(thisItem, "call")){
        callList <- unlist(thisItem)
        if(as.character(callList[[1]]) %in% JS.operators){
          result <- list(
            # the "!" operator needs to come first
            if(as.character(callList[[1]]) %in% "!"){
              paste0(as.character(callList[[1]]))
            } else {},
            if(is.call(callList[[2]])){
              do.call("replaceJSOperators", list(callList[[2]]))
            } else if(is.character(callList[[2]])){
              paste0("\"", callList[[2]], "\"")
            } else if(is.name(callList[[2]])){
              # if this gets called inside a local() call, make sure we fetch the referenced object at all
              fetchedObject1 <- dynGet(as.character(callList[[2]]), ifnotfound=get(as.character(callList[[2]])))
              do.call(call, list(fetchedObject1))
            } else {
              do.call(call, list(callList[[2]]))
            },
            # all except the "!" operator come here
            if(!as.character(callList[[1]]) %in% "!"){
              paste0(" ", as.character(callList[[1]]), " ")
            } else {},
            # operators like "!" don't have a third element
            if(length(callList) > 2){
              if(is.call(callList[[3]])){
                do.call("replaceJSOperators", list(callList[[3]]))
              } else if(is.character(callList[[3]])){
                paste0("\"", callList[[3]], "\"")
              } else if(is.name(callList[[3]])){
                # same as fetchedObject1 above
                fetchedObject2 <- dynGet(as.character(callList[[3]]), ifnotfound=get(as.character(callList[[3]])))
                do.call(call, list(fetchedObject2))
              } else {
                do.call(call, list(callList[[3]]))
              }
            } else {}
          )
          return(paste0(unlist(result), collapse=""))
        } else {
          # replace object names with the actual objects for evaluation
          if(length(thisItem) > 1){
            for (itemParts in 2:length(thisItem)){
              if(is.name(thisItem[[itemParts]])){
                thisItem[[itemParts]] <- dynGet(as.character(thisItem[[itemParts]]), ifnotfound=get(as.character(thisItem[[itemParts]])))
              } else {}
            }
          } else {}
          thisItem <- eval(thisItem)
          # R vectors don't make much sense, collapse them for JS
          if(is.vector(thisItem)){
            thisItem <- paste0(thisItem, collapse=", ")
          } else {}
          return(thisItem)
        }
      } else {
        return(thisItem)
      }
    }
  )
  return(unlist(dotlist))
} ## end function replaceJSOperators


## function uncurl()
# used by js() to fetch calls from then/else segments of if conditions,
# omitting curly brackets that would get in the way with ite()
uncurl <- function(cond, level=1, indent.by=rk.get.indent()){
  if(!is.null(cond)){
    cond.list <- as.list(cond)
    # first check for the bracket
    if(identical(as.character(cond[[1]]), "{")){
      # now make sure the bracket isn't empty
      if(length(cond) > 1){
        cond <- paste0(
          sapply(
            2:length(cond.list),
            function(this.cond.num){
              do.call("js", args=list(cond[[this.cond.num]], level=level, by=indent.by, linebreaks=FALSE))
            }
          ),
          collapse=paste0("\n", indent(level=level, by=indent.by))
        )
      } else {
        cond <- ""
      }
    } else {
      cond <- do.call("js", args=list(cond, level=level, by=indent.by, linebreaks=FALSE))
    }
  } else {}
  return(cond)
} ## end function uncurl()


## function replaceJSIf
replaceJSIf <- function(cond, level=1, paste=TRUE, indent.by=rk.get.indent(), empty.e=rk.get.empty.e()){
  if(inherits(cond, "if")){
    # if condition -- should be save to give to js()
    cond.if   <- do.call(
      "js",
      args=list(
        cond[[2]],
        level=level,
        indent.by=indent.by,
        linebreaks=FALSE,
        empty.e=empty.e
      )
    )
    # then do -- could be nested with another if condition
    if(inherits(cond[[3]], "if")){
      cond.then <- replaceJSIf(cond[[3]], level=level+1, paste=FALSE, indent.by=indent.by, empty.e=empty.e)
    } else {
      cond.then <- do.call(
        "js",
        args=list(
          uncurl(cond[[3]], level=level+1, indent.by=indent.by),
          level=level,
          indent.by=indent.by,
          linebreaks=FALSE,
          empty.e=empty.e
        )
      )
    }
    # else do -- could be missing or yet another if condition
    cond.else <- NULL
    if(length(as.list(cond)) > 3){
      if(inherits(cond[[4]], "if")){
        cond.else <- replaceJSIf(cond[[4]], level=level+1, paste=FALSE, indent.by=indent.by, empty.e=empty.e)
      } else {
        cond.else <- do.call(
          "js",
          args=list(
            uncurl(cond[[4]], level=level+1, indent.by=indent.by),
            level=level,
            indent.by=indent.by,
            linebreaks=FALSE,
            empty.e=empty.e
          )
        )
      }
    } else {}

    iteObject <- ite(
      ifjs=cond.if,
      thenjs=cond.then,
      elsejs=cond.else 
    )
    if(isTRUE(paste)){
      # the pasted result needs to be trimmed, because js() adds indentation itself
      return(trim(rk.paste.JS(iteObject, level=level, indent.by=indent.by, empty.e=empty.e)))
    } else {
      return(iteObject)
    }
  } else {
    cond <- do.call(
      "js",
      args=list(cond, level=level, indent.by=indent.by, linebreaks=FALSE, empty.e=empty.e)
    )
    return(cond)
  }
} ## end function replaceJSIf


## function replaceJSFor
# this function is currently not publicly announced, but is available through the js() function
# 
#<documentation> 
# Using \code{for} loops is a bit more delicate, as they are very differently constructed in JavaScript. As
# a workaround, \code{js} will define an array and a counter variable with randomly generated names, fill
# the array with the values you provided and iterate through the array. In order to keep the iterator variable
# name you used in the original R loop, so you can use it inside the loop body, you will have to define it before
# the \code{js} call with a substitution of itself (see examples). Otherwise, you will get an "object not found" error.
#
# example:
# # let's try preserving a for loop
# # to use iterator variable i, we must initialize it first
# i <- substitute(i)  # DON'T FORGET THIS!
# cat(rk.paste.JS(js(
#   for (i in 1:10) {
#     echo(i)
#   }
# )))
#</documentation> 
replaceJSFor <- function(loop, level=1, indent.by=rk.get.indent()){
  if(inherits(loop, "for")){
    # for loops must be handled differently, we need to create an array
    # first and then interate through the array to imitate ho R does this
    # 
    # also, the array and iterator variables must not be named like any
    # of the given variables/objects. therefore, we will use some randomly
    # generated character strings for those
    arrayName <- paste0("a", paste0(sample(c(letters,LETTERS,0:9), 5, replace=TRUE), collapse=""))
    iterName <- paste0("i", paste0(sample(c(letters,LETTERS,0:9), 5, replace=TRUE), collapse=""))
    loop <- paste(
      paste0(indent(level=level, by=indent.by), "// the variable names \"", arrayName, "\" and \"", iterName, "\" were randomly generated"),
      paste0("var ", arrayName, " = new Array();"),
      paste0(arrayName, ".push(", do.call("js", args=list(loop[[3]], level=level, indent.by=indent.by, linebreaks=FALSE)), ");"),
      paste0("for (var ", as.character(loop[[2]]), "=", arrayName, "[0], ", iterName, "=0; ",
        iterName, " < ", arrayName, ".length; ",
        iterName, "++, ", as.character(loop[[2]]), "=", arrayName, "[", iterName, "]) {"),
      paste0(
        indent(level=level, by=indent.by),
        do.call(
          "js",
          args=list(
            uncurl(loop[[4]], level=level+1, indent.by=indent.by),
            level=level,
            indent.by=indent.by,
            linebreaks=FALSE
          )
        )
      ),
      "}\n",
      sep=paste0("\n", indent(level=level, by=indent.by))
    )
  } else {
    loop <- do.call("js", args=list(loop, level=level, indent.by=indent.by, linebreaks=FALSE))
    return(loop)
  }
} ## end function replaceJSFor


## function writeRequire()
# this function is called by rk.JS.doc()
# to toggle previews and load.silencer, solve the code generation once
writeRequire <- function(requirement, needPreview=FALSE, suppress=FALSE, level=2, indent.by=rk.get.indent()){
  if(isTRUE(suppress)){
    start_is_preview <- "suppressMessages(base::require("
    end_is_preview <- ")))"
    start_no_preview <- "suppressMessages(require("
    end_no_preview <- "))"
  } else {
    start_is_preview <- "base::require("
    end_is_preview <- "))"
    start_no_preview <- "require("
    end_no_preview <- ")"
  }
  if(isTRUE(needPreview)){
    result <- rk.paste.JS(
      ite("is_preview",
        echo(
          id("if(!", start_is_preview, requirement, end_is_preview, "{stop("),
          i18n(id("Preview not available, because package ", requirement, " is not installed or cannot be loaded.")),
          ")}\n"
        ),
        echo(id(start_no_preview, requirement, end_no_preview, "\n"))
      ),
      level=level, indent.by=indent.by
    )
  } else {
    result <- rk.paste.JS(echo(id(start_no_preview, requirement, end_no_preview, "\n")), level=level, indent.by=indent.by)
  }
  return(result)
} ## end function writeRequire()
rkward-community/rkwarddev documentation built on May 9, 2022, 3:02 p.m.