nullFun = function(...) TRUE
setClassUnion("characterOrNULL", c("character","NULL"))
setOldClass("ggplot")
#I'm envisioning each ReportHandlers set representing a distinct target (eg internal DOM, write to pipe, etc)
#Currently args expects a named list of named lists, up to one for each function, eg list(finish = list(file = "myfile.html"))
#we might want to make this an S4 class later?
setClass("ReportHandlers",
representation = list(init = "function",
addElement = "function",
removeElement = "function",
finish = "function",
location = "ANY",
args = "ANY"),
prototype = list(init = nullFun,
addElement = nullFun,
removeElement = nullFun,
finish= nullFun,
location = NULL,
args = NULL)
)
baseReport <- setRefClass("BaseReportRef",
fields = list(
.shortName = "character",
shortName = function(val)
{
if(missing(val))
.shortName
else
.self$.shortName <- val
},
.title = "character",
title = function(val)
{
if(missing(val))
.title
else
{
.self$.title <- val
#if we have an HTML document, change/add the title node
if(is(.reportDOM, "XMLInternalDocument"))
{
tnode = getNodeSet(.reportDOM, "/html/head/title")
if(length(tnode))
xmlValue(tnode[[1]]) = val
else
{
head = getNodeSet(.reportDOM, "/html/head")[[1]]
addChildren(head, newXMLNode("title", val))}
}
.title
}
},
.reportDirectory = "character",
reportDirectory = function(val)
{
if(missing(val))
.reportDirectory
else
#.self$.reportDirectory <- val
.reportDirectory <<- val
},
.report = "list",
.reportDOM = "ANY",
.basePath = "characterOrNULL",
basePath = function(val)
{
if(missing(val))
.basePath
else
.self$.basePath <- val
},
.handlers = "list",
handlers = function(val)
{
if(missing(val))
.handlers
else
{
#stop("The addHandlers/removeHandlers methods must be used to manipulate event handlers.")
if(!is.list(val))
val = list(val)
.self$.handlers <- val
}
}),
methods = list(
addHandlers = function(handlers, init = nullFun, addElement = nullFun,
removeElement = nullFun, finalize = nullFun)
{
if(missing(handlers) | is.null(handlers))
handlers2 = new("ReportHandlers", init = init, addElement = addElement,
removeElement = removeElement, finalize = finalize)
else
handlers2 = handlers
.self$.handlers <- c(.handlers, handlers2)
},
removeHandlers = function(pos = 1)
{
if (!(pos %in% seq(along = .handlers)))
stop(sprintf("There is no set of handlers at position %d to remove.", pos))
.self$.handlers <- .handlers[-pos]
}
)
)
htmlReport <- setRefClass("HTMLReportRef", contains = "BaseReportRef",
fields = list(
.toHTML = "list",
.toDF = "list",
.modifyDF = "list"),
methods = list(
prepare = function(obj,.toHTML = NULL, .toDF = NULL, .modifyDF = NULL, ... )
{
#if the user has overridden the html conversion for this class, we use that
#we grab the first class if there are more than one (ie with XMLInternalNode)
klass = class(obj)[1]
f = if(missing(.toHTML) || is.null(.toHTML)) .self$.toHTML[[klass]] else .toHTML
if(is.function(f))
{
ret = f(obj, ...)
}
else
{
if(missing(.toDF) || is.null(.toDF))
.toDF2 = .self$.toDF[[klass]]
else
.toDF2 = .toDF
if(missing(.modifyDF) || is.null(.modifyDF))
.modifyDF2 = .self$.modifyDF[[klass]]
else
.modifyDF2 = .modifyDF
html = objectToHTML(obj, .self, ..., .toDF=.toDF2, .modifyDF = .modifyDF2 )
#prepping for conversion from text HTML nubuilding to XML construction Once the
#conversion is complete objectToHTML methods will be returning XMLInternalNode
#objects
#this is a bit hacky but oh well!! We may be getting a list with elements "html"
#and "object"
if(is(html, "XMLNodeSet"))
{
htmlcode <- html
} else if(is.list(html))
{
htmlcode = html$html
obj = html$object
}
else
{
htmlcode = html
obj = NULL
}
if(is.character(htmlcode))
{
htmlcode = htmlParse(htmlcode)
ret = getNodeSet(htmlcode, "//body/*")
} else {
ret = htmlcode
}
}
list(html = ret, object = obj)
},
finish = function()
{
sapply(.handlers, function(fs) fs@finish(.self, fs@args$finish))
# do we want to force a saveXML call here, or just assign one as a finalize
# event handler by default? For now we make people assign a handler,
# because sometimes we only want to send the content down a connection and
# not write a file.
},
addElement = function(name, value, .toHTML = NULL, .toDF = NULL,
.modifyDF = NULL, pos = NA, ... )
{
if(missing(name))
name = paste("id", length(getNodeSet(.self$.reportDOM,"//body/div")) + 1, sep="")
if(is.character(name))
nodes = getNodeSet(.self$.reportDOM, sprintf("//div[@id='%s']", name))
else if (is.numeric(name))
stop("positional insertion is not yet supported")
if(length(nodes))
{
if(!is.na(pos))
stop("Attempt to specify a position (pos) when replacing an existing element")
node = nodes[[1]]
#remove whatever was assigned to this name previously
removeChildren(node, kids = xmlChildren(node))
} else {
#create new div with the specified id and add it to the body of the HTML page
node = newXMLNode("div", attrs= list(id=name, class = "ReportingTools container"))
if(is.na(pos))
{
#No position means it gets added to the end of the document
body = getNodeSet(.self$.reportDOM, "//body")[[1]]
addChildren(body, node)
} else {
#If we are given a position n, find the nth report element in the current report and insert our new element directly ahead of it.
addSibling(.self$.report[[pos]], after=FALSE, node)
}
}
#turn value into html nodes to add to DOM
newcontent = .self$prepare(value, .toHTML = .toHTML, .toDF = .toDF,
.modifyDF = .modifyDF,... )
obj = newcontent$object
newcontent=newcontent$html
if(is.list(newcontent))
addChildren(node, kids = newcontent)
else
addChildren(node, newcontent)
# call all currently assigned addElement handlers with the node for the div
# containing the new content
sapply(.self$.handlers, function(fs, node, name)
fs@addElement(node, name, fs@args$addElement), node= node, name=name)
if(is.na(pos))
.self$.report[[name]] = node
else
{
#insert our new element in the right place and shift everything else around.
oldlist = .self$.report
oldnames = names(oldlist)
before = which(seq(along=oldlist) < pos)
after = which(seq(along=oldlist) >= pos)
newlist = vector("list", length(oldlist) + 1)
newlist[before] = oldlist[before]
newlist[[pos]] = node
newlist[after + 1] = oldlist[after]
names(newlist ) = c(oldnames[before], name, oldnames[after])
.self$.report = newlist
}
invisible(obj)
},
initialize = function(...)
{
args = list(...)
handlers2 = args$handlers
if(is.null(handlers))
handlers2 = list(toFileHandlers)
#if we only have one set of handler sometimes it won't come in the form of a list
if(!is.list(handlers2))
handlers2 = list(handlers2)
dom = startHTMLReport(...)
.self$.reportDOM = dom
sapply(handlers2, function(h) h@init(dom, h@args$init))
.self$initFields(shortName = args$shortName, title = args$title,
reportDirectory = args$reportDirectory, handlers = handlers2,
basePath = args$basePath)
}
)
)
HTMLReport <- function(shortName = "coolProject",
title = shortName,
reportDirectory = ".",
basePath = NULL,
handlers = list(fileHandlers(makeReportPath(basePath, reportDirectory, shortName))),
.toHTML = list(),
.toDF = list(),
.modifyDF= list(),
link.css = NULL,
link.javascript=NULL,
overwrite.js = TRUE,
...
)
{
shortName <- sub(".html$", "", shortName)
if(is.null(title))
title <- shortName
if(substr(reportDirectory,1,1) == "~"){
reportDirectory <- path.expand(reportDirectory)
}
if(!is.list(handlers))
handlers = list(handlers)
allargs = list(shortName = shortName, basePath = basePath, reportDirectory = reportDirectory, ...)
handlers = lapply(handlers, function(hnd)
{
if(is(hnd, "ReportHandlers"))
hnd
else if (is(hnd, "function"))
do.call(hnd, allargs)
else
stop("handlers must be a list containing only ReportHandler objects and/or functions which return such objects.")
})
if(substr(reportDirectory, 1, 1) == "/")
stop("Non-NULL baseDirectory in combination with absolute reportDirectory is not supported.")
if(sum(grepl("[A-Za-z]:", reportDirectory)) > 0 & .Platform$OS.type == "windows")
stop("Non-NULL baseDirectory in combination with absolute reportDirectory is not supported.")
htmlReport$new(title = title, shortName = shortName,
reportDirectory = reportDirectory, handlers = handlers,
basePath = basePath, .toHTML = .toHTML,
.toDF = .toDF, .modifyDF = .modifyDF, link.css= link.css,
link.javascript = link.javascript, ovewrite.js = overwrite.js)
}
### See AllOldClasses.R for old S4 based Report classes.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.