openxlsx/0000755000176200001440000000000014156124026012133 5ustar liggesusersopenxlsx/NAMESPACE0000644000176200001440000000507514155631556013373 0ustar liggesusers# Generated by roxygen2: do not edit by hand S3method("names<-",Workbook) S3method(getNamedRegions,Workbook) S3method(getNamedRegions,default) S3method(names,Workbook) S3method(read.xlsx,Workbook) S3method(read.xlsx,default) export("activeSheet<-") export("sheetVisibility<-") export("sheetVisible<-") export("worksheetOrder<-") export(activeSheet) export(addCreator) export(addFilter) export(addStyle) export(addWorksheet) export(buildWorkbook) export(cloneWorksheet) export(col2int) export(conditionalFormat) export(conditionalFormatting) export(convertFromExcelRef) export(convertToDate) export(convertToDateTime) export(copyWorkbook) export(createComment) export(createNamedRegion) export(createStyle) export(createWorkbook) export(dataValidation) export(deleteData) export(deleteNamedRegion) export(freezePane) export(getBaseFont) export(getCellRefs) export(getCreators) export(getDateOrigin) export(getNamedRegions) export(getSheetNames) export(getStyles) export(getTables) export(groupColumns) export(groupRows) export(insertImage) export(insertPlot) export(int2col) export(loadWorkbook) export(makeHyperlinkString) export(mergeCells) export(modifyBaseFont) export(op.openxlsx) export(openXL) export(openxlsx_getOp) export(openxlsx_setOp) export(pageBreak) export(pageSetup) export(protectWorkbook) export(protectWorksheet) export(read.xlsx) export(readWorkbook) export(removeCellMerge) export(removeColWidths) export(removeComment) export(removeFilter) export(removeRowHeights) export(removeTable) export(removeWorksheet) export(renameWorksheet) export(replaceStyle) export(saveWorkbook) export(setColWidths) export(setFooter) export(setHeader) export(setHeaderFooter) export(setLastModifiedBy) export(setRowHeights) export(sheetVisibility) export(sheetVisible) export(sheets) export(showGridLines) export(temp_xlsx) export(ungroupColumns) export(ungroupRows) export(worksheetOrder) export(write.xlsx) export(writeComment) export(writeData) export(writeDataTable) export(writeFormula) import(Rcpp) import(methods) import(stringi) importFrom(grDevices,bmp) importFrom(grDevices,col2rgb) importFrom(grDevices,colours) importFrom(grDevices,dev.copy) importFrom(grDevices,dev.list) importFrom(grDevices,dev.off) importFrom(grDevices,jpeg) importFrom(grDevices,png) importFrom(grDevices,rgb) importFrom(grDevices,tiff) importFrom(stats,pchisq) importFrom(utils,download.file) importFrom(utils,head) importFrom(utils,menu) importFrom(utils,unzip) importFrom(zip,zipr) useDynLib(openxlsx, .registration=TRUE) openxlsx/LICENSE0000644000176200001440000000006514155600363013143 0ustar liggesusersYEAR: 2014-2021 COPYRIGHT HOLDER: openxlsx authors openxlsx/README.md0000644000176200001440000000312314155600363013413 0ustar liggesusers[openxlsx](https://ycphs.github.io/openxlsx/) ======== [![codecov](https://codecov.io/gh/ycphs/openxlsx/branch/master/graph/badge.svg)](https://app.codecov.io/gh/ycphs/openxlsx) [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/openxlsx)](https://cran.r-project.org/package=openxlsx) [![CRAN RStudio mirror downloads](https://cranlogs.r-pkg.org/badges/openxlsx)](https://cran.r-project.org/package=openxlsx) ![R-CMD-check](https://github.com/ycphs/openxlsx/workflows/R-CMD-check/badge.svg?branch=master) This [R](https://www.R-project.org/) package simplifies the creation of `.xlsx` files by providing a high level interface to writing, styling and editing worksheets. Through the use of [`Rcpp`](https://CRAN.R-project.org/package=Rcpp), read/write times are comparable to the [`xlsx`](https://CRAN.R-project.org/package=xlsx) and [`XLConnect`](https://CRAN.R-project.org/package=XLConnect) packages with the added benefit of removing the dependency on Java. ## Installation ### Stable version Current stable version is available on [CRAN](https://CRAN.R-project.org/) via ```R install.packages("openxlsx", dependencies = TRUE) ``` ### Development version ```R install.packages(c("Rcpp", "remotes"), dependencies = TRUE) remotes::install_github("ycphs/openxlsx") ``` ## Bug/feature request Please let me know which version of openxlsx you are using when posting bug reports. ```R packageVersion("openxlsx") ``` ## News [Here](https://raw.githubusercontent.com/ycphs/openxlsx/master/NEWS.md). openxlsx/man/0000755000176200001440000000000014155633372012716 5ustar liggesusersopenxlsx/man/protectWorkbook.Rd0000644000176200001440000000272314155600363016401 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{protectWorkbook} \alias{protectWorkbook} \title{Protect a workbook from modifications} \usage{ protectWorkbook( wb, protect = TRUE, password = NULL, lockStructure = FALSE, lockWindows = FALSE, type = 1L ) } \arguments{ \item{wb}{A workbook object} \item{protect}{Whether to protect or unprotect the sheet (default=TRUE)} \item{password}{(optional) password required to unprotect the workbook} \item{lockStructure}{Whether the workbook structure should be locked} \item{lockWindows}{Whether the window position of the spreadsheet should be locked} \item{type}{Lock type, default 1. From the xml documentation: 1 - Document is password protected. 2 - Document is recommended to be opened as read-only. 4 - Document is enforced to be opened as read-only. 8 - Document is locked for annotation.} } \description{ Protect or unprotect a workbook from modifications by the user in the graphical user interface. Replaces an existing protection. } \examples{ wb <- createWorkbook() addWorksheet(wb, "S1") protectWorkbook(wb, protect = TRUE, password = "Password", lockStructure = TRUE) \dontrun{ saveWorkbook(wb, "WorkBook_Protection.xlsx", overwrite = TRUE) } # Remove the protection protectWorkbook(wb, protect = FALSE) \dontrun{ saveWorkbook(wb, "WorkBook_Protection_unprotected.xlsx", overwrite = TRUE) } } \author{ Reinhold Kainhofer } openxlsx/man/replaceStyle.Rd0000644000176200001440000000171614155600364015641 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{replaceStyle} \alias{replaceStyle} \title{Replace an existing cell style} \usage{ replaceStyle(wb, index, newStyle) } \arguments{ \item{wb}{A workbook object} \item{index}{Index of style object to replace} \item{newStyle}{A style to replace the existing style as position index} } \description{ Replace an existing cell style Replace a style object } \examples{ ## load a workbook wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) ## create a new style and replace style 2 newStyle <- createStyle(fgFill = "#00FF00") ## replace style 2 getStyles(wb)[1:3] ## prints styles replaceStyle(wb, 2, newStyle = newStyle) ## Save workbook \dontrun{ saveWorkbook(wb, "replaceStyleExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=getStyles]{getStyles()}} } \author{ Alexander Walker } openxlsx/man/writeFormula.Rd0000644000176200001440000000550214155600364015662 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/writeData.R \name{writeFormula} \alias{writeFormula} \title{Write a character vector as an Excel Formula} \usage{ writeFormula( wb, sheet, x, startCol = 1, startRow = 1, array = FALSE, xy = NULL ) } \arguments{ \item{wb}{A Workbook object containing a worksheet.} \item{sheet}{The worksheet to write to. Can be the worksheet index or name.} \item{x}{A character vector.} \item{startCol}{A vector specifying the starting column to write to.} \item{startRow}{A vector specifying the starting row to write to.} \item{array}{A bool if the function written is of type array} \item{xy}{An alternative to specifying \code{startCol} and \code{startRow} individually. A vector of the form \code{c(startCol, startRow)}.} } \description{ Write a a character vector containing Excel formula to a worksheet. } \details{ Currently only the english version of functions are supported. Please don't use the local translation. The examples below show a small list of possible formulas: \itemize{ \item{SUM(B2:B4)} \item{AVERAGE(B2:B4)} \item{MIN(B2:B4)} \item{MAX(B2:B4)} \item{...} } } \examples{ ## There are 3 ways to write a formula wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, "Sheet 1", x = iris) ## SEE int2col() to convert int to Excel column label ## 1. - As a character vector using writeFormula v <- c("SUM(A2:A151)", "AVERAGE(B2:B151)") ## skip header row writeFormula(wb, sheet = 1, x = v, startCol = 10, startRow = 2) writeFormula(wb, 1, x = "A2 + B2", startCol = 10, startRow = 10) ## 2. - As a data.frame column with class "formula" using writeData df <- data.frame( x = 1:3, y = 1:3, z = paste(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = " + "), z2 = sprintf("ADDRESS(1,\%s)", 1:3), stringsAsFactors = FALSE ) class(df$z) <- c(class(df$z), "formula") class(df$z2) <- c(class(df$z2), "formula") addWorksheet(wb, "Sheet 2") writeData(wb, sheet = 2, x = df) ## 3. - As a vector with class "formula" using writeData v2 <- c("SUM(A2:A4)", "AVERAGE(B2:B4)", "MEDIAN(C2:C4)") class(v2) <- c(class(v2), "formula") writeData(wb, sheet = 2, x = v2, startCol = 10, startRow = 2) ## Save workbook \dontrun{ saveWorkbook(wb, "writeFormulaExample.xlsx", overwrite = TRUE) } ## 4. - Writing internal hyperlinks wb <- createWorkbook() addWorksheet(wb, "Sheet1") addWorksheet(wb, "Sheet2") writeFormula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")') ## Save workbook \dontrun{ saveWorkbook(wb, "writeFormulaHyperlinkExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=writeData]{writeData()}} \code{\link[=makeHyperlinkString]{makeHyperlinkString()}} } \author{ Alexander Walker } openxlsx/man/getStyles.Rd0000644000176200001440000000101714155600363015161 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getStyles} \alias{getStyles} \title{Returns a list of all styles in the workbook} \usage{ getStyles(wb) } \arguments{ \item{wb}{A workbook object} } \description{ Returns list of style objects in the workbook } \examples{ ## load a workbook wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) getStyles(wb)[1:3] } \seealso{ \code{\link[=replaceStyle]{replaceStyle()}} } openxlsx/man/activeSheet.Rd0000644000176200001440000000152314155600363015444 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{activeSheet} \alias{activeSheet} \alias{activeSheet<-} \title{Get/set active sheet of the workbook} \usage{ activeSheet(wb) activeSheet(wb) <- value } \arguments{ \item{wb}{A workbook object} \item{value}{index of the active sheet or name of the active sheet} } \value{ return the active sheet of the workbook } \description{ Get and set active sheet of the workbook } \examples{ wb <- createWorkbook() addWorksheet(wb, sheetName = "S1") addWorksheet(wb, sheetName = "S2") addWorksheet(wb, sheetName = "S3") activeSheet(wb) # default value is the first sheet active activeSheet(wb) <- 1 ## active sheet S1 activeSheet(wb) activeSheet(wb) <- "S2" ## active sheet S2 activeSheet(wb) } \author{ Philipp Schauberger } openxlsx/man/addFilter.Rd0000644000176200001440000000216714155600363015103 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{addFilter} \alias{addFilter} \title{Add column filters} \usage{ addFilter(wb, sheet, rows, cols) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{rows}{A row number.} \item{cols}{columns to add filter to.} } \description{ Add excel column filters to a worksheet } \details{ adds filters to worksheet columns, same as filter parameters in writeData. writeDataTable automatically adds filters to first row of a table. NOTE Can only have a single filter per worksheet unless using tables. } \examples{ wb <- createWorkbook() addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") addWorksheet(wb, "Sheet 3") writeData(wb, 1, iris) addFilter(wb, 1, row = 1, cols = 1:ncol(iris)) ## Equivalently writeData(wb, 2, x = iris, withFilter = TRUE) ## Similarly writeDataTable(wb, 3, iris) \dontrun{ saveWorkbook(wb, file = "addFilterExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=writeData]{writeData()}} \code{\link[=addFilter]{addFilter()}} } openxlsx/man/protectWorksheet.Rd0000644000176200001440000000507714155600363016564 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{protectWorksheet} \alias{protectWorksheet} \title{Protect a worksheet from modifications} \usage{ protectWorksheet( wb, sheet, protect = TRUE, password = NULL, lockSelectingLockedCells = NULL, lockSelectingUnlockedCells = NULL, lockFormattingCells = NULL, lockFormattingColumns = NULL, lockFormattingRows = NULL, lockInsertingColumns = NULL, lockInsertingRows = NULL, lockInsertingHyperlinks = NULL, lockDeletingColumns = NULL, lockDeletingRows = NULL, lockSorting = NULL, lockAutoFilter = NULL, lockPivotTables = NULL, lockObjects = NULL, lockScenarios = NULL ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{protect}{Whether to protect or unprotect the sheet (default=TRUE)} \item{password}{(optional) password required to unprotect the worksheet} \item{lockSelectingLockedCells}{Whether selecting locked cells is locked} \item{lockSelectingUnlockedCells}{Whether selecting unlocked cells is locked} \item{lockFormattingCells}{Whether formatting cells is locked} \item{lockFormattingColumns}{Whether formatting columns is locked} \item{lockFormattingRows}{Whether formatting rows is locked} \item{lockInsertingColumns}{Whether inserting columns is locked} \item{lockInsertingRows}{Whether inserting rows is locked} \item{lockInsertingHyperlinks}{Whether inserting hyperlinks is locked} \item{lockDeletingColumns}{Whether deleting columns is locked} \item{lockDeletingRows}{Whether deleting rows is locked} \item{lockSorting}{Whether sorting is locked} \item{lockAutoFilter}{Whether auto-filter is locked} \item{lockPivotTables}{Whether pivot tables are locked} \item{lockObjects}{Whether objects are locked} \item{lockScenarios}{Whether scenarios are locked} } \description{ Protect or unprotect a worksheet from modifications by the user in the graphical user interface. Replaces an existing protection. } \examples{ wb <- createWorkbook() addWorksheet(wb, "S1") writeDataTable(wb, 1, x = iris[1:30, ]) # Formatting cells / columns is allowed , but inserting / deleting columns is protected: protectWorksheet(wb, "S1", protect = TRUE, lockFormattingCells = FALSE, lockFormattingColumns = FALSE, lockInsertingColumns = TRUE, lockDeletingColumns = TRUE ) # Remove the protection protectWorksheet(wb, "S1", protect = FALSE) \dontrun{ saveWorkbook(wb, "pageSetupExample.xlsx", overwrite = TRUE) } } \author{ Reinhold Kainhofer } openxlsx/man/saveWorkbook.Rd0000644000176200001440000000230514155600364015654 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{saveWorkbook} \alias{saveWorkbook} \title{save Workbook to file} \usage{ saveWorkbook(wb, file, overwrite = FALSE, returnValue = FALSE) } \arguments{ \item{wb}{A Workbook object to write to file} \item{file}{A character string naming an xlsx file} \item{overwrite}{If \code{TRUE}, overwrite any existing file.} \item{returnValue}{If \code{TRUE}, returns \code{TRUE} in case of a success, else \code{FALSE}. If flag is \code{FALSE}, then no return value is returned.} } \description{ save a Workbook object to file } \examples{ ## Create a new workbook and add a worksheet wb <- createWorkbook("Creator of workbook") addWorksheet(wb, sheetName = "My first worksheet") ## Save workbook to working directory \dontrun{ saveWorkbook(wb, file = "saveWorkbookExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=createWorkbook]{createWorkbook()}} \code{\link[=addWorksheet]{addWorksheet()}} \code{\link[=loadWorkbook]{loadWorkbook()}} \code{\link[=writeData]{writeData()}} \code{\link[=writeDataTable]{writeDataTable()}} } \author{ Alexander Walker, Philipp Schauberger } openxlsx/man/names.Rd0000644000176200001440000000117714155600363014310 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{names} \alias{names} \alias{names.Workbook} \alias{names<-.Workbook} \title{get or set worksheet names} \usage{ \method{names}{Workbook}(x) \method{names}{Workbook}(x) <- value } \arguments{ \item{x}{A \code{Workbook} object} \item{value}{a character vector the same length as wb} } \description{ get or set worksheet names } \examples{ wb <- createWorkbook() addWorksheet(wb, "S1") addWorksheet(wb, "S2") addWorksheet(wb, "S3") names(wb) names(wb)[[2]] <- "S2a" names(wb) names(wb) <- paste("Sheet", 1:3) } openxlsx/man/createWorkbook.Rd0000644000176200001440000000221214155600363016155 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{createWorkbook} \alias{createWorkbook} \title{Create a new Workbook object} \usage{ createWorkbook( creator = ifelse(.Platform$OS.type == "windows", Sys.getenv("USERNAME"), Sys.getenv("USER")), title = NULL, subject = NULL, category = NULL ) } \arguments{ \item{creator}{Creator of the workbook (your name). Defaults to login username} \item{title}{Workbook properties title} \item{subject}{Workbook properties subject} \item{category}{Workbook properties category} } \value{ Workbook object } \description{ Create a new Workbook object } \examples{ ## Create a new workbook wb <- createWorkbook() ## Save workbook to working directory \dontrun{ saveWorkbook(wb, file = "createWorkbookExample.xlsx", overwrite = TRUE) } ## Set Workbook properties wb <- createWorkbook( creator = "Me", title = "title here", subject = "this & that", category = "something" ) } \seealso{ \code{\link[=loadWorkbook]{loadWorkbook()}} \code{\link[=saveWorkbook]{saveWorkbook()}} } \author{ Alexander Walker } openxlsx/man/worksheetOrder.Rd0000644000176200001440000000266414155600364016217 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{worksheetOrder} \alias{worksheetOrder} \alias{worksheetOrder<-} \title{Order of worksheets in xlsx file} \usage{ worksheetOrder(wb) worksheetOrder(wb) <- value } \arguments{ \item{wb}{A workbook object} \item{value}{Vector specifying order to write worksheets to file} } \description{ Get/set order of worksheets in a Workbook object } \details{ This function does not reorder the worksheets within the workbook object, it simply shuffles the order when writing to file. } \examples{ ## setup a workbook with 3 worksheets wb <- createWorkbook() addWorksheet(wb = wb, sheetName = "Sheet 1", gridLines = FALSE) writeDataTable(wb = wb, sheet = 1, x = iris) addWorksheet(wb = wb, sheetName = "mtcars (Sheet 2)", gridLines = FALSE) writeData(wb = wb, sheet = 2, x = mtcars) addWorksheet(wb = wb, sheetName = "Sheet 3", gridLines = FALSE) writeData(wb = wb, sheet = 3, x = Formaldehyde) worksheetOrder(wb) names(wb) worksheetOrder(wb) <- c(1, 3, 2) # switch position of sheets 2 & 3 writeData(wb, 2, 'This is still the "mtcars" worksheet', startCol = 15) worksheetOrder(wb) names(wb) ## ordering within workbook is not changed \dontrun{ saveWorkbook(wb, "worksheetOrderExample.xlsx", overwrite = TRUE) } worksheetOrder(wb) <- c(3, 2, 1) \dontrun{ saveWorkbook(wb, "worksheetOrderExample2.xlsx", overwrite = TRUE) } } openxlsx/man/groupColumns.Rd0000644000176200001440000000153714155600363015702 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{groupColumns} \alias{groupColumns} \title{Group columns} \usage{ groupColumns(wb, sheet, cols, hidden = FALSE) } \arguments{ \item{wb}{A workbook object.} \item{sheet}{A name or index of a worksheet.} \item{cols}{Indices of cols to group.} \item{hidden}{Logical vector. If TRUE the grouped columns are hidden. Defaults to FALSE.} } \description{ Group a selection of columns } \details{ Group columns together, with the option to hide them. NOTE: \code{\link[=setColWidths]{setColWidths()}} has a conflicting \code{hidden} parameter; changing one will update the other. } \seealso{ \code{\link[=ungroupColumns]{ungroupColumns()}} to ungroup columns. \code{\link[=groupRows]{groupRows()}} for grouping rows. } \author{ Joshua Sturm } openxlsx/man/cloneWorksheet.Rd0000644000176200001440000000146714155600363016203 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{cloneWorksheet} \alias{cloneWorksheet} \title{Clone a worksheet to a workbook} \usage{ cloneWorksheet(wb, sheetName, clonedSheet) } \arguments{ \item{wb}{A Workbook object to attach the new worksheet} \item{sheetName}{A name for the new worksheet} \item{clonedSheet}{The name of the existing worksheet to be cloned.} } \value{ XML tree } \description{ Clone a worksheet to a Workbook object } \examples{ ## Create a new workbook wb <- createWorkbook("Fred") ## Add 3 worksheets addWorksheet(wb, "Sheet 1") cloneWorksheet(wb, "Sheet 2", clonedSheet = "Sheet 1") ## Save workbook \dontrun{ saveWorkbook(wb, "cloneWorksheetExample.xlsx", overwrite = TRUE) } } \author{ Reinhold Kainhofer } openxlsx/man/col2int.Rd0000644000176200001440000000051514155600363014552 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{col2int} \alias{col2int} \title{Convert Excel column to integer} \usage{ col2int(x) } \arguments{ \item{x}{A character vector} } \description{ Converts an Excel column label to an integer. } \examples{ col2int(LETTERS) } openxlsx/man/conditionalFormatting.Rd0000644000176200001440000002350614155600363017543 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/conditional_formatting.R \name{conditionalFormatting} \alias{conditionalFormatting} \alias{databar} \title{Add conditional formatting to cells} \usage{ conditionalFormatting( wb, sheet, cols, rows, rule = NULL, style = NULL, type = "expression", ... ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Columns to apply conditional formatting to} \item{rows}{Rows to apply conditional formatting to} \item{rule}{The condition under which to apply the formatting. See examples.} \item{style}{A style to apply to those cells that satisfy the rule. Default is createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE")} \item{type}{Either 'expression', 'colourScale', 'databar', 'duplicates', 'beginsWith', 'endsWith', 'topN', 'bottomN', 'contains' or 'notContains' (case insensitive).} \item{...}{See below} } \description{ Add conditional formatting to cells } \details{ See Examples. If type == "expression" \itemize{ \item{style is a Style object. See \code{\link[=createStyle]{createStyle()}}} \item{rule is an expression. Valid operators are "<", "<=", ">", ">=", "==", "!=".} } If type == "colourScale" \itemize{ \item{style is a vector of colours with length 2 or 3} \item{rule can be NULL or a vector of colours of equal length to styles} } If type == "databar" \itemize{ \item{style is a vector of colours with length 2 or 3} \item{rule is a numeric vector specifying the range of the databar colours. Must be equal length to style} \item{... \itemize{ \item{\strong{showvalue} If FALSE the cell value is hidden. Default TRUE.} \item{\strong{gradient} If FALSE colour gradient is removed. Default TRUE.} \item{\strong{border} If FALSE the border around the database is hidden. Default TRUE.} } } } If type == "duplicates" \itemize{ \item{style is a Style object. See \code{\link[=createStyle]{createStyle()}}} \item{rule is ignored.} } If type == "contains" \itemize{ \item{style is a Style object. See \code{\link[=createStyle]{createStyle()}}} \item{rule is the text to look for within cells} } If type == "between" \itemize{ \item{style is a Style object. See \code{\link[=createStyle]{createStyle()}}} \item{rule is a numeric vector of length 2 specifying lower and upper bound (Inclusive)} } If type == "topN" \itemize{ \item{style is a Style object. See \code{\link[=createStyle]{createStyle()}}} \item{rule is ignored} \item{... \itemize{ \item{\strong{rank} numeric vector of length 1 indicating number of highest values.} \item{\strong{percent} TRUE if you want top N percentage.} } } } If type == "bottomN" \itemize{ \item{style is a Style object. See \code{\link[=createStyle]{createStyle()}}} \item{rule is ignored} \item{... \itemize{ \item{\strong{rank} numeric vector of length 1 indicating number of lowest values.} \item{\strong{percent} TRUE if you want bottom N percentage.} } } } } \examples{ wb <- createWorkbook() addWorksheet(wb, "cellIs") addWorksheet(wb, "Moving Row") addWorksheet(wb, "Moving Col") addWorksheet(wb, "Dependent on") addWorksheet(wb, "Duplicates") addWorksheet(wb, "containsText") addWorksheet(wb, "notcontainsText") addWorksheet(wb, "beginsWith") addWorksheet(wb, "endsWith") addWorksheet(wb, "colourScale", zoom = 30) addWorksheet(wb, "databar") addWorksheet(wb, "between") addWorksheet(wb, "topN") addWorksheet(wb, "bottomN") addWorksheet(wb, "logical operators") negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE") ## rule applies to all each cell in range writeData(wb, "cellIs", -5:5) writeData(wb, "cellIs", LETTERS[1:11], startCol = 2) conditionalFormatting(wb, "cellIs", cols = 1, rows = 1:11, rule = "!=0", style = negStyle ) conditionalFormatting(wb, "cellIs", cols = 1, rows = 1:11, rule = "==0", style = posStyle ) ## highlight row dependent on first cell in row writeData(wb, "Moving Row", -5:5) writeData(wb, "Moving Row", LETTERS[1:11], startCol = 2) conditionalFormatting(wb, "Moving Row", cols = 1:2, rows = 1:11, rule = "$A1<0", style = negStyle ) conditionalFormatting(wb, "Moving Row", cols = 1:2, rows = 1:11, rule = "$A1>0", style = posStyle ) ## highlight column dependent on first cell in column writeData(wb, "Moving Col", -5:5) writeData(wb, "Moving Col", LETTERS[1:11], startCol = 2) conditionalFormatting(wb, "Moving Col", cols = 1:2, rows = 1:11, rule = "A$1<0", style = negStyle ) conditionalFormatting(wb, "Moving Col", cols = 1:2, rows = 1:11, rule = "A$1>0", style = posStyle ) ## highlight entire range cols X rows dependent only on cell A1 writeData(wb, "Dependent on", -5:5) writeData(wb, "Dependent on", LETTERS[1:11], startCol = 2) conditionalFormatting(wb, "Dependent on", cols = 1:2, rows = 1:11, rule = "$A$1<0", style = negStyle ) conditionalFormatting(wb, "Dependent on", cols = 1:2, rows = 1:11, rule = "$A$1>0", style = posStyle ) ## highlight cells in column 1 based on value in column 2 writeData(wb, "Dependent on", data.frame(x = 1:10, y = runif(10)), startRow = 15) conditionalFormatting(wb, "Dependent on", cols = 1, rows = 16:25, rule = "B16<0.5", style = negStyle ) conditionalFormatting(wb, "Dependent on", cols = 1, rows = 16:25, rule = "B16>=0.5", style = posStyle ) ## highlight duplicates using default style writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE)) conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates") ## cells containing text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "containsText", sapply(1:10, fn)) conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains", rule = "A") ## cells not containing text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "containsText", sapply(1:10, fn)) conditionalFormatting(wb, "notcontainsText", cols = 1, rows = 1:10, type = "notcontains", rule = "A") ## cells begins with text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "beginsWith", sapply(1:100, fn)) conditionalFormatting(wb, "beginsWith", cols = 1, rows = 1:100, type = "beginsWith", rule = "A") ## cells ends with text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "endsWith", sapply(1:100, fn)) conditionalFormatting(wb, "endsWith", cols = 1, rows = 1:100, type = "endsWith", rule = "A") ## colourscale colours cells based on cell value df <- read.xlsx(system.file("extdata", "readTest.xlsx", package = "openxlsx"), sheet = 4) writeData(wb, "colourScale", df, colNames = FALSE) ## write data.frame ## rule is a vector or colours of length 2 or 3 (any hex colour or any of colours()) ## If rule is NULL, min and max of cells is used. Rule must be the same length as style or NULL. conditionalFormatting(wb, "colourScale", cols = 1:ncol(df), rows = 1:nrow(df), style = c("black", "white"), rule = c(0, 255), type = "colourScale" ) setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07) setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5) ## Databars writeData(wb, "databar", -5:5) conditionalFormatting(wb, "databar", cols = 1, rows = 1:11, type = "databar") ## Default colours ## Between # Highlight cells in interval [-2, 2] writeData(wb, "between", -5:5) conditionalFormatting(wb, "between", cols = 1, rows = 1:11, type = "between", rule = c(-2, 2)) ## Top N writeData(wb, "topN", data.frame(x = 1:10, y = rnorm(10))) # Highlight top 5 values in column x conditionalFormatting(wb, "topN", cols = 1, rows = 2:11, style = posStyle, type = "topN", rank = 5)#' # Highlight top 20 percentage in column y conditionalFormatting(wb, "topN", cols = 2, rows = 2:11, style = posStyle, type = "topN", rank = 20, percent = TRUE) ## Bottom N writeData(wb, "bottomN", data.frame(x = 1:10, y = rnorm(10))) # Highlight bottom 5 values in column x conditionalFormatting(wb, "bottomN", cols = 1, rows = 2:11, style = negStyle, type = "topN", rank = 5) # Highlight bottom 20 percentage in column y conditionalFormatting(wb, "bottomN", cols = 2, rows = 2:11, style = negStyle, type = "topN", rank = 20, percent = TRUE) ## Logical Operators # You can use Excels logical Operators writeData(wb, "logical operators", 1:10) conditionalFormatting(wb, "logical operators", cols = 1, rows = 1:10, rule = "OR($A1=1,$A1=3,$A1=5,$A1=7)" ) \dontrun{ saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE) } ######################################################################### ## Databar Example wb <- createWorkbook() addWorksheet(wb, "databar") ## Databars writeData(wb, "databar", -5:5, startCol = 1) conditionalFormatting(wb, "databar", cols = 1, rows = 1:11, type = "databar") ## Defaults writeData(wb, "databar", -5:5, startCol = 3) conditionalFormatting(wb, "databar", cols = 3, rows = 1:11, type = "databar", border = FALSE) writeData(wb, "databar", -5:5, startCol = 5) conditionalFormatting(wb, "databar", cols = 5, rows = 1:11, type = "databar", style = c("#a6a6a6"), showValue = FALSE ) writeData(wb, "databar", -5:5, startCol = 7) conditionalFormatting(wb, "databar", cols = 7, rows = 1:11, type = "databar", style = c("#a6a6a6"), showValue = FALSE, gradient = FALSE ) writeData(wb, "databar", -5:5, startCol = 9) conditionalFormatting(wb, "databar", cols = 9, rows = 1:11, type = "databar", style = c("#a6a6a6", "#a6a6a6"), showValue = FALSE, gradient = FALSE ) \dontrun{ saveWorkbook(wb, file = "databarExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=createStyle]{createStyle()}} } \author{ Alexander Walker, Philipp Schauberger } openxlsx/man/removeTable.Rd0000644000176200001440000000266114155600363015451 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{removeTable} \alias{removeTable} \title{Remove an Excel table in a workbook} \usage{ removeTable(wb, sheet, table) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{table}{Name of table to remove. See \code{\link[=getTables]{getTables()}}} } \value{ character vector of table names on the specified sheet } \description{ List Excel tables in a workbook } \examples{ wb <- createWorkbook() addWorksheet(wb, sheetName = "Sheet 1") addWorksheet(wb, sheetName = "Sheet 2") writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) removeWorksheet(wb, sheet = 1) ## delete worksheet removes table objects writeDataTable(wb, sheet = 1, x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) ## removeTable() deletes table object and all data getTables(wb, sheet = 1) removeTable(wb = wb, sheet = 1, table = "iris") writeDataTable(wb, sheet = 1, x = iris, tableName = "iris", startCol = 1) getTables(wb, sheet = 1) removeTable(wb = wb, sheet = 1, table = "iris") writeDataTable(wb, sheet = 1, x = iris, tableName = "iris", startCol = 1) \dontrun{ saveWorkbook(wb = wb, file = "removeTableExample.xlsx", overwrite = TRUE) } } openxlsx/man/sheetVisible.Rd0000644000176200001440000000156014155600364015630 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{sheetVisible} \alias{sheetVisible} \alias{sheetVisible<-} \title{Get worksheet visible state.} \usage{ sheetVisible(wb) sheetVisible(wb) <- value } \arguments{ \item{wb}{A workbook object} \item{value}{a logical vector the same length as sheetVisible(wb)} } \value{ Character vector of worksheet names. TRUE if sheet is visible, FALSE if sheet is hidden } \description{ DEPRECATED - Use function 'sheetVisibility() } \examples{ wb <- createWorkbook() addWorksheet(wb, sheetName = "S1", visible = FALSE) addWorksheet(wb, sheetName = "S2", visible = TRUE) addWorksheet(wb, sheetName = "S3", visible = FALSE) sheetVisible(wb) sheetVisible(wb)[1] <- TRUE ## show sheet 1 sheetVisible(wb)[2] <- FALSE ## hide sheet 2 } \author{ Alexander Walker } openxlsx/man/addWorksheet.Rd0000644000176200001440000001016714155600363015630 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{addWorksheet} \alias{addWorksheet} \title{Add a worksheet to a workbook} \usage{ addWorksheet( wb, sheetName, gridLines = openxlsx_getOp("gridLines", TRUE), tabColour = NULL, zoom = 100, header = openxlsx_getOp("header"), footer = openxlsx_getOp("footer"), evenHeader = openxlsx_getOp("evenHeader"), evenFooter = openxlsx_getOp("evenFooter"), firstHeader = openxlsx_getOp("firstHeader"), firstFooter = openxlsx_getOp("firstFooter"), visible = TRUE, paperSize = openxlsx_getOp("paperSize", 9), orientation = openxlsx_getOp("orientation", "portrait"), vdpi = openxlsx_getOp("vdpi", 300), hdpi = openxlsx_getOp("hdpi", 300) ) } \arguments{ \item{wb}{A Workbook object to attach the new worksheet} \item{sheetName}{A name for the new worksheet} \item{gridLines}{A logical. If \code{FALSE}, the worksheet grid lines will be hidden.} \item{tabColour}{Colour of the worksheet tab. A valid colour (belonging to colours()) or a valid hex colour beginning with "#"} \item{zoom}{A numeric between 10 and 400. Worksheet zoom level as a percentage.} \item{header}{document header. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position.} \item{footer}{document footer. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position.} \item{evenHeader}{document header for even pages.} \item{evenFooter}{document footer for even pages.} \item{firstHeader}{document header for first page only.} \item{firstFooter}{document footer for first page only.} \item{visible}{If FALSE, sheet is hidden else visible.} \item{paperSize}{An integer corresponding to a paper size. See ?pageSetup for details.} \item{orientation}{One of "portrait" or "landscape"} \item{vdpi}{Vertical DPI. Can be set with options("openxlsx.dpi" = X) or options("openxlsx.vdpi" = X)} \item{hdpi}{Horizontal DPI. Can be set with options("openxlsx.dpi" = X) or options("openxlsx.hdpi" = X)} } \value{ XML tree } \description{ Add a worksheet to a Workbook object } \details{ Headers and footers can contain special tags \itemize{ \item{\strong{&[Page]}}{ Page number} \item{\strong{&[Pages]}}{ Number of pages} \item{\strong{&[Date]}}{ Current date} \item{\strong{&[Time]}}{ Current time} \item{\strong{&[Path]}}{ File path} \item{\strong{&[File]}}{ File name} \item{\strong{&[Tab]}}{ Worksheet name} } } \examples{ ## Create a new workbook wb <- createWorkbook("Fred") ## Add 3 worksheets addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2", gridLines = FALSE) addWorksheet(wb, "Sheet 3", tabColour = "red") addWorksheet(wb, "Sheet 4", gridLines = FALSE, tabColour = "#4F81BD") ## Headers and Footers addWorksheet(wb, "Sheet 5", header = c("ODD HEAD LEFT", "ODD HEAD CENTER", "ODD HEAD RIGHT"), footer = c("ODD FOOT RIGHT", "ODD FOOT CENTER", "ODD FOOT RIGHT"), evenHeader = c("EVEN HEAD LEFT", "EVEN HEAD CENTER", "EVEN HEAD RIGHT"), evenFooter = c("EVEN FOOT RIGHT", "EVEN FOOT CENTER", "EVEN FOOT RIGHT"), firstHeader = c("TOP", "OF FIRST", "PAGE"), firstFooter = c("BOTTOM", "OF FIRST", "PAGE") ) addWorksheet(wb, "Sheet 6", header = c("&[Date]", "ALL HEAD CENTER 2", "&[Page] / &[Pages]"), footer = c("&[Path]&[File]", NA, "&[Tab]"), firstHeader = c(NA, "Center Header of First Page", NA), firstFooter = c(NA, "Center Footer of First Page", NA) ) addWorksheet(wb, "Sheet 7", header = c("ALL HEAD LEFT 2", "ALL HEAD CENTER 2", "ALL HEAD RIGHT 2"), footer = c("ALL FOOT RIGHT 2", "ALL FOOT CENTER 2", "ALL FOOT RIGHT 2") ) addWorksheet(wb, "Sheet 8", firstHeader = c("FIRST ONLY L", NA, "FIRST ONLY R"), firstFooter = c("FIRST ONLY L", NA, "FIRST ONLY R") ) ## Need data on worksheet to see all headers and footers writeData(wb, sheet = 5, 1:400) writeData(wb, sheet = 6, 1:400) writeData(wb, sheet = 7, 1:400) writeData(wb, sheet = 8, 1:400) ## Save workbook \dontrun{ saveWorkbook(wb, "addWorksheetExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/buildWorkbook.Rd0000644000176200001440000001036414155600363016020 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/build_workbook.R \name{buildWorkbook} \alias{buildWorkbook} \title{Build Workbook} \usage{ buildWorkbook(x, asTable = FALSE, ...) } \arguments{ \item{x}{A data.frame or a (named) list of objects that can be handled by \code{\link[=writeData]{writeData()}} or \code{\link[=writeDataTable]{writeDataTable()}} to write to file} \item{asTable}{If \code{TRUE} will use \code{\link[=writeDataTable]{writeDataTable()}} rather than \code{\link[=writeData]{writeData()}} to write \code{x} to the file (default: \code{FALSE})} \item{...}{Additional arguments passed to \code{\link[=writeData]{writeData()}}, \code{\link[=writeDataTable]{writeDataTable()}}, \code{\link[=setColWidths]{setColWidths()}} (see Optional Parameters)} } \value{ A Workbook object } \description{ Build a workbook from a data.frame or named list } \details{ This function can be used as shortcut to create a workbook object from a data.frame or named list. If names are available in the list they will be used as the worksheet names. The parameters in \code{...} are collected and passed to \code{\link[=writeData]{writeData()}} or \code{\link[=writeDataTable]{writeDataTable()}} to initially create the Workbook objects then appropriate parameters are passed to \code{\link[=setColWidths]{setColWidths()}}. columns of x with class Date or POSIXt are automatically styled as dates and datetimes respectively. } \section{Optional Parameters}{ \strong{createWorkbook Parameters} \itemize{ \item{\strong{creator}}{ A string specifying the workbook author} } \strong{addWorksheet Parameters} \itemize{ \item{\strong{sheetName}}{ Name of the worksheet} \item{\strong{gridLines}}{ A logical. If \code{FALSE}, the worksheet grid lines will be hidden.} \item{\strong{tabColour}}{ Colour of the worksheet tab. A valid colour (belonging to colours()) or a valid hex colour beginning with "#".} \item{\strong{zoom}}{ A numeric between 10 and 400. Worksheet zoom level as a percentage.} } \strong{writeData/writeDataTable Parameters} \itemize{ \item{\strong{startCol}}{ A vector specifying the starting column(s) to write df} \item{\strong{startRow}}{ A vector specifying the starting row(s) to write df} \item{\strong{xy}}{ An alternative to specifying startCol and startRow individually. A vector of the form c(startCol, startRow)} \item{\strong{colNames or col.names}}{ If \code{TRUE}, column names of x are written.} \item{\strong{rowNames or row.names}}{ If \code{TRUE}, row names of x are written.} \item{\strong{headerStyle}}{ Custom style to apply to column names.} \item{\strong{borders}}{ Either "surrounding", "columns" or "rows" or NULL. If "surrounding", a border is drawn around the data. If "rows", a surrounding border is drawn a border around each row. If "columns", a surrounding border is drawn with a border between each column. If "\code{all}" all cell borders are drawn.} \item{\strong{borderColour}}{ Colour of cell border} \item{\strong{borderStyle}}{ Border line style.} \item{\strong{keepNA}} {If \code{TRUE}, NA values are converted to #N/A (or \code{na.string}, if not NULL) in Excel, else NA cells will be empty. Defaults to FALSE.} \item{\strong{na.string}} {If not NULL, and if \code{keepNA} is \code{TRUE}, NA values are converted to this string in Excel. Defaults to NULL.} } \strong{freezePane Parameters} \itemize{ \item{\strong{firstActiveRow}} {Top row of active region to freeze pane.} \item{\strong{firstActiveCol}} {Furthest left column of active region to freeze pane.} \item{\strong{firstRow}} {If \code{TRUE}, freezes the first row (equivalent to firstActiveRow = 2)} \item{\strong{firstCol}} {If \code{TRUE}, freezes the first column (equivalent to firstActiveCol = 2)} } \strong{colWidths Parameters} \itemize{ \item{\strong{colWidths}} {May be a single value for all columns (or "auto"), or a list of vectors that will be recycled for each sheet (see examples)} } } \examples{ x <- data.frame(a = 1, b = 2) wb <- buildWorkbook(x) y <- list(a = x, b = x, c = x) buildWorkbook(y, asTable = TRUE) buildWorkbook(y, asTable = TRUE, tableStyle = "TableStyleLight8") } \seealso{ \code{\link[=write.xlsx]{write.xlsx()}} } \author{ Jordan Mark Barbone } openxlsx/man/int2col.Rd0000644000176200001440000000051014155600363014545 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{int2col} \alias{int2col} \title{Convert integer to Excel column} \usage{ int2col(x) } \arguments{ \item{x}{A numeric vector} } \description{ Converts an integer to an Excel column label. } \examples{ int2col(1:10) } openxlsx/man/read.xlsx.Rd0000644000176200001440000000707214155600363015115 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/readWorkbook.R \name{read.xlsx} \alias{read.xlsx} \title{Read from an Excel file or Workbook object} \usage{ read.xlsx( xlsxFile, sheet, startRow = 1, colNames = TRUE, rowNames = FALSE, detectDates = FALSE, skipEmptyRows = TRUE, skipEmptyCols = TRUE, rows = NULL, cols = NULL, check.names = FALSE, sep.names = ".", namedRegion = NULL, na.strings = "NA", fillMergedCells = FALSE ) } \arguments{ \item{xlsxFile}{An xlsx file, Workbook object or URL to xlsx file.} \item{sheet}{The name or index of the sheet to read data from.} \item{startRow}{first row to begin looking for data. Empty rows at the top of a file are always skipped, regardless of the value of startRow.} \item{colNames}{If \code{TRUE}, the first row of data will be used as column names.} \item{rowNames}{If \code{TRUE}, first column of data will be used as row names.} \item{detectDates}{If \code{TRUE}, attempt to recognise dates and perform conversion.} \item{skipEmptyRows}{If \code{TRUE}, empty rows are skipped else empty rows after the first row containing data will return a row of NAs.} \item{skipEmptyCols}{If \code{TRUE}, empty columns are skipped.} \item{rows}{A numeric vector specifying which rows in the Excel file to read. If NULL, all rows are read.} \item{cols}{A numeric vector specifying which columns in the Excel file to read. If NULL, all columns are read.} \item{check.names}{logical. If TRUE then the names of the variables in the data frame are checked to ensure that they are syntactically valid variable names} \item{sep.names}{One character which substitutes blanks in column names. By default, "."} \item{namedRegion}{A named region in the Workbook. If not NULL startRow, rows and cols parameters are ignored.} \item{na.strings}{A character vector of strings which are to be interpreted as NA. Blank cells will be returned as NA.} \item{fillMergedCells}{If TRUE, the value in a merged cell is given to all cells within the merge.} } \value{ data.frame } \description{ Read data from an Excel file or Workbook object into a data.frame } \details{ Formulae written using writeFormula to a Workbook object will not get picked up by read.xlsx(). This is because only the formula is written and left to be evaluated when the file is opened in Excel. Opening, saving and closing the file with Excel will resolve this. } \examples{ xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") df1 <- read.xlsx(xlsxFile = xlsxFile, sheet = 1, skipEmptyRows = FALSE) sapply(df1, class) df2 <- read.xlsx(xlsxFile = xlsxFile, sheet = 3, skipEmptyRows = TRUE) df2$Date <- convertToDate(df2$Date) sapply(df2, class) head(df2) df2 <- read.xlsx( xlsxFile = xlsxFile, sheet = 3, skipEmptyRows = TRUE, detectDates = TRUE ) sapply(df2, class) head(df2) wb <- loadWorkbook(system.file("extdata", "readTest.xlsx", package = "openxlsx")) df3 <- read.xlsx(wb, sheet = 2, skipEmptyRows = FALSE, colNames = TRUE) df4 <- read.xlsx(xlsxFile, sheet = 2, skipEmptyRows = FALSE, colNames = TRUE) all.equal(df3, df4) wb <- loadWorkbook(system.file("extdata", "readTest.xlsx", package = "openxlsx")) df3 <- read.xlsx(wb, sheet = 2, skipEmptyRows = FALSE, cols = c(1, 4), rows = c(1, 3, 4) ) ## URL ## \dontrun{ xlsxFile <- "https://github.com/awalker89/openxlsx/raw/master/inst/readTest.xlsx" head(read.xlsx(xlsxFile)) } } \seealso{ \code{\link[=getNamedRegions]{getNamedRegions()}} } \author{ Alexander Walker } openxlsx/man/openXL.Rd0000644000176200001440000000250614155600363014407 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/openXL.R \name{openXL} \alias{openXL} \title{Open a Microsoft Excel file (xls/xlsx) or an openxlsx Workbook} \usage{ openXL(file=NULL) } \arguments{ \item{file}{path to the Excel (xls/xlsx) file or Workbook object.} } \description{ This function tries to open a Microsoft Excel (xls/xlsx) file or an openxlsx Workbook with the proper application, in a portable manner. In Windows (c) and Mac (c), it uses system default handlers, given the file type. In Linux it searches (via \code{which}) for available xls/xlsx reader applications (unless \code{options('openxlsx.excelApp')} is set to the app bin path), and if it finds anything, sets \code{options('openxlsx.excelApp')} to the program choosen by the user via a menu (if many are present, otherwise it will set the only available). Currently searched for apps are Libreoffice/Openoffice (\code{soffice} bin), Gnumeric (\code{gnumeric}) and Calligra Sheets (\code{calligrasheets}). } \examples{ # file example example(writeData) # openXL("writeDataExample.xlsx") # (not yet saved) Workbook example wb <- createWorkbook() x <- mtcars[1:6, ] addWorksheet(wb, "Cars") writeData(wb, "Cars", x, startCol = 2, startRow = 3, rowNames = TRUE) # openXL(wb) } \author{ Luca Braglia } openxlsx/man/openxlsx.Rd0000644000176200001440000000334614155600363015065 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/openxlsx.R \docType{package} \name{openxlsx} \alias{openxlsx} \title{xlsx reading, writing and editing.} \description{ openxlsx simplifies the the process of writing and styling Excel xlsx files from R and removes the dependency on Java. } \details{ The openxlsx package uses global options, most to simplify formatting. These are stored in the \code{op.openxlsx} object. \describe{ \item{openxlsx.bandedCols}{FALSE} \item{openxlsx.bandedRows}{TRUE} \item{openxlsx.borderColour}{"black"} \item{openxlsx.borders}{"none"} \item{openxlsx.borderStyle}{"thin"} \item{openxlsx.compressionLevel}{"9"} \item{openxlsx.creator}{""} \item{openxlsx.dateFormat}{"mm/dd/yyyy"} \item{openxlsx.datetimeFormat}{"yyyy-mm-dd hh:mm:ss"} \item{openxlsx.headerStyle}{NULL} \item{openxlsx.keepNA}{FALSE} \item{openxlsx.na.string}{NULL} \item{openxlsx.numFmt}{NULL} \item{openxlsx.orientation}{"portrait"} \item{openxlsx.paperSize}{9} \item{openxlsx.tabColour}{"TableStyleLight9"} \item{openxlsx.tableStyle}{"TableStyleLight9"} \item{openxlsx.withFilter}{NA Whether to write data with or without a filter. If NA will make filters with \code{writeDataTable} and will not for \code{writeData}} } See the Formatting vignette for examples. Additional options } \seealso{ \itemize{ \item{\code{vignette("Introduction", package = "openxlsx")}} \item{\code{vignette("formatting", package = "openxlsx")}} \item{\code{\link[=writeData]{writeData()}}} \item{\code{\link[=writeDataTable]{writeDataTable()}}} \item{\code{\link[=write.xlsx]{write.xlsx()}}} \item{\code{\link[=read.xlsx]{read.xlsx()}}} \item{\code{\link[=op.openxlsx]{op.openxlsx()}}} } for examples } openxlsx/man/getCreators.Rd0000644000176200001440000000072114155600363015461 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getCreators} \alias{getCreators} \title{Add another author to the meta data of the file.} \usage{ getCreators(wb) } \arguments{ \item{wb}{A workbook object} } \value{ vector of creators } \description{ Just a wrapper of wb$getCreators() Get the names of the } \examples{ wb <- createWorkbook() getCreators(wb) } \author{ Philipp Schauberger } openxlsx/man/removeWorksheet.Rd0000644000176200001440000000133714155600364016375 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{removeWorksheet} \alias{removeWorksheet} \title{Remove a worksheet from a workbook} \usage{ removeWorksheet(wb, sheet) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} } \description{ Remove a worksheet from a Workbook object Remove a worksheet from a workbook } \examples{ ## load a workbook wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) ## Remove sheet 2 removeWorksheet(wb, 2) ## save the modified workbook \dontrun{ saveWorkbook(wb, "removeWorksheetExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/sheetVisibility.Rd0000644000176200001440000000173514155600364016366 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{sheetVisibility} \alias{sheetVisibility} \alias{sheetVisibility<-} \title{Get/set worksheet visible state} \usage{ sheetVisibility(wb) sheetVisibility(wb) <- value } \arguments{ \item{wb}{A workbook object} \item{value}{a logical/character vector the same length as sheetVisibility(wb)} } \value{ Character vector of worksheet names. Vector of "hidden", "visible", "veryHidden" } \description{ Get and set worksheet visible state } \examples{ wb <- createWorkbook() addWorksheet(wb, sheetName = "S1", visible = FALSE) addWorksheet(wb, sheetName = "S2", visible = TRUE) addWorksheet(wb, sheetName = "S3", visible = FALSE) sheetVisibility(wb) sheetVisibility(wb)[1] <- TRUE ## show sheet 1 sheetVisibility(wb)[2] <- FALSE ## hide sheet 2 sheetVisibility(wb)[3] <- "hidden" ## hide sheet 3 sheetVisibility(wb)[3] <- "veryHidden" ## hide sheet 3 from UI } openxlsx/man/showGridLines.Rd0000644000176200001440000000150314155600364015760 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{showGridLines} \alias{showGridLines} \title{Set worksheet gridlines to show or hide.} \usage{ showGridLines(wb, sheet, showGridLines = FALSE) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{showGridLines}{A logical. If \code{FALSE}, grid lines are hidden.} } \description{ Set worksheet gridlines to show or hide. } \examples{ wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) names(wb) ## list worksheets in workbook showGridLines(wb, 1, showGridLines = FALSE) showGridLines(wb, "testing", showGridLines = FALSE) \dontrun{ saveWorkbook(wb, "showGridLinesExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/writeDataTable.Rd0000644000176200001440000001407614155600364016104 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/writeDataTable.R \name{writeDataTable} \alias{writeDataTable} \title{Write to a worksheet as an Excel table} \usage{ writeDataTable( wb, sheet, x, startCol = 1, startRow = 1, xy = NULL, colNames = TRUE, rowNames = FALSE, tableStyle = openxlsx_getOp("tableStyle", "TableStyleLight9"), tableName = NULL, headerStyle = openxlsx_getOp("headerStyle"), withFilter = openxlsx_getOp("withFilter", TRUE), keepNA = openxlsx_getOp("keepNA", FALSE), na.string = openxlsx_getOp("na.string"), sep = ", ", stack = FALSE, firstColumn = openxlsx_getOp("firstColumn", FALSE), lastColumn = openxlsx_getOp("lastColumn", FALSE), bandedRows = openxlsx_getOp("bandedRows", TRUE), bandedCols = openxlsx_getOp("bandedCols", FALSE), col.names, row.names ) } \arguments{ \item{wb}{A Workbook object containing a worksheet.} \item{sheet}{The worksheet to write to. Can be the worksheet index or name.} \item{x}{A dataframe.} \item{startCol}{A vector specifying the starting column to write df} \item{startRow}{A vector specifying the starting row to write df} \item{xy}{An alternative to specifying startCol and startRow individually. A vector of the form c(startCol, startRow)} \item{colNames}{If \code{TRUE}, column names of x are written.} \item{rowNames}{If \code{TRUE}, row names of x are written.} \item{tableStyle}{Any excel table style name or "none" (see "formatting" vignette).} \item{tableName}{name of table in workbook. The table name must be unique.} \item{headerStyle}{Custom style to apply to column names.} \item{withFilter}{If \code{TRUE} or \code{NA}, columns with have filters in the first row.} \item{keepNA}{If \code{TRUE}, NA values are converted to #N/A (or \code{na.string}, if not NULL) in Excel, else NA cells will be empty.} \item{na.string}{If not NULL, and if \code{keepNA} is \code{TRUE}, NA values are converted to this string in Excel.} \item{sep}{Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep).} \item{stack}{If \code{TRUE} the new style is merged with any existing cell styles. If FALSE, any existing style is replaced by the new style. \cr\cr \cr\strong{The below options correspond to Excel table options:} \cr \if{html}{\figure{tableoptions.png}{options: width="40\%" alt="Figure: table_options.png"}} \if{latex}{\figure{tableoptions.pdf}{options: width=7cm}}} \item{firstColumn}{logical. If TRUE, the first column is bold} \item{lastColumn}{logical. If TRUE, the last column is bold} \item{bandedRows}{logical. If TRUE, rows are colour banded} \item{bandedCols}{logical. If TRUE, the columns are colour banded} \item{row.names, col.names}{Deprecated, please use \code{rowNames}, \code{colNames} instead} } \description{ Write to a worksheet and format as an Excel table } \details{ columns of x with class Date/POSIXt, currency, accounting, hyperlink, percentage are automatically styled as dates, currency, accounting, hyperlinks, percentages respectively. } \examples{ ## see package vignettes for further examples. ##################################################################################### ## Create Workbook object and add worksheets wb <- createWorkbook() addWorksheet(wb, "S1") addWorksheet(wb, "S2") addWorksheet(wb, "S3") ##################################################################################### ## -- write data.frame as an Excel table with column filters ## -- default table style is "TableStyleMedium2" writeDataTable(wb, "S1", x = iris) writeDataTable(wb, "S2", x = mtcars, xy = c("B", 3), rowNames = TRUE, tableStyle = "TableStyleLight9" ) df <- data.frame( "Date" = Sys.Date() - 0:19, "T" = TRUE, "F" = FALSE, "Time" = Sys.time() - 0:19 * 60 * 60, "Cash" = paste("$", 1:20), "Cash2" = 31:50, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(0, 1, length.out = 20), "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE ) ## openxlsx will apply default Excel styling for these classes class(df$Cash) <- c(class(df$Cash), "currency") class(df$Cash2) <- c(class(df$Cash2), "accounting") class(df$hLink) <- "hyperlink" class(df$Percentage) <- c(class(df$Percentage), "percentage") class(df$TinyNumbers) <- c(class(df$TinyNumbers), "scientific") writeDataTable(wb, "S3", x = df, startRow = 4, rowNames = TRUE, tableStyle = "TableStyleMedium9") ##################################################################################### ## Additional Header Styling and remove column filters writeDataTable(wb, sheet = 1, x = iris, startCol = 7, headerStyle = createStyle(textRotation = 45), withFilter = FALSE ) ##################################################################################### ## Save workbook ## Open in excel without saving file: openXL(wb) \dontrun{ saveWorkbook(wb, "writeDataTableExample.xlsx", overwrite = TRUE) } ##################################################################################### ## Pre-defined table styles gallery wb <- createWorkbook(paste0("tableStylesGallery.xlsx")) addWorksheet(wb, "Style Samples") for (i in 1:21) { style <- paste0("TableStyleLight", i) writeDataTable(wb, x = data.frame(style), sheet = 1, tableStyle = style, startRow = 1, startCol = i * 3 - 2 ) } for (i in 1:28) { style <- paste0("TableStyleMedium", i) writeDataTable(wb, x = data.frame(style), sheet = 1, tableStyle = style, startRow = 4, startCol = i * 3 - 2 ) } for (i in 1:11) { style <- paste0("TableStyleDark", i) writeDataTable(wb, x = data.frame(style), sheet = 1, tableStyle = style, startRow = 7, startCol = i * 3 - 2 ) } ## openXL(wb) \dontrun{ saveWorkbook(wb, file = "tableStylesGallery.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=addWorksheet]{addWorksheet()}} \code{\link[=writeData]{writeData()}} \code{\link[=removeTable]{removeTable()}} \code{\link[=getTables]{getTables()}} } openxlsx/man/writeComment.Rd0000644000176200001440000000243614155600364015662 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/CommentClass.R \name{writeComment} \alias{writeComment} \title{write a cell comment} \usage{ writeComment(wb, sheet, col, row, comment, xy = NULL) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A vector of names or indices of worksheets} \item{col}{Column a column number of letter} \item{row}{A row number.} \item{comment}{A Comment object. See \code{\link[=createComment]{createComment()}}.} \item{xy}{An alternative to specifying \code{col} and \code{row} individually. A vector of the form \code{c(col, row)}.} } \description{ Write a Comment object to a worksheet } \examples{ wb <- createWorkbook() addWorksheet(wb, "Sheet 1") c1 <- createComment(comment = "this is comment") writeComment(wb, 1, col = "B", row = 10, comment = c1) s1 <- createStyle(fontSize = 12, fontColour = "red", textDecoration = c("BOLD")) s2 <- createStyle(fontSize = 9, fontColour = "black") c2 <- createComment(comment = c("This Part Bold red\n\n", "This part black"), style = c(s1, s2)) c2 writeComment(wb, 1, col = 6, row = 3, comment = c2) \dontrun{ saveWorkbook(wb, file = "writeCommentExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=createComment]{createComment()}} } openxlsx/man/freezePane.Rd0000644000176200001440000000254414155600363015270 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{freezePane} \alias{freezePane} \title{Freeze a worksheet pane} \usage{ freezePane( wb, sheet, firstActiveRow = NULL, firstActiveCol = NULL, firstRow = FALSE, firstCol = FALSE ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{firstActiveRow}{Top row of active region} \item{firstActiveCol}{Furthest left column of active region} \item{firstRow}{If \code{TRUE}, freezes the first row (equivalent to firstActiveRow = 2)} \item{firstCol}{If \code{TRUE}, freezes the first column (equivalent to firstActiveCol = 2)} } \description{ Freeze a worksheet pane } \examples{ ## Create a new workbook wb <- createWorkbook("Kenshin") ## Add some worksheets addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") addWorksheet(wb, "Sheet 3") addWorksheet(wb, "Sheet 4") ## Freeze Panes freezePane(wb, "Sheet 1", firstActiveRow = 5, firstActiveCol = 3) freezePane(wb, "Sheet 2", firstCol = TRUE) ## shortcut to firstActiveCol = 2 freezePane(wb, 3, firstRow = TRUE) ## shortcut to firstActiveRow = 2 freezePane(wb, 4, firstActiveRow = 1, firstActiveCol = "D") ## Save workbook \dontrun{ saveWorkbook(wb, "freezePaneExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/convertToDate.Rd0000644000176200001440000000136514155600363015765 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{convertToDate} \alias{convertToDate} \title{Convert from excel date number to R Date type} \usage{ convertToDate(x, origin = "1900-01-01", ...) } \arguments{ \item{x}{A vector of integers} \item{origin}{date. Default value is for Windows Excel 2010} \item{...}{additional parameters passed to as.Date()} } \description{ Convert from excel date number to R Date type } \details{ Excel stores dates as number of days from some origin day } \examples{ ## 2014 April 21st to 25th convertToDate(c(41750, 41751, 41752, 41753, 41754, NA)) convertToDate(c(41750.2, 41751.99, NA, 41753)) } \seealso{ \code{\link[=writeData]{writeData()}} } openxlsx/man/convertFromExcelRef.Rd0000644000176200001440000000076314155600363017127 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{convertFromExcelRef} \alias{convertFromExcelRef} \title{Convert excel column name to integer index} \usage{ convertFromExcelRef(col) } \arguments{ \item{col}{An excel column reference} } \description{ Convert excel column name to integer index e.g. "J" to 10 } \examples{ convertFromExcelRef("DOG") convertFromExcelRef("COW") ## numbers will be removed convertFromExcelRef("R22") } openxlsx/man/readWorkbook.Rd0000644000176200001440000000526014155600363015633 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/readWorkbook.R \name{readWorkbook} \alias{readWorkbook} \title{Read from an Excel file or Workbook object} \usage{ readWorkbook( xlsxFile, sheet = 1, startRow = 1, colNames = TRUE, rowNames = FALSE, detectDates = FALSE, skipEmptyRows = TRUE, skipEmptyCols = TRUE, rows = NULL, cols = NULL, check.names = FALSE, sep.names = ".", namedRegion = NULL, na.strings = "NA", fillMergedCells = FALSE ) } \arguments{ \item{xlsxFile}{An xlsx file, Workbook object or URL to xlsx file.} \item{sheet}{The name or index of the sheet to read data from.} \item{startRow}{first row to begin looking for data. Empty rows at the top of a file are always skipped, regardless of the value of startRow.} \item{colNames}{If \code{TRUE}, the first row of data will be used as column names.} \item{rowNames}{If \code{TRUE}, first column of data will be used as row names.} \item{detectDates}{If \code{TRUE}, attempt to recognise dates and perform conversion.} \item{skipEmptyRows}{If \code{TRUE}, empty rows are skipped else empty rows after the first row containing data will return a row of NAs.} \item{skipEmptyCols}{If \code{TRUE}, empty columns are skipped.} \item{rows}{A numeric vector specifying which rows in the Excel file to read. If NULL, all rows are read.} \item{cols}{A numeric vector specifying which columns in the Excel file to read. If NULL, all columns are read.} \item{check.names}{logical. If TRUE then the names of the variables in the data frame are checked to ensure that they are syntactically valid variable names} \item{sep.names}{One character which substitutes blanks in column names. By default, "."} \item{namedRegion}{A named region in the Workbook. If not NULL startRow, rows and cols parameters are ignored.} \item{na.strings}{A character vector of strings which are to be interpreted as NA. Blank cells will be returned as NA.} \item{fillMergedCells}{If TRUE, the value in a merged cell is given to all cells within the merge.} } \value{ data.frame } \description{ Read data from an Excel file or Workbook object into a data.frame } \details{ Creates a data.frame of all data in worksheet. } \examples{ xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") df1 <- readWorkbook(xlsxFile = xlsxFile, sheet = 1) xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") df1 <- readWorkbook(xlsxFile = xlsxFile, sheet = 1, rows = c(1, 3, 5), cols = 1:3) } \seealso{ \code{\link[=getNamedRegions]{getNamedRegions()}} \code{\link[=read.xlsx]{read.xlsx()}} } \author{ Alexander Walker } openxlsx/man/write.xlsx.Rd0000644000176200001440000001200714155600364015327 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/writexlsx.R \name{write.xlsx} \alias{write.xlsx} \title{write data to an xlsx file} \usage{ write.xlsx(x, file, asTable = FALSE, overwrite = TRUE, ...) } \arguments{ \item{x}{A data.frame or a (named) list of objects that can be handled by \code{\link[=writeData]{writeData()}} or \code{\link[=writeDataTable]{writeDataTable()}} to write to file} \item{file}{A file path to save the xlsx file} \item{asTable}{If \code{TRUE} will use \code{\link[=writeDataTable]{writeDataTable()}} rather than \code{\link[=writeData]{writeData()}} to write \code{x} to the file (default: \code{FALSE})} \item{overwrite}{Overwrite existing file (Defaults to \code{TRUE} as with \code{write.table})} \item{...}{Additional arguments passed to \code{\link[=buildWorkbook]{buildWorkbook()}}; see details} } \value{ A workbook object } \description{ write a data.frame or list of data.frames to an xlsx file } \section{Optional Parameters}{ \strong{createWorkbook Parameters} \itemize{ \item{\strong{creator}}{ A string specifying the workbook author} } \strong{addWorksheet Parameters} \itemize{ \item{\strong{sheetName}}{ Name of the worksheet} \item{\strong{gridLines}}{ A logical. If \code{FALSE}, the worksheet grid lines will be hidden.} \item{\strong{tabColour}}{ Colour of the worksheet tab. A valid colour (belonging to colours()) or a valid hex colour beginning with "#".} \item{\strong{zoom}}{ A numeric between 10 and 400. Worksheet zoom level as a percentage.} } \strong{writeData/writeDataTable Parameters} \itemize{ \item{\strong{startCol}}{ A vector specifying the starting column(s) to write df} \item{\strong{startRow}}{ A vector specifying the starting row(s) to write df} \item{\strong{xy}}{ An alternative to specifying startCol and startRow individually. A vector of the form c(startCol, startRow)} \item{\strong{colNames or col.names}}{ If \code{TRUE}, column names of x are written.} \item{\strong{rowNames or row.names}}{ If \code{TRUE}, row names of x are written.} \item{\strong{headerStyle}}{ Custom style to apply to column names.} \item{\strong{borders}}{ Either "surrounding", "columns" or "rows" or NULL. If "surrounding", a border is drawn around the data. If "rows", a surrounding border is drawn a border around each row. If "columns", a surrounding border is drawn with a border between each column. If "\code{all}" all cell borders are drawn.} \item{\strong{borderColour}}{ Colour of cell border} \item{\strong{borderStyle}}{ Border line style.} \item{\strong{keepNA}} {If \code{TRUE}, NA values are converted to #N/A (or \code{na.string}, if not NULL) in Excel, else NA cells will be empty. Defaults to FALSE.} \item{\strong{na.string}} {If not NULL, and if \code{keepNA} is \code{TRUE}, NA values are converted to this string in Excel. Defaults to NULL.} } \strong{freezePane Parameters} \itemize{ \item{\strong{firstActiveRow}} {Top row of active region to freeze pane.} \item{\strong{firstActiveCol}} {Furthest left column of active region to freeze pane.} \item{\strong{firstRow}} {If \code{TRUE}, freezes the first row (equivalent to firstActiveRow = 2)} \item{\strong{firstCol}} {If \code{TRUE}, freezes the first column (equivalent to firstActiveCol = 2)} } \strong{colWidths Parameters} \itemize{ \item{\strong{colWidths}} {May be a single value for all columns (or "auto"), or a list of vectors that will be recycled for each sheet (see examples)} } } \examples{ ## write to working directory options("openxlsx.borderColour" = "#4F80BD") ## set default border colour \dontrun{ write.xlsx(iris, file = "writeXLSX1.xlsx", colNames = TRUE, borders = "columns") write.xlsx(iris, file = "writeXLSX2.xlsx", colNames = TRUE, borders = "surrounding") } hs <- createStyle( textDecoration = "BOLD", fontColour = "#FFFFFF", fontSize = 12, fontName = "Arial Narrow", fgFill = "#4F80BD" ) \dontrun{ write.xlsx(iris, file = "writeXLSX3.xlsx", colNames = TRUE, borders = "rows", headerStyle = hs ) } ## Lists elements are written to individual worksheets, using list names as sheet names if available l <- list("IRIS" = iris, "MTCATS" = mtcars, matrix(runif(1000), ncol = 5)) \dontrun{ write.xlsx(l, "writeList1.xlsx", colWidths = c(NA, "auto", "auto")) } ## different sheets can be given different parameters \dontrun{ write.xlsx(l, "writeList2.xlsx", startCol = c(1, 2, 3), startRow = 2, asTable = c(TRUE, TRUE, FALSE), withFilter = c(TRUE, FALSE, FALSE) ) } # specify column widths for multiple sheets \dontrun{ write.xlsx(l, "writeList2.xlsx", colWidths = 20) write.xlsx(l, "writeList2.xlsx", colWidths = list(100, 200, 300)) write.xlsx(l, "writeList2.xlsx", colWidths = list(rep(10, 5), rep(8, 11), rep(5, 5))) } } \seealso{ \code{\link[=addWorksheet]{addWorksheet()}} \code{\link[=writeData]{writeData()}} \code{\link[=createStyle]{createStyle()}} for style parameters \code{\link[=buildWorkbook]{buildWorkbook()}} } \author{ Alexander Walker, Jordan Mark Barbone } openxlsx/man/ungroupColumns.Rd0000644000176200001440000000106714155600364016244 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{ungroupColumns} \alias{ungroupColumns} \title{Ungroup Columns} \usage{ ungroupColumns(wb, sheet, cols) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Indices of columns to ungroup} } \description{ Ungroup a selection of columns } \details{ If column was previously hidden, it will now be shown } \seealso{ \code{\link[=ungroupRows]{ungroupRows()}} To ungroup rows } \author{ Joshua Sturm } openxlsx/man/removeCellMerge.Rd0000644000176200001440000000115614155600363016257 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{removeCellMerge} \alias{removeCellMerge} \title{Create a new Workbook object} \usage{ removeCellMerge(wb, sheet, cols, rows) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{vector of column indices} \item{rows}{vector of row indices} } \description{ Unmerges any merged cells that intersect with the region specified by, min(cols):max(cols) X min(rows):max(rows) } \seealso{ \code{\link[=mergeCells]{mergeCells()}} } \author{ Alexander Walker } openxlsx/man/setHeader.Rd0000644000176200001440000000202514155600364015103 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{setHeader} \alias{setHeader} \title{Set header for all worksheets} \usage{ setHeader(wb, text, position = "center") } \arguments{ \item{wb}{A workbook object} \item{text}{header text. A character vector of length 1.} \item{position}{Position of text in header. One of "left", "center" or "right"} } \description{ DEPRECATED } \examples{ \dontrun{ wb <- createWorkbook("Edgar Anderson") addWorksheet(wb, "S1") writeDataTable(wb, "S1", x = iris[1:30, ], xy = c("C", 5)) ## set all headers setHeader(wb, "This is a header", position = "center") setHeader(wb, "To the left", position = "left") setHeader(wb, "On the right", position = "right") ## set all footers setFooter(wb, "Center Footer Here", position = "center") setFooter(wb, "Bottom left", position = "left") setFooter(wb, Sys.Date(), position = "right") saveWorkbook(wb, "headerHeaderExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/conditionalFormat.Rd0000644000176200001440000000252614155600363016660 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{conditionalFormat} \alias{conditionalFormat} \title{Add conditional formatting to cells} \usage{ conditionalFormat( wb, sheet, cols, rows, rule = NULL, style = NULL, type = "expression" ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Columns to apply conditional formatting to} \item{rows}{Rows to apply conditional formatting to} \item{rule}{The condition under which to apply the formatting or a vector of colours. See examples.} \item{style}{A style to apply to those cells that satisfy the rule. A Style object returned from createStyle()} \item{type}{Either 'expression', 'colorscale' or 'databar'. If 'expression' the formatting is determined by a formula. If colorScale cells are coloured based on cell value. See examples.} } \description{ DEPRECATED! USE \code{\link[=conditionalFormatting]{conditionalFormatting()}} } \details{ DEPRECATED! USE \code{\link[=conditionalFormatting]{conditionalFormatting()}} Valid operators are "<", "<=", ">", ">=", "==", "!=". See Examples. Default style given by: createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } \seealso{ \code{\link[=createStyle]{createStyle()}} } \author{ Alexander Walker } openxlsx/man/temp_xlsx.Rd0000644000176200001440000000056414155600364015230 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \name{temp_xlsx} \alias{temp_xlsx} \title{helper function to create tempory directory for testing purpose} \usage{ temp_xlsx(name = "temp_xlsx") } \arguments{ \item{name}{for the temp file} } \description{ helper function to create tempory directory for testing purpose } openxlsx/man/pageSetup.Rd0000644000176200001440000001454414155600363015144 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{pageSetup} \alias{pageSetup} \title{Set page margins, orientation and print scaling} \usage{ pageSetup( wb, sheet, orientation = NULL, scale = 100, left = 0.7, right = 0.7, top = 0.75, bottom = 0.75, header = 0.3, footer = 0.3, fitToWidth = FALSE, fitToHeight = FALSE, paperSize = NULL, printTitleRows = NULL, printTitleCols = NULL, summaryRow = NULL, summaryCol = NULL ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{orientation}{Page orientation. One of "portrait" or "landscape"} \item{scale}{Print scaling. Numeric value between 10 and 400} \item{left}{left page margin in inches} \item{right}{right page margin in inches} \item{top}{top page margin in inches} \item{bottom}{bottom page margin in inches} \item{header}{header margin in inches} \item{footer}{footer margin in inches} \item{fitToWidth}{If \code{TRUE}, worksheet is scaled to fit to page width on printing.} \item{fitToHeight}{If \code{TRUE}, worksheet is scaled to fit to page height on printing.} \item{paperSize}{See details. Default value is 9 (A4 paper).} \item{printTitleRows}{Rows to repeat at top of page when printing. Integer vector.} \item{printTitleCols}{Columns to repeat at left when printing. Integer vector.} \item{summaryRow}{Location of summary rows in groupings. One of "Above" or "Below".} \item{summaryCol}{Location of summary columns in groupings. One of "Right" or "Left".} } \description{ Set page margins, orientation and print scaling } \details{ paperSize is an integer corresponding to: \itemize{ \item{\strong{1}}{ Letter paper (8.5 in. by 11 in.)} \item{\strong{2}}{ Letter small paper (8.5 in. by 11 in.)} \item{\strong{3}}{ Tabloid paper (11 in. by 17 in.)} \item{\strong{4}}{ Ledger paper (17 in. by 11 in.)} \item{\strong{5}}{ Legal paper (8.5 in. by 14 in.)} \item{\strong{6}}{ Statement paper (5.5 in. by 8.5 in.)} \item{\strong{7}}{ Executive paper (7.25 in. by 10.5 in.)} \item{\strong{8}}{ A3 paper (297 mm by 420 mm)} \item{\strong{9}}{ A4 paper (210 mm by 297 mm)} \item{\strong{10}}{ A4 small paper (210 mm by 297 mm)} \item{\strong{11}}{ A5 paper (148 mm by 210 mm)} \item{\strong{12}}{ B4 paper (250 mm by 353 mm)} \item{\strong{13}}{ B5 paper (176 mm by 250 mm)} \item{\strong{14}}{ Folio paper (8.5 in. by 13 in.)} \item{\strong{15}}{ Quarto paper (215 mm by 275 mm)} \item{\strong{16}}{ Standard paper (10 in. by 14 in.)} \item{\strong{17}}{ Standard paper (11 in. by 17 in.)} \item{\strong{18}}{ Note paper (8.5 in. by 11 in.)} \item{\strong{19}}{ #9 envelope (3.875 in. by 8.875 in.)} \item{\strong{20}}{ #10 envelope (4.125 in. by 9.5 in.)} \item{\strong{21}}{ #11 envelope (4.5 in. by 10.375 in.)} \item{\strong{22}}{ #12 envelope (4.75 in. by 11 in.)} \item{\strong{23}}{ #14 envelope (5 in. by 11.5 in.)} \item{\strong{24}}{ C paper (17 in. by 22 in.)} \item{\strong{25}}{ D paper (22 in. by 34 in.)} \item{\strong{26}}{ E paper (34 in. by 44 in.)} \item{\strong{27}}{ DL envelope (110 mm by 220 mm)} \item{\strong{28}}{ C5 envelope (162 mm by 229 mm)} \item{\strong{29}}{ C3 envelope (324 mm by 458 mm)} \item{\strong{30}}{ C4 envelope (229 mm by 324 mm)} \item{\strong{31}}{ C6 envelope (114 mm by 162 mm)} \item{\strong{32}}{ C65 envelope (114 mm by 229 mm)} \item{\strong{33}}{ B4 envelope (250 mm by 353 mm)} \item{\strong{34}}{ B5 envelope (176 mm by 250 mm)} \item{\strong{35}}{ B6 envelope (176 mm by 125 mm)} \item{\strong{36}}{ Italy envelope (110 mm by 230 mm)} \item{\strong{37}}{ Monarch envelope (3.875 in. by 7.5 in.).} \item{\strong{38}}{ 6 3/4 envelope (3.625 in. by 6.5 in.)} \item{\strong{39}}{ US standard fanfold (14.875 in. by 11 in.)} \item{\strong{40}}{ German standard fanfold (8.5 in. by 12 in.)} \item{\strong{41}}{ German legal fanfold (8.5 in. by 13 in.)} \item{\strong{42}}{ ISO B4 (250 mm by 353 mm)} \item{\strong{43}}{ Japanese double postcard (200 mm by 148 mm)} \item{\strong{44}}{ Standard paper (9 in. by 11 in.)} \item{\strong{45}}{ Standard paper (10 in. by 11 in.)} \item{\strong{46}}{ Standard paper (15 in. by 11 in.)} \item{\strong{47}}{ Invite envelope (220 mm by 220 mm)} \item{\strong{50}}{ Letter extra paper (9.275 in. by 12 in.)} \item{\strong{51}}{ Legal extra paper (9.275 in. by 15 in.)} \item{\strong{52}}{ Tabloid extra paper (11.69 in. by 18 in.)} \item{\strong{53}}{ A4 extra paper (236 mm by 322 mm)} \item{\strong{54}}{ Letter transverse paper (8.275 in. by 11 in.)} \item{\strong{55}}{ A4 transverse paper (210 mm by 297 mm)} \item{\strong{56}}{ Letter extra transverse paper (9.275 in. by 12 in.)} \item{\strong{57}}{ SuperA/SuperA/A4 paper (227 mm by 356 mm)} \item{\strong{58}}{ SuperB/SuperB/A3 paper (305 mm by 487 mm)} \item{\strong{59}}{ Letter plus paper (8.5 in. by 12.69 in.)} \item{\strong{60}}{ A4 plus paper (210 mm by 330 mm)} \item{\strong{61}}{ A5 transverse paper (148 mm by 210 mm)} \item{\strong{62}}{ JIS B5 transverse paper (182 mm by 257 mm)} \item{\strong{63}}{ A3 extra paper (322 mm by 445 mm)} \item{\strong{64}}{ A5 extra paper (174 mm by 235 mm)} \item{\strong{65}}{ ISO B5 extra paper (201 mm by 276 mm)} \item{\strong{66}}{ A2 paper (420 mm by 594 mm)} \item{\strong{67}}{ A3 transverse paper (297 mm by 420 mm)} \item{\strong{68}}{ A3 extra transverse paper (322 mm by 445 mm)} } } \examples{ wb <- createWorkbook() addWorksheet(wb, "S1") addWorksheet(wb, "S2") writeDataTable(wb, 1, x = iris[1:30, ]) writeDataTable(wb, 2, x = iris[1:30, ], xy = c("C", 5)) ## landscape page scaled to 50\% pageSetup(wb, sheet = 1, orientation = "landscape", scale = 50) ## portrait page scales to 300\% with 0.5in left and right margins pageSetup(wb, sheet = 2, orientation = "portrait", scale = 300, left = 0.5, right = 0.5) ## print titles addWorksheet(wb, "print_title_rows") addWorksheet(wb, "print_title_cols") writeData(wb, "print_title_rows", rbind(iris, iris, iris, iris)) writeData(wb, "print_title_cols", x = rbind(mtcars, mtcars, mtcars), rowNames = TRUE) pageSetup(wb, sheet = "print_title_rows", printTitleRows = 1) ## first row pageSetup(wb, sheet = "print_title_cols", printTitleCols = 1, printTitleRows = 1) \dontrun{ saveWorkbook(wb, "pageSetupExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker, Joshua Sturm } openxlsx/man/addStyle.Rd0000644000176200001440000000336514155600363014757 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{addStyle} \alias{addStyle} \title{Add a style to a set of cells} \usage{ addStyle(wb, sheet, style, rows, cols, gridExpand = FALSE, stack = FALSE) } \arguments{ \item{wb}{A Workbook object containing a worksheet.} \item{sheet}{A worksheet to apply the style to.} \item{style}{A style object returned from createStyle()} \item{rows}{Rows to apply style to.} \item{cols}{columns to apply style to.} \item{gridExpand}{If \code{TRUE}, style will be applied to all combinations of rows and cols.} \item{stack}{If \code{TRUE} the new style is merged with any existing cell styles. If FALSE, any existing style is replaced by the new style.} } \description{ Function adds a style to a specified set of cells. } \examples{ ## See package vignette for more examples. ## Create a new workbook wb <- createWorkbook("My name here") ## Add a worksheets addWorksheet(wb, "Expenditure", gridLines = FALSE) ## write data to worksheet 1 writeData(wb, sheet = 1, USPersonalExpenditure, rowNames = TRUE) ## create and add a style to the column headers headerStyle <- createStyle( fontSize = 14, fontColour = "#FFFFFF", halign = "center", fgFill = "#4F81BD", border = "TopBottom", borderColour = "#4F81BD" ) ## style for body bodyStyle <- createStyle(border = "TopBottom", borderColour = "#4F81BD") addStyle(wb, sheet = 1, bodyStyle, rows = 2:6, cols = 1:6, gridExpand = TRUE) setColWidths(wb, 1, cols = 1, widths = 21) ## set column width for row names column \dontrun{ saveWorkbook(wb, "addStyleExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=createStyle]{createStyle()}} expand.grid } \author{ Alexander Walker } openxlsx/man/NamedRegion.Rd0000644000176200001440000000342214155600363015370 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{createNamedRegion} \alias{createNamedRegion} \alias{deleteNamedRegion} \title{Create / delete a named region.} \usage{ createNamedRegion(wb, sheet, cols, rows, name, overwrite = FALSE) deleteNamedRegion(wb, name) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Numeric vector specifying columns to include in region} \item{rows}{Numeric vector specifying rows to include in region} \item{name}{Name for region. A character vector of length 1. Note region names musts be case-insensitive unique.} \item{overwrite}{Boolean. Overwrite if exists ? Default to FALSE} } \description{ Create / delete a named region } \details{ Region is given by: min(cols):max(cols) X min(rows):max(rows) } \examples{ ## create named regions wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## specify region writeData(wb, sheet = 1, x = iris, startCol = 1, startRow = 1) createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = 1:(nrow(iris) + 1), cols = 1:ncol(iris) ) ## using writeData 'name' argument writeData(wb, sheet = 1, x = iris, name = "iris2", startCol = 10) out_file <- tempfile(fileext = ".xlsx") \dontrun{ saveWorkbook(wb, out_file, overwrite = TRUE) ## see named regions getNamedRegions(wb) ## From Workbook object getNamedRegions(out_file) ## From xlsx file ## delete one deleteNamedRegion(wb = wb, name = "iris2") getNamedRegions(wb) ## read named regions df <- read.xlsx(wb, namedRegion = "iris") head(df) df <- read.xlsx(out_file, namedRegion = "iris2") head(df) } } \seealso{ \code{\link[=getNamedRegions]{getNamedRegions()}} } \author{ Alexander Walker } openxlsx/man/setFooter.Rd0000644000176200001440000000202514155600364015151 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{setFooter} \alias{setFooter} \title{Set footer for all worksheets} \usage{ setFooter(wb, text, position = "center") } \arguments{ \item{wb}{A workbook object} \item{text}{footer text. A character vector of length 1.} \item{position}{Position of text in footer. One of "left", "center" or "right"} } \description{ DEPRECATED } \examples{ \dontrun{ wb <- createWorkbook("Edgar Anderson") addWorksheet(wb, "S1") writeDataTable(wb, "S1", x = iris[1:30, ], xy = c("C", 5)) ## set all headers setHeader(wb, "This is a header", position = "center") setHeader(wb, "To the left", position = "left") setHeader(wb, "On the right", position = "right") ## set all footers setFooter(wb, "Center Footer Here", position = "center") setFooter(wb, "Bottom left", position = "left") setFooter(wb, Sys.Date(), position = "right") saveWorkbook(wb, "headerFooterExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/pageBreak.Rd0000644000176200001440000000162614155600363015065 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{pageBreak} \alias{pageBreak} \title{add a page break to a worksheet} \usage{ pageBreak(wb, sheet, i, type = "row") } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{i}{row or column number to insert page break.} \item{type}{One of "row" or "column" for a row break or column break.} } \description{ insert page breaks into a worksheet } \examples{ wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, sheet = 1, x = iris) pageBreak(wb, sheet = 1, i = 10, type = "row") pageBreak(wb, sheet = 1, i = 20, type = "row") pageBreak(wb, sheet = 1, i = 2, type = "column") \dontrun{ saveWorkbook(wb, "pageBreakExample.xlsx", TRUE) } ## In Excel: View tab -> Page Break Preview } \seealso{ \code{\link[=addWorksheet]{addWorksheet()}} } openxlsx/man/getNamedRegions.Rd0000644000176200001440000000226114155600363016253 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getNamedRegions} \alias{getNamedRegions} \title{Get named regions} \usage{ getNamedRegions(x) } \arguments{ \item{x}{An xlsx file or Workbook object} } \description{ Return a vector of named regions in a xlsx file or Workbook object } \examples{ ## create named regions wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## specify region writeData(wb, sheet = 1, x = iris, startCol = 1, startRow = 1) createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = 1:(nrow(iris) + 1), cols = 1:ncol(iris) ) ## using writeData 'name' argument to create a named region writeData(wb, sheet = 1, x = iris, name = "iris2", startCol = 10) \dontrun{ out_file <- tempfile(fileext = ".xlsx") saveWorkbook(wb, out_file, overwrite = TRUE) ## see named regions getNamedRegions(wb) ## From Workbook object getNamedRegions(out_file) ## From xlsx file ## read named regions df <- read.xlsx(wb, namedRegion = "iris") head(df) df <- read.xlsx(out_file, namedRegion = "iris2") head(df) } } \seealso{ \code{\link[=createNamedRegion]{createNamedRegion()}} } openxlsx/man/groupRows.Rd0000644000176200001440000000120314155600363015202 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{groupRows} \alias{groupRows} \title{Group Rows} \usage{ groupRows(wb, sheet, rows, hidden = FALSE) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{rows}{Indices of rows to group} \item{hidden}{Logical vector. If TRUE the grouped columns are hidden. Defaults to FALSE} } \description{ Group a selection of rows } \seealso{ \code{\link[=ungroupRows]{ungroupRows()}} to ungroup rows. \code{\link[=groupColumns]{groupColumns()}} for grouping columns. } \author{ Joshua Sturm } openxlsx/man/figures/0000755000176200001440000000000014155600363014354 5ustar liggesusersopenxlsx/man/figures/lifecycle-defunct.svg0000644000176200001440000000170414155600363020464 0ustar liggesuserslifecyclelifecycledefunctdefunct openxlsx/man/figures/lifecycle-maturing.svg0000644000176200001440000000170614155600363020664 0ustar liggesuserslifecyclelifecyclematuringmaturing openxlsx/man/figures/lifecycle-archived.svg0000644000176200001440000000170714155600363020624 0ustar liggesusers lifecyclelifecyclearchivedarchived openxlsx/man/figures/lifecycle-soft-deprecated.svg0000644000176200001440000000172614155600363022111 0ustar liggesuserslifecyclelifecyclesoft-deprecatedsoft-deprecated openxlsx/man/figures/lifecycle-questioning.svg0000644000176200001440000000171414155600363021402 0ustar liggesuserslifecyclelifecyclequestioningquestioning openxlsx/man/figures/lifecycle-stable.svg0000644000176200001440000000167414155600363020314 0ustar liggesuserslifecyclelifecyclestablestable openxlsx/man/figures/lifecycle-experimental.svg0000644000176200001440000000171614155600363021534 0ustar liggesuserslifecyclelifecycleexperimentalexperimental openxlsx/man/figures/tableoptions.png0000644000176200001440000000636114155600363017573 0ustar liggesusersPNG  IHDR5Y_^sRGBgAMA a pHYsod IDATx^흽M$<S#CQA0CD101 CM d^oٝ}v/(YycD8cI3NjƘpR3LE5=|PuOyPڻ6ͤaqb^vx/{={%ϟ?/Ϟ=[~ +WwZ.QNly:=zt7[텸zq(py2[߳(-!^9;'߿/_ݻ|R k؟~*(ZZ~hGA[k^b E>ܛ<s;'Ǐ/nZ>}tQ%[.d__ "˾ׯ7~\=D-b}ܵCIWߏ?|rym;ğ*\߹sWf؟~ˋN8B0/e3 D{w7}[?h5fNj5_d¯Ɗ)c'<{QU+@ M,Ӌ{[3GIR)&]%wm\.1c) e^bՊ`ZI/;)kNjqdzEA M^AojnRZJ6Kc\[E\3ҊD}*aB+8Ɠ.Uٚh&bC)=1_Ԍ1qR3L1f*Ԍ1Sf '5cT8cI3NjƘpR3LE5MOa(ź'4ZKpx/I ^3^I˳gϖ?~4/{?7|N'9eR;,OGefvNj߿_߿ܻwo["qo.s݅ڹmzhkO23;'Ǐ/nZ>}tQH_ϡ 2^qɑVȌ?|rym T.z$SV)m|vuJO$3~5ˇN?x˯qѾ|3v\ߩlZD?H\wːuzcEQ[%ݮ{kg(ݻ˗/_>\=}tER]+q-ڬ1=F\RCkSqk?ϹTk|K'G̏/mݨf?gXsmxfΘ|i!So< C6ā OcROCHv7Ks81qkIuI[v=z}xFT^~ZclI팉K%lԒo3ڱJɃ1ڠ6~9IɗI>b[lifecyclelifecycledeprecateddeprecated openxlsx/man/figures/tableoptions.pdf0000644000176200001440000000764414155600363017565 0ustar liggesusers%PDF-1.3 % 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Kids [3 0 R] /Type /Pages /Count 1 >> endobj 3 0 obj << /Resources << /ProcSet 4 0 R /XObject << /Im0 5 0 R >> >> /Contents 6 0 R /Parent 2 0 R /Type /Page /Thumb 7 0 R /MediaBox [0 0 231.75 66.75] /CropBox [0 0 231.75 66.75] >> endobj 6 0 obj << /Length 36 >> stream q 231.75 0 0 66.75 0 0 cm /Im0 Do Q endstream endobj 4 0 obj [/PDF /Text /ImageC] endobj 5 0 obj << /Subtype /Image /Name /Im0 /Type /XObject /Filter [/FlateDecode] /Width 309 /Height 89 /BitsPerComponent 8 /Length 1368 /ColorSpace 8 0 R >> stream x[=8. Urnj,tm7ؔ4) .?"%6i[oO&G< )[@ A}!"mu]Dp+ ,2lؐ_015zBH0i>i<@ci̘p~lفbJߺoX++PLe3u-8TrVS^VYCW/zlJ[sXf2{ @ї_yiw֒:0;/&d 3벤12-O&_ጠAa2vat"R?4<)>@&-)E6܋DŽ=bˈ,+H#Z+$^Hj-iՃ@?ja}js{=iZ!ltH̭yߞ@ !^WH]2{1b%M;̛)x%m1ع@evHL&;*x+nSxi)] iF >9tMkZnB iC^(uҔۭI*)]iF .MTTTvFS2tdE Ao(4pAdb^t=U'S=C/7i>:wՉz ɺK*].ʬ86<Ҧodjc_diANYΑ&Ku" '$:LspFCoRt4iHݸw&x@Z_\^GaMzN=141˻N3$gJ5 endstream endobj 8 0 obj /DeviceGray endobj 7 0 obj << /Filter [/FlateDecode] /Width 106 /Height 31 /BitsPerComponent 8 /Length 1243 /ColorSpace 8 0 R >> stream xڭVisHvaW*1F\11!`!!-!^II8iUfFO03l^uCa4GZ7P{0-Á<3Niӕ lj]waybCXƕ.pUzV)CQSoоeO[u6օ#1:P4r[E=D\6}3 RGaA[-ZۇF z0:n5@M$D}{z6.B]ګ9S0]p޸ l~Tr! % vv7\ȵ˳ . fSዞn!EW`Ivop Acy& qd}WL YP$$4O.8wD4 ۗ! κ-e8) .^]S'f@CQ0(HSb j1᯿dŁ|R>bɊ-#岭'xLO0Ϛn_TQ'ZQFqwFf쫦4U̎rI@2Eĥ''zz9!D#'Q-Uw'7D FctQ5J:M Li,H&`32ަ W |t Y-K_t0oK:z^i5/fKiC5mfS}9D'b;J~Aք}JMXI\ ZzGgx ̄\͑nW!_ dbYwgt2p,gl6A>^g+:dpzqa~6b> endobj xref 0 10 0000000000 65535 f 0000000015 00000 n 0000000066 00000 n 0000000125 00000 n 0000000409 00000 n 0000000446 00000 n 0000000320 00000 n 0000002022 00000 n 0000001994 00000 n 0000003403 00000 n trailer << /Info 9 0 R /ID [<89e679221bdee51109cf78c857e2cf8a8b87664cf67428090bb2625251705fd8> <89e679221bdee51109cf78c857e2cf8a8b87664cf67428090bb2625251705fd8>] /Root 1 0 R /Size 10 >> startxref 3585 %%EOF openxlsx/man/figures/lifecycle-retired.svg0000644000176200001440000000170514155600363020473 0ustar liggesusers lifecyclelifecycleretiredretired openxlsx/man/removeRowHeights.Rd0000644000176200001440000000153314155600363016502 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{removeRowHeights} \alias{removeRowHeights} \title{Remove custom row heights from a worksheet} \usage{ removeRowHeights(wb, sheet, rows) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{rows}{Indices of rows to remove custom height (if any) from.} } \description{ Remove row heights from a worksheet } \examples{ ## Create a new workbook wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) ## remove any custom row heights in rows 1 to 10 removeRowHeights(wb, 1, rows = 1:10) \dontrun{ saveWorkbook(wb, "removeRowHeightsExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=setRowHeights]{setRowHeights()}} } \author{ Alexander Walker } openxlsx/man/dataValidation.Rd0000644000176200001440000000445214155600363016130 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{dataValidation} \alias{dataValidation} \title{Add data validation to cells} \usage{ dataValidation( wb, sheet, cols, rows, type, operator, value, allowBlank = TRUE, showInputMsg = TRUE, showErrorMsg = TRUE ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Contiguous columns to apply conditional formatting to} \item{rows}{Contiguous rows to apply conditional formatting to} \item{type}{One of 'whole', 'decimal', 'date', 'time', 'textLength', 'list' (see examples)} \item{operator}{One of 'between', 'notBetween', 'equal', 'notEqual', 'greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual'} \item{value}{a vector of length 1 or 2 depending on operator (see examples)} \item{allowBlank}{logical} \item{showInputMsg}{logical} \item{showErrorMsg}{logical} } \description{ Add Excel data validation to cells } \examples{ wb <- createWorkbook() addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") writeDataTable(wb, 1, x = iris[1:30, ]) dataValidation(wb, 1, col = 1:3, rows = 2:31, type = "whole", operator = "between", value = c(1, 9) ) dataValidation(wb, 1, col = 5, rows = 2:31, type = "textLength", operator = "between", value = c(4, 6) ) ## Date and Time cell validation df <- data.frame( "d" = as.Date("2016-01-01") + -5:5, "t" = as.POSIXct("2016-01-01") + -5:5 * 10000 ) writeData(wb, 2, x = df) dataValidation(wb, 2, col = 1, rows = 2:12, type = "date", operator = "greaterThanOrEqual", value = as.Date("2016-01-01") ) dataValidation(wb, 2, col = 2, rows = 2:12, type = "time", operator = "between", value = df$t[c(4, 8)] ) \dontrun{ saveWorkbook(wb, "dataValidationExample.xlsx", overwrite = TRUE) } ###################################################################### ## If type == 'list' # operator argument is ignored. wb <- createWorkbook() addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") writeDataTable(wb, sheet = 1, x = iris[1:30, ]) writeData(wb, sheet = 2, x = sample(iris$Sepal.Length, 10)) dataValidation(wb, 1, col = 1, rows = 2:31, type = "list", value = "'Sheet 2'!$A$1:$A$10") # openXL(wb) } openxlsx/man/loadWorkbook.Rd0000644000176200001440000000202114155600363015627 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/loadWorkbook.R \name{loadWorkbook} \alias{loadWorkbook} \title{Load an existing .xlsx file} \usage{ loadWorkbook(file, xlsxFile = NULL, isUnzipped = FALSE) } \arguments{ \item{file}{A path to an existing .xlsx or .xlsm file} \item{xlsxFile}{alias for file} \item{isUnzipped}{Set to TRUE if the xlsx file is already unzipped} } \value{ Workbook object. } \description{ loadWorkbook returns a workbook object conserving styles and formatting of the original .xlsx file. } \examples{ ## load existing workbook from package folder wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) names(wb) # list worksheets wb ## view object ## Add a worksheet addWorksheet(wb, "A new worksheet") ## Save workbook \dontrun{ saveWorkbook(wb, "loadExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=removeWorksheet]{removeWorksheet()}} } \author{ Alexander Walker, Philipp Schauberger } openxlsx/man/insertPlot.Rd0000644000176200001440000000400714155600363015343 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{insertPlot} \alias{insertPlot} \title{Insert the current plot into a worksheet} \usage{ insertPlot( wb, sheet, width = 6, height = 4, xy = NULL, startRow = 1, startCol = 1, fileType = "png", units = "in", dpi = 300 ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{width}{Width of figure. Defaults to 6in.} \item{height}{Height of figure . Defaults to 4in.} \item{xy}{Alternate way to specify startRow and startCol. A vector of length 2 of form (startcol, startRow)} \item{startRow}{Row coordinate of upper left corner of figure.\code{ xy[[2]]} when xy is given.} \item{startCol}{Column coordinate of upper left corner of figure. \code{xy[[1]]} when xy is given.} \item{fileType}{File type of image} \item{units}{Units of width and height. Can be "in", "cm" or "px"} \item{dpi}{Image resolution} } \description{ The current plot is saved to a temporary image file using dev.copy. This file is then written to the workbook using insertImage. } \examples{ \dontrun{ ## Create a new workbook wb <- createWorkbook() ## Add a worksheet addWorksheet(wb, "Sheet 1", gridLines = FALSE) ## create plot objects require(ggplot2) p1 <- qplot(mpg, data = mtcars, geom = "density", fill = as.factor(gear), alpha = I(.5), main = "Distribution of Gas Mileage" ) p2 <- qplot(age, circumference, data = Orange, geom = c("point", "line"), colour = Tree ) ## Insert currently displayed plot to sheet 1, row 1, column 1 print(p1) # plot needs to be showing insertPlot(wb, 1, width = 5, height = 3.5, fileType = "png", units = "in") ## Insert plot 2 print(p2) insertPlot(wb, 1, xy = c("J", 2), width = 16, height = 10, fileType = "png", units = "cm") ## Save workbook saveWorkbook(wb, "insertPlotExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=insertImage]{insertImage()}} } \author{ Alexander Walker } openxlsx/man/renameWorksheet.Rd0000644000176200001440000000207714155600364016351 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{renameWorksheet} \alias{renameWorksheet} \title{Rename a worksheet} \usage{ renameWorksheet(wb, sheet, newName) } \arguments{ \item{wb}{A Workbook object containing a worksheet} \item{sheet}{The name or index of the worksheet to rename} \item{newName}{The new name of the worksheet. No longer than 31 chars.} } \description{ Rename a worksheet } \details{ DEPRECATED. Use \code{\link[=names]{names()}} } \examples{ ## Create a new workbook wb <- createWorkbook("CREATOR") ## Add 3 worksheets addWorksheet(wb, "Worksheet Name") addWorksheet(wb, "This is worksheet 2") addWorksheet(wb, "Not the best name") #' ## rename all worksheets names(wb) <- c("A", "B", "C") ## Rename worksheet 1 & 3 renameWorksheet(wb, 1, "New name for sheet 1") names(wb)[[1]] <- "New name for sheet 1" names(wb)[[3]] <- "A better name" ## Save workbook \dontrun{ saveWorkbook(wb, "renameWorksheetExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/writeData.Rd0000644000176200001440000001543014155633372015134 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/writeData.R \name{writeData} \alias{writeData} \title{Write an object to a worksheet} \usage{ writeData( wb, sheet, x, startCol = 1, startRow = 1, array = FALSE, xy = NULL, colNames = TRUE, rowNames = FALSE, headerStyle = openxlsx_getOp("headerStyle"), borders = openxlsx_getOp("borders", "none"), borderColour = openxlsx_getOp("borderColour", "black"), borderStyle = openxlsx_getOp("borderStyle", "thin"), withFilter = openxlsx_getOp("withFilter", FALSE), keepNA = openxlsx_getOp("keepNA", FALSE), na.string = openxlsx_getOp("na.string"), name = NULL, sep = ", ", col.names, row.names ) } \arguments{ \item{wb}{A Workbook object containing a worksheet.} \item{sheet}{The worksheet to write to. Can be the worksheet index or name.} \item{x}{Object to be written. For classes supported look at the examples.} \item{startCol}{A vector specifying the starting column to write to.} \item{startRow}{A vector specifying the starting row to write to.} \item{array}{A bool if the function written is of type array} \item{xy}{An alternative to specifying \code{startCol} and \code{startRow} individually. A vector of the form \code{c(startCol, startRow)}.} \item{colNames}{If \code{TRUE}, column names of x are written.} \item{rowNames}{If \code{TRUE}, data.frame row names of x are written.} \item{headerStyle}{Custom style to apply to column names.} \item{borders}{Either "\code{none}" (default), "\code{surrounding}", "\code{columns}", "\code{rows}" or \emph{respective abbreviations}. If "\code{surrounding}", a border is drawn around the data. If "\code{rows}", a surrounding border is drawn with a border around each row. If "\code{columns}", a surrounding border is drawn with a border between each column. If "\code{all}" all cell borders are drawn.} \item{borderColour}{Colour of cell border. A valid colour (belonging to \code{colours()} or a hex colour code, eg see \href{https://www.w3schools.com/colors/colors_picker.asp}{here}).} \item{borderStyle}{Border line style \itemize{ \item{\strong{none}}{ no border} \item{\strong{thin}}{ thin border} \item{\strong{medium}}{ medium border} \item{\strong{dashed}}{ dashed border} \item{\strong{dotted}}{ dotted border} \item{\strong{thick}}{ thick border} \item{\strong{double}}{ double line border} \item{\strong{hair}}{ hairline border} \item{\strong{mediumDashed}}{ medium weight dashed border} \item{\strong{dashDot}}{ dash-dot border} \item{\strong{mediumDashDot}}{ medium weight dash-dot border} \item{\strong{dashDotDot}}{ dash-dot-dot border} \item{\strong{mediumDashDotDot}}{ medium weight dash-dot-dot border} \item{\strong{slantDashDot}}{ slanted dash-dot border} }} \item{withFilter}{If \code{TRUE} or \code{NA}, add filters to the column name row. NOTE can only have one filter per worksheet.} \item{keepNA}{If \code{TRUE}, NA values are converted to #N/A (or \code{na.string}, if not NULL) in Excel, else NA cells will be empty.} \item{na.string}{If not NULL, and if \code{keepNA} is \code{TRUE}, NA values are converted to this string in Excel.} \item{name}{If not NULL, a named region is defined.} \item{sep}{Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep).} \item{row.names, col.names}{Deprecated, please use \code{rowNames}, \code{colNames} instead} } \value{ invisible(0) } \description{ Write an object to worksheet with optional styling. } \details{ Formulae written using writeFormula to a Workbook object will not get picked up by read.xlsx(). This is because only the formula is written and left to Excel to evaluate the formula when the file is opened in Excel. } \examples{ ## See formatting vignette for further examples. ## Options for default styling (These are the defaults) options("openxlsx.borderColour" = "black") options("openxlsx.borderStyle" = "thin") options("openxlsx.dateFormat" = "mm/dd/yyyy") options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") options("openxlsx.numFmt" = NULL) ## Change the default border colour to #4F81BD options("openxlsx.borderColour" = "#4F81BD") ##################################################################################### ## Create Workbook object and add worksheets wb <- createWorkbook() ## Add worksheets addWorksheet(wb, "Cars") addWorksheet(wb, "Formula") x <- mtcars[1:6, ] writeData(wb, "Cars", x, startCol = 2, startRow = 3, rowNames = TRUE) ##################################################################################### ## Bordering writeData(wb, "Cars", x, rowNames = TRUE, startCol = "O", startRow = 3, borders = "surrounding", borderColour = "black" ) ## black border writeData(wb, "Cars", x, rowNames = TRUE, startCol = 2, startRow = 12, borders = "columns" ) writeData(wb, "Cars", x, rowNames = TRUE, startCol = "O", startRow = 12, borders = "rows" ) ##################################################################################### ## Header Styles hs1 <- createStyle( fgFill = "#DCE6F1", halign = "CENTER", textDecoration = "italic", border = "Bottom" ) writeData(wb, "Cars", x, colNames = TRUE, rowNames = TRUE, startCol = "B", startRow = 23, borders = "rows", headerStyle = hs1, borderStyle = "dashed" ) hs2 <- createStyle( fontColour = "#ffffff", fgFill = "#4F80BD", halign = "center", valign = "center", textDecoration = "bold", border = "TopBottomLeftRight" ) writeData(wb, "Cars", x, colNames = TRUE, rowNames = TRUE, startCol = "O", startRow = 23, borders = "columns", headerStyle = hs2 ) ##################################################################################### ## Hyperlinks ## - vectors/columns with class 'hyperlink' are written as hyperlinks' v <- rep("https://CRAN.R-project.org/", 4) names(v) <- paste0("Hyperlink", 1:4) # Optional: names will be used as display text class(v) <- "hyperlink" writeData(wb, "Cars", x = v, xy = c("B", 32)) ##################################################################################### ## Formulas ## - vectors/columns with class 'formula' are written as formulas' df <- data.frame( x = 1:3, y = 1:3, z = paste0(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = " + "), stringsAsFactors = FALSE ) class(df$z) <- c(class(df$z), "formula") writeData(wb, sheet = "Formula", x = df) ##################################################################################### ## Save workbook ## Open in excel without saving file: openXL(wb) \dontrun{ saveWorkbook(wb, "writeDataExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=writeDataTable]{writeDataTable()}} } \author{ Alexander Walker } openxlsx/man/deleteData.Rd0000644000176200001440000000217214155600363015235 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{deleteData} \alias{deleteData} \title{Delete cell data} \usage{ deleteData(wb, sheet, cols, rows, gridExpand = FALSE) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{columns to delete data from.} \item{rows}{Rows to delete data from.} \item{gridExpand}{If \code{TRUE}, all data in rectangle min(rows):max(rows) X min(cols):max(cols) will be removed.} } \description{ Delete contents and styling from a cell. } \examples{ ## write some data wb <- createWorkbook() addWorksheet(wb, "Worksheet 1") x <- data.frame(matrix(runif(200), ncol = 10)) writeData(wb, sheet = 1, x = x, startCol = 2, startRow = 3, colNames = FALSE) ## delete some data deleteData(wb, sheet = 1, cols = 3:5, rows = 5:7, gridExpand = TRUE) deleteData(wb, sheet = 1, cols = 7:9, rows = 5:7, gridExpand = TRUE) deleteData(wb, sheet = 1, cols = LETTERS, rows = 18, gridExpand = TRUE) \dontrun{ saveWorkbook(wb, "deleteDataExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/setRowHeights.Rd0000644000176200001440000000172714155600364016006 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{setRowHeights} \alias{setRowHeights} \title{Set worksheet row heights} \usage{ setRowHeights(wb, sheet, rows, heights) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{rows}{Indices of rows to set height} \item{heights}{Heights to set rows to specified in Excel column height units.} } \description{ Set worksheet row heights } \examples{ ## Create a new workbook wb <- createWorkbook() ## Add a worksheet addWorksheet(wb, "Sheet 1") ## set row heights setRowHeights(wb, 1, rows = c(1, 4, 22, 2, 19), heights = c(24, 28, 32, 42, 33)) ## overwrite row 1 height setRowHeights(wb, 1, rows = 1, heights = 40) ## Save workbook \dontrun{ saveWorkbook(wb, "setRowHeightsExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=removeRowHeights]{removeRowHeights()}} } \author{ Alexander Walker } openxlsx/man/getSheetNames.Rd0000644000176200001440000000075714155600363015744 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getSheetNames} \alias{getSheetNames} \title{Get names of worksheets} \usage{ getSheetNames(file) } \arguments{ \item{file}{An xlsx or xlsm file.} } \value{ Character vector of worksheet names. } \description{ Returns the worksheet names within an xlsx file } \examples{ getSheetNames(system.file("extdata", "readTest.xlsx", package = "openxlsx")) } \author{ Alexander Walker } openxlsx/man/openxlsxFontSizeLookupTable.Rd0000644000176200001440000000074714155600363020713 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/data-fontSizeLookupTables.R \docType{data} \name{openxlsxFontSizeLookupTable} \alias{openxlsxFontSizeLookupTable} \alias{openxlsxFontSizeLookupTableBold} \title{Font Size Lookup tables} \format{ A data.frame with column names corresponding to font names } \usage{ openxlsxFontSizeLookupTable openxlsxFontSizeLookupTableBold } \description{ Lookup tables for font size } \keyword{datasets} openxlsx/man/openxlsx_options.Rd0000644000176200001440000000234314155600363016634 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/openxlsx.R \docType{data} \name{openxlsx_options} \alias{openxlsx_options} \alias{op.openxlsx} \alias{openxlsx_getOp} \alias{openxlsx_setOp} \title{openxlsx Options} \format{ An object of class \code{list} of length 34. } \usage{ op.openxlsx openxlsx_getOp(x, default = NULL) openxlsx_setOp(x, value) } \arguments{ \item{x}{An option name (\code{"openxlsx."} prefix optional)} \item{default}{A default value if \code{NULL}} \item{value}{The new value for the option (optional if x is a named list)} } \description{ See and get the openxlsx options } \details{ \code{openxlsx_getOp()} retrieves the \code{"openxlsx"} options found in \code{op.openxlsx}. If none are set (currently \code{NULL}) retrieves the default option from \code{op.openxlsx}. This will also check that the intended option is a standard option (listed in \code{op.openxlsx}) and will provide a warning otherwise. \code{openxlsx_setOp()} is a safer way to set an option as it will first check that the option is a standard option (as above) before setting. } \examples{ openxlsx_getOp("borders") op.openxlsx[["openxlsx.borders"]] } \keyword{datasets} openxlsx/man/all.equal.Rd0000644000176200001440000000065414155600363015062 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{all.equal} \alias{all.equal} \alias{all.equal.Workbook} \title{Check equality of workbooks} \usage{ \method{all.equal}{Workbook}(target, current, ...) } \arguments{ \item{target}{A \code{Workbook} object} \item{current}{A \code{Workbook} object} \item{...}{ignored} } \description{ Check equality of workbooks } openxlsx/man/createComment.Rd0000644000176200001440000000266014155600363015771 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/CommentClass.R \name{createComment} \alias{createComment} \title{create a Comment object} \usage{ createComment( comment, author = Sys.getenv("USERNAME"), style = NULL, visible = TRUE, width = 2, height = 4 ) } \arguments{ \item{comment}{Comment text. Character vector.} \item{author}{Author of comment. Character vector of length 1} \item{style}{A Style object or list of style objects the same length as comment vector. See \code{\link[=createStyle]{createStyle()}}.} \item{visible}{TRUE or FALSE. Is comment visible.} \item{width, height}{Width and height of textbook (in number of cells); doubles are rounded with \code{base::round()}} } \description{ Create a cell Comment object to pass to writeComment() } \examples{ wb <- createWorkbook() addWorksheet(wb, "Sheet 1") c1 <- createComment(comment = "this is comment") writeComment(wb, 1, col = "B", row = 10, comment = c1) s1 <- createStyle(fontSize = 12, fontColour = "red", textDecoration = c("BOLD")) s2 <- createStyle(fontSize = 9, fontColour = "black") c2 <- createComment(comment = c("This Part Bold red\n\n", "This part black"), style = c(s1, s2)) c2 writeComment(wb, 1, col = 6, row = 3, comment = c2) \dontrun{ saveWorkbook(wb, file = "createCommentExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=writeComment]{writeComment()}} } openxlsx/man/getBaseFont.Rd0000644000176200001440000000115014155600363015375 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getBaseFont} \alias{getBaseFont} \title{Return the workbook default font} \usage{ getBaseFont(wb) } \arguments{ \item{wb}{A workbook object} } \description{ Return the workbook default font Returns the base font used in the workbook. } \examples{ ## create a workbook wb <- createWorkbook() getBaseFont(wb) ## modify base font to size 10 Arial Narrow in red modifyBaseFont(wb, fontSize = 10, fontColour = "#FF0000", fontName = "Arial Narrow") getBaseFont(wb) } \author{ Alexander Walker } openxlsx/man/setColWidths.Rd0000644000176200001440000000354314155600364015621 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{setColWidths} \alias{setColWidths} \title{Set worksheet column widths} \usage{ setColWidths( wb, sheet, cols, widths = 8.43, hidden = rep(FALSE, length(cols)), ignoreMergedCells = FALSE ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Indices of cols to set width} \item{widths}{widths to set cols to specified in Excel column width units or "auto" for automatic sizing. The widths argument is recycled to the length of cols.} \item{hidden}{Logical vector. If TRUE the column is hidden.} \item{ignoreMergedCells}{Ignore any cells that have been merged with other cells in the calculation of "auto" column widths.} } \description{ Set worksheet column widths to specific width or "auto". } \details{ The global min and max column width for "auto" columns is set by (default values show): \itemize{ \item{options("openxlsx.minWidth" = 3)} \item{options("openxlsx.maxWidth" = 250)} ## This is the maximum width allowed in Excel } NOTE: The calculation of column widths can be slow for large worksheets. NOTE: The \code{hidden} parameter may conflict with the one set in \code{groupColumns}; changing one will update the other. } \examples{ ## Create a new workbook wb <- createWorkbook() ## Add a worksheet addWorksheet(wb, "Sheet 1") ## set col widths setColWidths(wb, 1, cols = c(1, 4, 6, 7, 9), widths = c(16, 15, 12, 18, 33)) ## auto columns addWorksheet(wb, "Sheet 2") writeData(wb, sheet = 2, x = iris) setColWidths(wb, sheet = 2, cols = 1:5, widths = "auto") ## Save workbook \dontrun{ saveWorkbook(wb, "setColWidthsExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=removeColWidths]{removeColWidths()}} } \author{ Alexander Walker } openxlsx/man/setLastModifiedBy.Rd0000644000176200001440000000104714155600364016555 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{setLastModifiedBy} \alias{setLastModifiedBy} \title{Add another author to the meta data of the file.} \usage{ setLastModifiedBy(wb, LastModifiedBy) } \arguments{ \item{wb}{A workbook object} \item{LastModifiedBy}{A string object with the name of the LastModifiedBy-User} } \description{ Just a wrapper of wb$changeLastModifiedBy() } \examples{ wb <- createWorkbook() setLastModifiedBy(wb, "test") } \author{ Philipp Schauberger } openxlsx/man/sheets.Rd0000644000176200001440000000152714155600364014500 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{sheets} \alias{sheets} \title{Returns names of worksheets.} \usage{ sheets(wb) } \arguments{ \item{wb}{A workbook object} } \value{ Name of worksheet(s) for a given index } \description{ DEPRECATED. Use names(). } \details{ DEPRECATED. Use \code{\link[=names]{names()}} } \examples{ ## Create a new workbook wb <- createWorkbook() ## Add some worksheets addWorksheet(wb, "Worksheet Name") addWorksheet(wb, "This is worksheet 2") addWorksheet(wb, "The third worksheet") ## Return names of sheets, can not be used for assignment. names(wb) # openXL(wb) names(wb) <- c("A", "B", "C") names(wb) # openXL(wb) } \seealso{ \code{\link[=names]{names()}} to rename a worksheet in a Workbook } \author{ Alexander Walker } openxlsx/man/removeFilter.Rd0000644000176200001440000000156614155600363015652 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{removeFilter} \alias{removeFilter} \title{Remove a worksheet filter} \usage{ removeFilter(wb, sheet) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A vector of names or indices of worksheets} } \description{ Removes filters from addFilter() and writeData() } \examples{ wb <- createWorkbook() addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") addWorksheet(wb, "Sheet 3") writeData(wb, 1, iris) addFilter(wb, 1, row = 1, cols = 1:ncol(iris)) ## Equivalently writeData(wb, 2, x = iris, withFilter = TRUE) ## Similarly writeDataTable(wb, 3, iris) ## remove filters removeFilter(wb, 1:2) ## remove filters removeFilter(wb, 3) ## Does not affect tables! \dontrun{ saveWorkbook(wb, file = "removeFilterExample.xlsx", overwrite = TRUE) } } openxlsx/man/copyWorkbook.Rd0000644000176200001440000000105414155600363015667 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{copyWorkbook} \alias{copyWorkbook} \title{Copy a Workbook object.} \usage{ copyWorkbook(wb) } \arguments{ \item{wb}{A workbook object} } \value{ Workbook } \description{ Just a wrapper of wb$copy() } \examples{ wb <- createWorkbook() wb2 <- wb ## does not create a copy wb3 <- copyWorkbook(wb) ## wrapper for wb$copy() addWorksheet(wb, "Sheet1") ## adds worksheet to both wb and wb2 but not wb3 names(wb) names(wb2) names(wb3) } openxlsx/man/mergeCells.Rd0000644000176200001440000000277114155600363015270 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{mergeCells} \alias{mergeCells} \title{Merge cells within a worksheet} \usage{ mergeCells(wb, sheet, cols, rows) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Columns to merge} \item{rows}{corresponding rows to merge} } \description{ Merge cells within a worksheet } \details{ As merged region must be rectangular, only min and max of cols and rows are used. } \examples{ ## Create a new workbook wb <- createWorkbook() ## Add a worksheet addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") ## Merge cells: Row 2 column C to F (3:6) mergeCells(wb, "Sheet 1", cols = 2, rows = 3:6) ## Merge cells:Rows 10 to 20 columns A to J (1:10) mergeCells(wb, 1, cols = 1:10, rows = 10:20) ## Intersecting merges mergeCells(wb, 2, cols = 1:10, rows = 1) mergeCells(wb, 2, cols = 5:10, rows = 2) mergeCells(wb, 2, cols = c(1, 10), rows = 12) ## equivalent to 1:10 as only min/max are used # mergeCells(wb, 2, cols = 1, rows = c(1,10)) # Throws error because intersects existing merge ## remove merged cells removeCellMerge(wb, 2, cols = 1, rows = 1) # removes any intersecting merges mergeCells(wb, 2, cols = 1, rows = 1:10) # Now this works ## Save workbook \dontrun{ saveWorkbook(wb, "mergeCellsExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=removeCellMerge]{removeCellMerge()}} } \author{ Alexander Walker } openxlsx/man/convertToDateTime.Rd0000644000176200001440000000142614155600363016602 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{convertToDateTime} \alias{convertToDateTime} \title{Convert from excel time number to R POSIXct type.} \usage{ convertToDateTime(x, origin = "1900-01-01", ...) } \arguments{ \item{x}{A numeric vector} \item{origin}{date. Default value is for Windows Excel 2010} \item{...}{Additional parameters passed to as.POSIXct} } \description{ Convert from excel time number to R POSIXct type. } \details{ Excel stores dates as number of days from some origin date } \examples{ ## 2014-07-01, 2014-06-30, 2014-06-29 x <- c(41821.8127314815, 41820.8127314815, NA, 41819, NaN) convertToDateTime(x) convertToDateTime(x, tz = "Australia/Perth") convertToDateTime(x, tz = "UTC") } openxlsx/man/getCellRefs.Rd0000644000176200001440000000112114155600363015371 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getCellRefs} \alias{getCellRefs} \title{Return excel cell coordinates from (x,y) coordinates} \usage{ getCellRefs(cellCoords) } \arguments{ \item{cellCoords}{A data.frame with two columns coordinate pairs.} } \value{ Excel alphanumeric cell reference } \description{ Return excel cell coordinates from (x,y) coordinates } \examples{ getCellRefs(data.frame(1, 2)) # "B1" getCellRefs(data.frame(1:3, 2:4)) # "B1" "C2" "D3" } \author{ Philipp Schauberger, Alexander Walker } openxlsx/man/if_null_then.Rd0000644000176200001440000000062314155600363015646 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/utils.R \name{if_null_then} \alias{if_null_then} \alias{\%||\%} \title{If NULL then ...} \usage{ x \%||\% y } \arguments{ \item{x}{A value to check} \item{y}{A value to substitute if x is null} } \description{ Replace NULL } \examples{ \dontrun{ x <- NULL x <- x \%||\% "none" x <- x \%||\% NA } } openxlsx/man/ungroupRows.Rd0000644000176200001440000000103014155600364015544 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{ungroupRows} \alias{ungroupRows} \title{Ungroup Rows} \usage{ ungroupRows(wb, sheet, rows) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{rows}{Indices of rows to ungroup} } \description{ Ungroup a selection of rows } \details{ If row was previously hidden, it will now be shown } \seealso{ \code{\link[=ungroupColumns]{ungroupColumns()}} } \author{ Joshua Sturm } openxlsx/man/addCreator.Rd0000644000176200001440000000074714155600363015257 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{addCreator} \alias{addCreator} \title{Add another author to the meta data of the file.} \usage{ addCreator(wb, Creator) } \arguments{ \item{wb}{A workbook object} \item{Creator}{A string object with the name of the creator} } \description{ Just a wrapper of wb$addCreator() } \examples{ wb <- createWorkbook() addCreator(wb, "test") } \author{ Philipp Schauberger } openxlsx/man/getDateOrigin.Rd0000644000176200001440000000205514155600363015726 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getDateOrigin} \alias{getDateOrigin} \title{Get the date origin an xlsx file is using} \usage{ getDateOrigin(xlsxFile) } \arguments{ \item{xlsxFile}{An xlsx or xlsm file.} } \value{ One of "1900-01-01" or "1904-01-01". } \description{ Return the date origin used internally by an xlsx or xlsm file } \details{ Excel stores dates as the number of days from either 1904-01-01 or 1900-01-01. This function checks the date origin being used in an Excel file and returns is so it can be used in \code{\link[=convertToDate]{convertToDate()}} } \examples{ ## create a file with some dates \dontrun{ write.xlsx(as.Date("2015-01-10") - (0:4), file = "getDateOriginExample.xlsx") m <- read.xlsx("getDateOriginExample.xlsx") ## convert to dates do <- getDateOrigin(system.file("extdata", "readTest.xlsx", package = "openxlsx")) convertToDate(m[[1]], do) } } \seealso{ \code{\link[=convertToDate]{convertToDate()}} } \author{ Alexander Walker } openxlsx/man/makeHyperlinkString.Rd0000644000176200001440000000520414155600363017172 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/helperFunctions.R \name{makeHyperlinkString} \alias{makeHyperlinkString} \title{create Excel hyperlink string} \usage{ makeHyperlinkString(sheet, row = 1, col = 1, text = NULL, file = NULL) } \arguments{ \item{sheet}{Name of a worksheet} \item{row}{integer row number for hyperlink to link to} \item{col}{column number of letter for hyperlink to link to} \item{text}{display text} \item{file}{Excel file name to point to. If NULL hyperlink is internal.} } \description{ Wrapper to create internal hyperlink string to pass to writeFormula(). Either link to external urls or local files or straight to cells of local Excel sheets. } \examples{ ## Writing internal hyperlinks wb <- createWorkbook() addWorksheet(wb, "Sheet1") addWorksheet(wb, "Sheet2") addWorksheet(wb, "Sheet 3") writeData(wb, sheet = 3, x = iris) ## External Hyperlink x <- c("https://www.google.com", "https://www.google.com.au") names(x) <- c("google", "google Aus") class(x) <- "hyperlink" writeData(wb, sheet = 1, x = x, startCol = 10) ## Internal Hyperlink - create hyperlink formula manually writeFormula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")', startCol = 3 ) ## Internal - No text to display using makeHyperlinkString() function writeFormula(wb, "Sheet1", startRow = 1, x = makeHyperlinkString(sheet = "Sheet 3", row = 1, col = 2) ) ## Internal - Text to display writeFormula(wb, "Sheet1", startRow = 2, x = makeHyperlinkString( sheet = "Sheet 3", row = 1, col = 2, text = "Link to Sheet 3" ) ) ## Link to file - No text to display writeFormula(wb, "Sheet1", startRow = 4, x = makeHyperlinkString( sheet = "testing", row = 3, col = 10, file = system.file("extdata", "loadExample.xlsx", package = "openxlsx") ) ) ## Link to file - Text to display writeFormula(wb, "Sheet1", startRow = 3, x = makeHyperlinkString( sheet = "testing", row = 3, col = 10, file = system.file("extdata", "loadExample.xlsx", package = "openxlsx"), text = "Link to File." ) ) ## Link to external file - Text to display writeFormula(wb, "Sheet1", startRow = 10, startCol = 1, x = '=HYPERLINK(\\\\"[C:/Users]\\\\", \\\\"Link to an external file\\\\")' ) ## Link to internal file x = makeHyperlinkString(text = "test.png", file = "D:/somepath/somepicture.png") writeFormula(wb, "Sheet1", startRow = 11, startCol = 1, x = x) \dontrun{ saveWorkbook(wb, "internalHyperlinks.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=writeFormula]{writeFormula()}} } openxlsx/man/modifyBaseFont.Rd0000644000176200001440000000204714155600363016113 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{modifyBaseFont} \alias{modifyBaseFont} \title{Modify the default font} \usage{ modifyBaseFont(wb, fontSize = 11, fontColour = "black", fontName = "Calibri") } \arguments{ \item{wb}{A workbook object} \item{fontSize}{font size} \item{fontColour}{font colour} \item{fontName}{Name of a font} } \description{ Modify the default font for this workbook } \details{ The font name is not validated in anyway. Excel replaces unknown font names with Arial. Base font is black, size 11, Calibri. } \examples{ ## create a workbook wb <- createWorkbook() addWorksheet(wb, "S1") ## modify base font to size 10 Arial Narrow in red modifyBaseFont(wb, fontSize = 10, fontColour = "#FF0000", fontName = "Arial Narrow") writeData(wb, "S1", iris) writeDataTable(wb, "S1", x = iris, startCol = 10) ## font colour does not affect tables \dontrun{ saveWorkbook(wb, "modifyBaseFontExample.xlsx", overwrite = TRUE) } } \author{ Alexander Walker } openxlsx/man/insertImage.Rd0000644000176200001440000000304614155600363015451 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{insertImage} \alias{insertImage} \title{Insert an image into a worksheet} \usage{ insertImage( wb, sheet, file, width = 6, height = 3, startRow = 1, startCol = 1, units = "in", dpi = 300 ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{file}{An image file. Valid file types are: jpeg, png, bmp} \item{width}{Width of figure.} \item{height}{Height of figure.} \item{startRow}{Row coordinate of upper left corner of the image} \item{startCol}{Column coordinate of upper left corner of the image} \item{units}{Units of width and height. Can be "in", "cm" or "px"} \item{dpi}{Image resolution used for conversion between units.} } \description{ Insert an image into a worksheet } \examples{ ## Create a new workbook wb <- createWorkbook("Ayanami") ## Add some worksheets addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") addWorksheet(wb, "Sheet 3") ## Insert images img <- system.file("extdata", "einstein.jpg", package = "openxlsx") insertImage(wb, "Sheet 1", img, startRow = 5, startCol = 3, width = 6, height = 5) insertImage(wb, 2, img, startRow = 2, startCol = 2) insertImage(wb, 3, img, width = 15, height = 12, startRow = 3, startCol = "G", units = "cm") ## Save workbook \dontrun{ saveWorkbook(wb, "insertImageExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=insertPlot]{insertPlot()}} } \author{ Alexander Walker } openxlsx/man/setHeaderFooter.Rd0000644000176200001440000000546314155600364016273 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{setHeaderFooter} \alias{setHeaderFooter} \title{Set document headers and footers} \usage{ setHeaderFooter( wb, sheet, header = NULL, footer = NULL, evenHeader = NULL, evenFooter = NULL, firstHeader = NULL, firstFooter = NULL ) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{header}{document header. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position.} \item{footer}{document footer. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position.} \item{evenHeader}{document header for even pages.} \item{evenFooter}{document footer for even pages.} \item{firstHeader}{document header for first page only.} \item{firstFooter}{document footer for first page only.} } \description{ Set document headers and footers } \details{ Headers and footers can contain special tags \itemize{ \item{\strong{&[Page]}}{ Page number} \item{\strong{&[Pages]}}{ Number of pages} \item{\strong{&[Date]}}{ Current date} \item{\strong{&[Time]}}{ Current time} \item{\strong{&[Path]}}{ File path} \item{\strong{&[File]}}{ File name} \item{\strong{&[Tab]}}{ Worksheet name} } } \examples{ wb <- createWorkbook() addWorksheet(wb, "S1") addWorksheet(wb, "S2") addWorksheet(wb, "S3") addWorksheet(wb, "S4") writeData(wb, 1, 1:400) writeData(wb, 2, 1:400) writeData(wb, 3, 3:400) writeData(wb, 4, 3:400) setHeaderFooter(wb, sheet = "S1", header = c("ODD HEAD LEFT", "ODD HEAD CENTER", "ODD HEAD RIGHT"), footer = c("ODD FOOT RIGHT", "ODD FOOT CENTER", "ODD FOOT RIGHT"), evenHeader = c("EVEN HEAD LEFT", "EVEN HEAD CENTER", "EVEN HEAD RIGHT"), evenFooter = c("EVEN FOOT RIGHT", "EVEN FOOT CENTER", "EVEN FOOT RIGHT"), firstHeader = c("TOP", "OF FIRST", "PAGE"), firstFooter = c("BOTTOM", "OF FIRST", "PAGE") ) setHeaderFooter(wb, sheet = 2, header = c("&[Date]", "ALL HEAD CENTER 2", "&[Page] / &[Pages]"), footer = c("&[Path]&[File]", NA, "&[Tab]"), firstHeader = c(NA, "Center Header of First Page", NA), firstFooter = c(NA, "Center Footer of First Page", NA) ) setHeaderFooter(wb, sheet = 3, header = c("ALL HEAD LEFT 2", "ALL HEAD CENTER 2", "ALL HEAD RIGHT 2"), footer = c("ALL FOOT RIGHT 2", "ALL FOOT CENTER 2", "ALL FOOT RIGHT 2") ) setHeaderFooter(wb, sheet = 4, firstHeader = c("FIRST ONLY L", NA, "FIRST ONLY R"), firstFooter = c("FIRST ONLY L", NA, "FIRST ONLY R") ) \dontrun{ saveWorkbook(wb, "setHeaderFooterExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=addWorksheet]{addWorksheet()}} to set headers and footers when adding a worksheet } \author{ Alexander Walker } openxlsx/man/removeColWidths.Rd0000644000176200001440000000151514155600363016317 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{removeColWidths} \alias{removeColWidths} \title{Remove column widths from a worksheet} \usage{ removeColWidths(wb, sheet, cols) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} \item{cols}{Indices of columns to remove custom width (if any) from.} } \description{ Remove column widths from a worksheet } \examples{ ## Create a new workbook wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) ## remove column widths in columns 1 to 20 removeColWidths(wb, 1, cols = 1:20) \dontrun{ saveWorkbook(wb, "removeColWidthsExample.xlsx", overwrite = TRUE) } } \seealso{ \code{\link[=setColWidths]{setColWidths()}} } \author{ Alexander Walker } openxlsx/man/getTables.Rd0000644000176200001440000000122714155600363015113 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{getTables} \alias{getTables} \title{List Excel tables in a workbook} \usage{ getTables(wb, sheet) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A name or index of a worksheet} } \value{ character vector of table names on the specified sheet } \description{ List Excel tables in a workbook } \examples{ wb <- createWorkbook() addWorksheet(wb, sheetName = "Sheet 1") writeDataTable(wb, sheet = "Sheet 1", x = iris) writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) getTables(wb, sheet = "Sheet 1") } openxlsx/man/removeComment.Rd0000644000176200001440000000133414155600363016020 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/CommentClass.R \name{removeComment} \alias{removeComment} \title{Remove a comment from a cell} \usage{ removeComment(wb, sheet, cols, rows, gridExpand = TRUE) } \arguments{ \item{wb}{A workbook object} \item{sheet}{A vector of names or indices of worksheets} \item{cols}{Columns to delete comments from} \item{rows}{Rows to delete comments from} \item{gridExpand}{If \code{TRUE}, all data in rectangle min(rows):max(rows) X min(cols):max(cols) will be removed.} } \description{ Remove a cell comment from a worksheet } \seealso{ \code{\link[=createComment]{createComment()}} \code{\link[=writeComment]{writeComment()}} } openxlsx/man/createStyle.Rd0000644000176200001440000001415114155600363015465 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{createStyle} \alias{createStyle} \title{Create a cell style} \usage{ createStyle( fontName = NULL, fontSize = NULL, fontColour = NULL, numFmt = openxlsx_getOp("numFmt", "GENERAL"), border = NULL, borderColour = openxlsx_getOp("borderColour", "black"), borderStyle = openxlsx_getOp("borderStyle", "thin"), bgFill = NULL, fgFill = NULL, halign = NULL, valign = NULL, textDecoration = NULL, wrapText = FALSE, textRotation = NULL, indent = NULL, locked = NULL, hidden = NULL ) } \arguments{ \item{fontName}{A name of a font. Note the font name is not validated. If fontName is NULL, the workbook base font is used. (Defaults to Calibri)} \item{fontSize}{Font size. A numeric greater than 0. If fontSize is NULL, the workbook base font size is used. (Defaults to 11)} \item{fontColour}{Colour of text in cell. A valid hex colour beginning with "#" or one of colours(). If fontColour is NULL, the workbook base font colours is used. (Defaults to black)} \item{numFmt}{Cell formatting \itemize{ \item{\strong{GENERAL}} \item{\strong{NUMBER}} \item{\strong{CURRENCY}} \item{\strong{ACCOUNTING}} \item{\strong{DATE}} \item{\strong{LONGDATE}} \item{\strong{TIME}} \item{\strong{PERCENTAGE}} \item{\strong{FRACTION}} \item{\strong{SCIENTIFIC}} \item{\strong{TEXT}} \item{\strong{COMMA}{ for comma separated thousands}} \item{For date/datetime styling a combination of d, m, y and punctuation marks} \item{For numeric rounding use "0.00" with the preferred number of decimal places} }} \item{border}{Cell border. A vector of "top", "bottom", "left", "right" or a single string). \itemize{ \item{\strong{"top"}}{ Top border} \item{\strong{bottom}}{ Bottom border} \item{\strong{left}}{ Left border} \item{\strong{right}}{ Right border} \item{\strong{TopBottom} or \strong{c("top", "bottom")}}{ Top and bottom border} \item{\strong{LeftRight} or \strong{c("left", "right")}}{ Left and right border} \item{\strong{TopLeftRight} or \strong{c("top", "left", "right")}}{ Top, Left and right border} \item{\strong{TopBottomLeftRight} or \strong{c("top", "bottom", "left", "right")}}{ All borders} }} \item{borderColour}{Colour of cell border vector the same length as the number of sides specified in "border" A valid colour (belonging to colours()) or a valid hex colour beginning with "#"} \item{borderStyle}{Border line style vector the same length as the number of sides specified in "border" \itemize{ \item{\strong{none}}{ No Border} \item{\strong{thin}}{ thin border} \item{\strong{medium}}{ medium border} \item{\strong{dashed}}{ dashed border} \item{\strong{dotted}}{ dotted border} \item{\strong{thick}}{ thick border} \item{\strong{double}}{ double line border} \item{\strong{hair}}{ Hairline border} \item{\strong{mediumDashed}}{ medium weight dashed border} \item{\strong{dashDot}}{ dash-dot border} \item{\strong{mediumDashDot}}{ medium weight dash-dot border} \item{\strong{dashDotDot}}{ dash-dot-dot border} \item{\strong{mediumDashDotDot}}{ medium weight dash-dot-dot border} \item{\strong{slantDashDot}}{ slanted dash-dot border} }} \item{bgFill}{Cell background fill colour. A valid colour (belonging to colours()) or a valid hex colour beginning with "#". -- \strong{Use for conditional formatting styles only.}} \item{fgFill}{Cell foreground fill colour. A valid colour (belonging to colours()) or a valid hex colour beginning with "#"} \item{halign}{Horizontal alignment of cell contents \itemize{ \item{\strong{left}}{ Left horizontal align cell contents} \item{\strong{right}}{ Right horizontal align cell contents} \item{\strong{center}}{ Center horizontal align cell contents} \item{\strong{justify}}{ Justify horizontal align cell contents} }} \item{valign}{A name Vertical alignment of cell contents \itemize{ \item{\strong{top}}{ Top vertical align cell contents} \item{\strong{center}}{ Center vertical align cell contents} \item{\strong{bottom}}{ Bottom vertical align cell contents} }} \item{textDecoration}{Text styling. \itemize{ \item{\strong{bold}}{ Bold cell contents} \item{\strong{strikeout}}{ Strikeout cell contents} \item{\strong{italic}}{ Italicise cell contents} \item{\strong{underline}}{ Underline cell contents} \item{\strong{underline2}}{ Double underline cell contents} \item{\strong{accounting}}{ Single accounting underline cell contents} \item{\strong{accounting2}}{ Double accounting underline cell contents} }} \item{wrapText}{Logical. If \code{TRUE} cell contents will wrap to fit in column.} \item{textRotation}{Rotation of text in degrees. 255 for vertical text.} \item{indent}{Horizontal indentation of cell contents.} \item{locked}{Whether cell contents are locked (if worksheet protection is turned on)} \item{hidden}{Whether the formula of the cell contents will be hidden (if worksheet protection is turned on)} } \value{ A style object } \description{ Create a new style to apply to worksheet cells } \examples{ ## See package vignettes for further examples ## Modify default values of border colour and border line style options("openxlsx.borderColour" = "#4F80BD") options("openxlsx.borderStyle" = "thin") ## Size 18 Arial, Bold, left horz. aligned, fill colour #1A33CC, all borders, style <- createStyle( fontSize = 18, fontName = "Arial", textDecoration = "bold", halign = "left", fgFill = "#1A33CC", border = "TopBottomLeftRight" ) ## Red, size 24, Bold, italic, underline, center aligned Font, bottom border style <- createStyle( fontSize = 24, fontColour = rgb(1, 0, 0), textDecoration = c("bold", "italic", "underline"), halign = "center", valign = "center", border = "Bottom" ) # borderColour is recycled for each border or all colours can be supplied # colour is recycled 3 times for "Top", "Bottom" & "Right" sides. createStyle(border = "TopBottomRight", borderColour = "red") # supply all colours createStyle(border = "TopBottomLeft", borderColour = c("red", "yellow", "green")) } \seealso{ \code{\link[=addStyle]{addStyle()}} } \author{ Alexander Walker } openxlsx/DESCRIPTION0000644000176200001440000000525114156124026013644 0ustar liggesusersType: Package Package: openxlsx Title: Read, Write and Edit xlsx Files Version: 4.2.5 Date: 2021-12-13 Authors@R: c(person(given = "Philipp", family = "Schauberger", role = c("aut", "cre"), email = "philipp@schauberger.co.at"), person(given = "Alexander", family = "Walker", role = "aut", email = "Alexander.Walker1989@gmail.com"), person(given = "Luca", family = "Braglia", role = "ctb"), person(given = "Joshua", family = "Sturm", role = "ctb"), person(given = "Jan Marvin", family = "Garbuszus", role = "ctb", email = "jan.garbuszus@ruhr-uni-bochum.de"), person(given = "Jordan Mark", family = "Barbone", role = "ctb", email = "jmbarbone@gmail.com", comment = c(ORCID = "0000-0001-9788-3628"))) Description: Simplifies the creation of Excel .xlsx files by providing a high level interface to writing, styling and editing worksheets. Through the use of 'Rcpp', read/write times are comparable to the 'xlsx' and 'XLConnect' packages with the added benefit of removing the dependency on Java. License: MIT + file LICENSE URL: https://ycphs.github.io/openxlsx/index.html, https://github.com/ycphs/openxlsx BugReports: https://github.com/ycphs/openxlsx/issues Depends: R (>= 3.3.0) Imports: grDevices, methods, Rcpp, stats, stringi, utils, zip Suggests: knitr, rmarkdown, roxygen2, testthat LinkingTo: Rcpp VignetteBuilder: knitr Encoding: UTF-8 Language: en-US RoxygenNote: 7.1.2 Collate: 'CommentClass.R' 'HyperlinkClass.R' 'RcppExports.R' 'class_definitions.R' 'StyleClass.R' 'WorkbookClass.R' 'asserts.R' 'baseXML.R' 'borderFunctions.R' 'build_workbook.R' 'chartsheet_class.R' 'conditional_formatting.R' 'data-fontSizeLookupTables.R' 'helperFunctions.R' 'loadWorkbook.R' 'onUnload.R' 'openXL.R' 'openxlsx-package.R' 'openxlsx.R' 'openxlsxCoerce.R' 'readWorkbook.R' 'sheet_data_class.R' 'utils.R' 'workbook_column_widths.R' 'workbook_read_workbook.R' 'workbook_write_data.R' 'worksheet_class.R' 'wrappers.R' 'writeData.R' 'writeDataTable.R' 'writexlsx.R' 'zzz.R' NeedsCompilation: yes Packaged: 2021-12-13 22:06:56 UTC; PhilippSchauberger Author: Philipp Schauberger [aut, cre], Alexander Walker [aut], Luca Braglia [ctb], Joshua Sturm [ctb], Jan Marvin Garbuszus [ctb], Jordan Mark Barbone [ctb] () Maintainer: Philipp Schauberger Repository: CRAN Date/Publication: 2021-12-14 14:20:06 UTC openxlsx/build/0000755000176200001440000000000014155741777013253 5ustar liggesusersopenxlsx/build/vignette.rds0000644000176200001440000000034714155741777015616 0ustar liggesusersu EG@_a7ƸpK,Z b;:m w8=B> =Ae '|̅R_CUh7r%Eq?+*xT2P Ue`C6eζ~rs; FB@"㇝FWñ6Ü2b`u< ^ZNw ]Idf٢KҶ7љ$2 g <bGopenxlsx/tests/0000755000176200001440000000000014155600364013300 5ustar liggesusersopenxlsx/tests/testthat/0000755000176200001440000000000014156124026015135 5ustar liggesusersopenxlsx/tests/testthat/test-load_read_file_read_equality.R0000644000176200001440000000335414155600364024066 0ustar liggesusers context("Reading from workbook is identical to reading from file") test_that("Reading from loaded workbook", { wb <- createWorkbook() for (i in 1:4) { addWorksheet(wb, sprintf("Sheet %s", i)) } writeData(wb, sheet = 1, x = mtcars, colNames = TRUE, rowNames = TRUE, startRow = 10, startCol = 5, borders = "all") writeData(wb, sheet = 2, x = mtcars, colNames = TRUE, rowNames = FALSE, startRow = 10, startCol = 5, borders = "rows") writeData(wb, sheet = 3, x = mtcars, colNames = FALSE, rowNames = TRUE, startRow = 2, startCol = 2, borders = "columns") writeData(wb, sheet = 4, x = mtcars, colNames = FALSE, rowNames = FALSE, startRow = 12, startCol = 1, borders = "surrounding") tempFile <- temp_xlsx() saveWorkbook(wb, tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) ## colNames = TRUE, rowNames = TRUE x <- read.xlsx(wb, 1, colNames = TRUE, rowNames = TRUE) expect_equal(object = mtcars, expected = x, check.attributes = TRUE) ## colNames = TRUE, rowNames = FALSE x <- read.xlsx(wb, sheet = 2, colNames = TRUE, rowNames = FALSE) expect_equal(object = mtcars, expected = x, check.attributes = FALSE) expect_equal(object = colnames(mtcars), expected = colnames(x), check.attributes = FALSE) ## colNames = FALSE, rowNames = TRUE x <- read.xlsx(wb, sheet = 3, colNames = FALSE, rowNames = TRUE) expect_equal(object = mtcars, expected = x, check.attributes = FALSE) expect_equal(object = rownames(mtcars), expected = rownames(x)) ## colNames = FALSE, rowNames = FALSE x <- read.xlsx(wb, sheet = 4, colNames = FALSE, rowNames = FALSE) expect_equal(object = mtcars, expected = x, check.attributes = FALSE) unlink(tempFile, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-named_regions.R0000644000176200001440000003176114155600364021062 0ustar liggesusers context("Named Regions") test_that("Maintaining Named Regions on Load", { ## create named regions wb <- createWorkbook() addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") ## specify region writeData(wb, sheet = 1, x = iris, startCol = 1, startRow = 1) createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = seq_len(nrow(iris) + 1), cols = seq_len(ncol(iris)) ) ## using writeData 'name' argument writeData(wb, sheet = 1, x = iris, name = "iris2", startCol = 10) ## Named region size 1 writeData(wb, sheet = 2, x = 99, name = "region1", startCol = 3, startRow = 3) ## save file for testing out_file <- temp_xlsx() saveWorkbook(wb, out_file, overwrite = TRUE) expect_equal(object = getNamedRegions(wb), expected = getNamedRegions(out_file)) df1 <- read.xlsx(wb, namedRegion = "iris") df2 <- read.xlsx(out_file, namedRegion = "iris") expect_equal(df1, df2) df1 <- read.xlsx(wb, namedRegion = "region1") expect_s3_class(df1, "data.frame") expect_equal(nrow(df1), 0) expect_equal(ncol(df1), 1) df1 <- read.xlsx(wb, namedRegion = "region1", colNames = FALSE) expect_s3_class(df1, "data.frame") expect_equal(nrow(df1), 1) expect_equal(ncol(df1), 1) df1 <- read.xlsx(wb, namedRegion = "region1", rowNames = TRUE) expect_s3_class(df1, "data.frame") expect_equal(nrow(df1), 0) expect_equal(ncol(df1), 0) }) test_that("Correctly Loading Named Regions Created in Excel", { # Load an excel workbook (in the repo, it's located in the /inst folder; # when installed on the user's system, it is located in the installation folder # of the package) filename <- system.file("extdata", "namedRegions.xlsx", package = "openxlsx") # Load this workbook. We will test read.xlsx by passing both the object wb and # the filename. Both should produce the same results. wb <- loadWorkbook(filename) # NamedTable refers to Sheet1!$C$5:$D$8 table_f <- read.xlsx(filename, namedRegion = "NamedTable" ) table_w <- read.xlsx(wb, namedRegion = "NamedTable" ) expect_equal(object = table_f, expected = table_w) expect_equal(object = class(table_f), expected = "data.frame") expect_equal(object = ncol(table_f), expected = 2) expect_equal(object = nrow(table_f), expected = 3) # NamedCell refers to Sheet1!$C$2 # This proeduced an error in an earlier version of the pacage when the object # wb was passed, but worked correctly when the filename was passed to read.xlsx cell_f <- read.xlsx(filename, namedRegion = "NamedCell", colNames = FALSE, rowNames = FALSE ) cell_w <- read.xlsx(wb, namedRegion = "NamedCell", colNames = FALSE, rowNames = FALSE ) expect_equal(object = cell_f, expected = cell_w) expect_equal(object = class(cell_f), expected = "data.frame") expect_equal(object = ncol(cell_f), expected = 1) expect_equal(object = nrow(cell_f), expected = 1) # NamedCell2 refers to Sheet1!$C$2:$C$2 cell2_f <- read.xlsx(filename, namedRegion = "NamedCell2", colNames = FALSE, rowNames = FALSE ) cell2_w <- read.xlsx(wb, namedRegion = "NamedCell2", colNames = FALSE, rowNames = FALSE ) expect_equal(object = cell2_f, expected = cell2_w) expect_equal(object = class(cell2_f), expected = "data.frame") expect_equal(object = ncol(cell2_f), expected = 1) expect_equal(object = nrow(cell2_f), expected = 1) }) test_that("Load names from an Excel file with funky non-region names", { filename <- system.file("extdata", "namedRegions2.xlsx", package = "openxlsx") wb <- loadWorkbook(filename) names <- getNamedRegions(wb) sheets <- attr(names, "sheet") positions <- attr(names, "position") expect_true(length(names) == length(sheets)) expect_true(length(names) == length(positions)) expect_equal( head(names, 5), c("barref", "barref", "fooref", "fooref", "IQ_CH") ) expect_equal( sheets, c( "Sheet with space", "Sheet1", "Sheet with space", "Sheet1", rep("", 26) ) ) expect_equal(positions, c("B4", "B4", "B3", "B3", rep("", 26))) names2 <- getNamedRegions(filename) expect_equal(names, names2) }) test_that("Missing rows in named regions", { temp_file <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## create region writeData(wb, sheet = 1, x = iris[1:11, ], startCol = 1, startRow = 1) deleteData(wb, sheet = 1, cols = 1:2, rows = c(6, 6)) createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = 1:(5 + 1), cols = 1:2 ) createNamedRegion( wb = wb, sheet = 1, name = "iris2", rows = 1:(5 + 2), cols = 1:2 ) ## iris region is rows 1:6 & cols 1:2 ## iris2 region is rows 1:7 & cols 1:2 ## row 6 columns 1 & 2 are blank expect_equal(getNamedRegions(wb)[1:2], c("iris", "iris2"), ignore.attributes = TRUE) expect_equal(attr(getNamedRegions(wb), "sheet"), c("Sheet 1", "Sheet 1")) expect_equal(attr(getNamedRegions(wb), "position"), c("A1:B6", "A1:B7")) ######################################################################## from Workbook ## Skip empty rows x <- read.xlsx(xlsxFile = wb, namedRegion = "iris", colNames = TRUE, skipEmptyRows = TRUE) expect_equal(dim(x), c(4, 2)) x <- read.xlsx(xlsxFile = wb, namedRegion = "iris2", colNames = TRUE, skipEmptyRows = TRUE) expect_equal(dim(x), c(5, 2)) ## Keep empty rows x <- read.xlsx(xlsxFile = wb, namedRegion = "iris", colNames = TRUE, skipEmptyRows = FALSE) expect_equal(dim(x), c(5, 2)) x <- read.xlsx(xlsxFile = wb, namedRegion = "iris2", colNames = TRUE, skipEmptyRows = FALSE) expect_equal(dim(x), c(6, 2)) ######################################################################## from file saveWorkbook(wb, file = temp_file, overwrite = TRUE) ## Skip empty rows x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris", colNames = TRUE, skipEmptyRows = TRUE) expect_equal(dim(x), c(4, 2)) x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris2", colNames = TRUE, skipEmptyRows = TRUE) expect_equal(dim(x), c(5, 2)) ## Keep empty rows x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris", colNames = TRUE, skipEmptyRows = FALSE) expect_equal(dim(x), c(5, 2)) x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris2", colNames = TRUE, skipEmptyRows = FALSE) expect_equal(dim(x), c(6, 2)) unlink(temp_file) }) test_that("Missing columns in named regions", { temp_file <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## create region writeData(wb, sheet = 1, x = iris[1:11, ], startCol = 1, startRow = 1) deleteData(wb, sheet = 1, cols = 2, rows = 1:12, gridExpand = TRUE) createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = 1:5, cols = 1:2 ) createNamedRegion( wb = wb, sheet = 1, name = "iris2", rows = 1:5, cols = 1:3 ) ## iris region is rows 1:5 & cols 1:2 ## iris2 region is rows 1:5 & cols 1:3 ## row 6 columns 1 & 2 are blank expect_equal(getNamedRegions(wb)[1:2], c("iris", "iris2"), ignore.attributes = TRUE) expect_equal(attr(getNamedRegions(wb), "sheet"), c("Sheet 1", "Sheet 1")) expect_equal(attr(getNamedRegions(wb), "position"), c("A1:B5", "A1:C5")) ######################################################################## from Workbook ## Skip empty cols x <- read.xlsx(xlsxFile = wb, namedRegion = "iris", colNames = TRUE, skipEmptyCols = TRUE) expect_equal(dim(x), c(4, 1)) x <- read.xlsx(xlsxFile = wb, namedRegion = "iris2", colNames = TRUE, skipEmptyCols = TRUE) expect_equal(dim(x), c(4, 2)) ## Keep empty cols x <- read.xlsx(xlsxFile = wb, namedRegion = "iris", colNames = TRUE, skipEmptyCols = FALSE) expect_equal(dim(x), c(4, 1)) x <- read.xlsx(xlsxFile = wb, namedRegion = "iris2", colNames = TRUE, skipEmptyCols = FALSE) expect_equal(dim(x), c(4, 3)) ######################################################################## from file saveWorkbook(wb, file = temp_file, overwrite = TRUE) ## Skip empty cols x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris", colNames = TRUE, skipEmptyCols = TRUE) expect_equal(dim(x), c(4, 1)) x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris2", colNames = TRUE, skipEmptyCols = TRUE) expect_equal(dim(x), c(4, 2)) ## Keep empty cols x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris", colNames = TRUE, skipEmptyCols = FALSE) expect_equal(dim(x), c(4, 1)) x <- read.xlsx(xlsxFile = temp_file, namedRegion = "iris2", colNames = TRUE, skipEmptyCols = FALSE) expect_equal(dim(x), c(4, 3)) unlink(temp_file) }) test_that("Matching Substrings breaks reading named regions", { temp_file <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, "table") addWorksheet(wb, "table2") t1 <- head(iris) t1$Species <- as.character(t1$Species) t2 <- head(mtcars) writeData(wb, sheet = "table", x = t1, name = "t", startCol = 3, startRow = 12) writeData(wb, sheet = "table2", x = t2, name = "t2", startCol = 5, startRow = 24, rowNames = TRUE) writeData(wb, sheet = "table", x = head(t1, 3), name = "t1", startCol = 9, startRow = 3) writeData(wb, sheet = "table2", x = head(t2, 3), name = "t22", startCol = 15, startRow = 12, rowNames = TRUE) saveWorkbook(wb, file = temp_file, overwrite = TRUE) r1 <- getNamedRegions(wb) expect_equal(attr(r1, "sheet"), c("table", "table2", "table", "table2")) expect_equal(attr(r1, "position"), c("C12:G18", "E24:P30", "I3:M6", "O12:Z15")) expect_equal(r1, c("t", "t2", "t1", "t22"), check.attributes = FALSE) r2 <- getNamedRegions(temp_file) expect_equal(attr(r2, "sheet"), c("table", "table2", "table", "table2")) expect_equal(attr(r1, "position"), c("C12:G18", "E24:P30", "I3:M6", "O12:Z15")) expect_equal(r2, c("t", "t2", "t1", "t22"), check.attributes = FALSE) ## read file named region expect_equal(t1, read.xlsx(xlsxFile = temp_file, namedRegion = "t")) expect_equal(t2, read.xlsx(xlsxFile = temp_file, namedRegion = "t2", rowNames = TRUE)) expect_equal(head(t1, 3), read.xlsx(xlsxFile = temp_file, namedRegion = "t1")) expect_equal(head(t2, 3), read.xlsx(xlsxFile = temp_file, namedRegion = "t22", rowNames = TRUE)) ## read Workbook named region expect_equal(t1, read.xlsx(xlsxFile = wb, namedRegion = "t")) expect_equal(t2, read.xlsx(xlsxFile = wb, namedRegion = "t2", rowNames = TRUE)) expect_equal(head(t1, 3), read.xlsx(xlsxFile = wb, namedRegion = "t1")) expect_equal(head(t2, 3), read.xlsx(xlsxFile = wb, namedRegion = "t22", rowNames = TRUE)) unlink(temp_file) }) test_that("Read namedRegion from specific sheet", { filename <- system.file("extdata", "namedRegions3.xlsx", package = "openxlsx") namedR <- "MyRange" sheets <- openxlsx::getSheetNames(filename) # read the correct sheets expect_equal(data.frame(X1 = "S1A1", X2 = "S1B1", stringsAsFactors = FALSE), read.xlsx(filename, sheet = "Sheet1", namedRegion = namedR, rowNames = FALSE, colNames = FALSE)) expect_equal(data.frame(X1 = "S2A1", X2 = "S2B1", stringsAsFactors = FALSE), read.xlsx(filename, sheet = which(sheets %in% "Sheet2"), namedRegion = namedR, rowNames = FALSE, colNames = FALSE)) expect_equal(data.frame(X1 = "S3A1", X2 = "S3B1", stringsAsFactors = FALSE), read.xlsx(filename, sheet = "Sheet3", namedRegion = namedR, rowNames = FALSE, colNames = FALSE)) # Warning: Workbook has no such named region. (Wrong namedRegion selected.) expect_warning(read.xlsx(filename, sheet = "Sheet2", namedRegion = "MyRage", rowNames = FALSE, colNames = FALSE)) # Warning: Workbook has no such named region on this sheet. (Correct namedRegion, but wrong sheet selected.) expect_warning(read.xlsx(filename, sheet = "Sheet4", namedRegion = namedR, rowNames = FALSE, colNames = FALSE)) }) test_that("Overwrite and delete named regions", { temp_file <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## create region writeData(wb, sheet = 1, x = iris[1:11, ], startCol = 1, startRow = 1, name = "iris") init_nr <- getNamedRegions(wb) expect_equal(attr(init_nr, "position"), "A1:E12") # no overwrite expect_error({ writeData(wb, sheet = 1, x = iris[1:11, ], startCol = 1, startRow = 1, name = "iris") }) expect_error({ createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = 1:5, cols = 1:2 ) }) # overwrite createNamedRegion( wb = wb, sheet = 1, name = "iris", rows = 1:5, cols = 1:2, overwrite = TRUE ) # check midification modify_nr <- getNamedRegions(wb) expect_equal(attr(modify_nr, "position"), "A1:B5") expect_true("iris" %in% modify_nr) # delete name region deleteNamedRegion(wb, "iris") expect_false("iris" %in% getNamedRegions(wb)) }) openxlsx/tests/testthat/test-write_data_to_sheetData.R0000644000176200001440000002616514155600364023061 0ustar liggesusers context("Converting R types to Excel types") test_that("Converting R types to Excel types", { wb <- createWorkbook() addWorksheet(wb, "S1") addWorksheet(wb, "S2") addWorksheet(wb, "S3") writeDataTable(wb, "S1", x = iris) n_values <- prod(dim(iris)) + ncol(iris) sheet_data <- wb$worksheets[[1]]$sheet_data sheet_v <- sheet_data$v sheet_t <- sheet_data$t sheet_f <- sheet_data$f sheet_row <- sheet_data$rows sheet_col <- sheet_data$cols sheet_v <- as.numeric(sheet_v) expect_length(sheet_row, n_values) expect_length(sheet_col, n_values) expect_length(sheet_t, n_values) expect_length(sheet_v, n_values) expect_length(sheet_f, n_values) ## rows/cols expect_equal(sheet_row, rep(1:151, each = 5)) expect_equal(sheet_col, rep(1:5, times = 151)) ## header types expect_equal(sheet_t[1:5], rep(1, 5)) ## data.frame t & v expect_equal(sheet_t[6:n_values], rep(c(0, 0, 0, 0, 1), 150)) expect_equal(sheet_v[1:5], 0:4) expected_v <- c( 5.1, 3.5, 1.4, 0.2, 5, 4.9, 3, 1.4, 0.2, 5, 4.7, 3.2, 1.3, 0.2, 5, 4.6, 3.1, 1.5, 0.2, 5, 5, 3.6, 1.4, 0.2, 5, 5.4, 3.9, 1.7, 0.4, 5, 4.6, 3.4, 1.4, 0.3, 5, 5, 3.4, 1.5, 0.2, 5, 4.4, 2.9, 1.4, 0.2, 5, 4.9, 3.1, 1.5, 0.1, 5, 5.4, 3.7, 1.5, 0.2, 5, 4.8, 3.4, 1.6, 0.2, 5, 4.8, 3, 1.4, 0.1, 5, 4.3, 3, 1.1, 0.1, 5, 5.8, 4, 1.2, 0.2, 5, 5.7, 4.4, 1.5, 0.4, 5, 5.4, 3.9, 1.3, 0.4, 5, 5.1, 3.5, 1.4, 0.3, 5, 5.7, 3.8, 1.7, 0.3, 5, 5.1, 3.8, 1.5, 0.3, 5, 5.4, 3.4, 1.7, 0.2, 5, 5.1, 3.7, 1.5, 0.4, 5, 4.6, 3.6, 1, 0.2, 5, 5.1, 3.3, 1.7, 0.5, 5, 4.8, 3.4, 1.9, 0.2, 5, 5, 3, 1.6, 0.2, 5, 5, 3.4, 1.6, 0.4, 5, 5.2, 3.5, 1.5, 0.2, 5, 5.2, 3.4, 1.4, 0.2, 5, 4.7, 3.2, 1.6, 0.2, 5, 4.8, 3.1, 1.6, 0.2, 5, 5.4, 3.4, 1.5, 0.4, 5, 5.2, 4.1, 1.5, 0.1, 5, 5.5, 4.2, 1.4, 0.2, 5, 4.9, 3.1, 1.5, 0.2, 5, 5, 3.2, 1.2, 0.2, 5, 5.5, 3.5, 1.3, 0.2, 5, 4.9, 3.6, 1.4, 0.1, 5, 4.4, 3, 1.3, 0.2, 5, 5.1, 3.4, 1.5, 0.2, 5, 5, 3.5, 1.3, 0.3, 5, 4.5, 2.3, 1.3, 0.3, 5, 4.4, 3.2, 1.3, 0.2, 5, 5, 3.5, 1.6, 0.6, 5, 5.1, 3.8, 1.9, 0.4, 5, 4.8, 3, 1.4, 0.3, 5, 5.1, 3.8, 1.6, 0.2, 5, 4.6, 3.2, 1.4, 0.2, 5, 5.3, 3.7, 1.5, 0.2, 5, 5, 3.3, 1.4, 0.2, 5, 7, 3.2, 4.7, 1.4, 6, 6.4, 3.2, 4.5, 1.5, 6, 6.9, 3.1, 4.9, 1.5, 6, 5.5, 2.3, 4, 1.3, 6, 6.5, 2.8, 4.6, 1.5, 6, 5.7, 2.8, 4.5, 1.3, 6, 6.3, 3.3, 4.7, 1.6, 6, 4.9, 2.4, 3.3, 1, 6, 6.6, 2.9, 4.6, 1.3, 6, 5.2, 2.7, 3.9, 1.4, 6, 5, 2, 3.5, 1, 6, 5.9, 3, 4.2, 1.5, 6, 6, 2.2, 4, 1, 6, 6.1, 2.9, 4.7, 1.4, 6, 5.6, 2.9, 3.6, 1.3, 6, 6.7, 3.1, 4.4, 1.4, 6, 5.6, 3, 4.5, 1.5, 6, 5.8, 2.7, 4.1, 1, 6, 6.2, 2.2, 4.5, 1.5, 6, 5.6, 2.5, 3.9, 1.1, 6, 5.9, 3.2, 4.8, 1.8, 6, 6.1, 2.8, 4, 1.3, 6, 6.3, 2.5, 4.9, 1.5, 6, 6.1, 2.8, 4.7, 1.2, 6, 6.4, 2.9, 4.3, 1.3, 6, 6.6, 3, 4.4, 1.4, 6, 6.8, 2.8, 4.8, 1.4, 6, 6.7, 3, 5, 1.7, 6, 6, 2.9, 4.5, 1.5, 6, 5.7, 2.6, 3.5, 1, 6, 5.5, 2.4, 3.8, 1.1, 6, 5.5, 2.4, 3.7, 1, 6, 5.8, 2.7, 3.9, 1.2, 6, 6, 2.7, 5.1, 1.6, 6, 5.4, 3, 4.5, 1.5, 6, 6, 3.4, 4.5, 1.6, 6, 6.7, 3.1, 4.7, 1.5, 6, 6.3, 2.3, 4.4, 1.3, 6, 5.6, 3, 4.1, 1.3, 6, 5.5, 2.5, 4, 1.3, 6, 5.5, 2.6, 4.4, 1.2, 6, 6.1, 3, 4.6, 1.4, 6, 5.8, 2.6, 4, 1.2, 6, 5, 2.3, 3.3, 1, 6, 5.6, 2.7, 4.2, 1.3, 6, 5.7, 3, 4.2, 1.2, 6, 5.7, 2.9, 4.2, 1.3, 6, 6.2, 2.9, 4.3, 1.3, 6, 5.1, 2.5, 3, 1.1, 6, 5.7, 2.8, 4.1, 1.3, 6, 6.3, 3.3, 6, 2.5, 7, 5.8, 2.7, 5.1, 1.9, 7, 7.1, 3, 5.9, 2.1, 7, 6.3, 2.9, 5.6, 1.8, 7, 6.5, 3, 5.8, 2.2, 7, 7.6, 3, 6.6, 2.1, 7, 4.9, 2.5, 4.5, 1.7, 7, 7.3, 2.9, 6.3, 1.8, 7, 6.7, 2.5, 5.8, 1.8, 7, 7.2, 3.6, 6.1, 2.5, 7, 6.5, 3.2, 5.1, 2, 7, 6.4, 2.7, 5.3, 1.9, 7, 6.8, 3, 5.5, 2.1, 7, 5.7, 2.5, 5, 2, 7, 5.8, 2.8, 5.1, 2.4, 7, 6.4, 3.2, 5.3, 2.3, 7, 6.5, 3, 5.5, 1.8, 7, 7.7, 3.8, 6.7, 2.2, 7, 7.7, 2.6, 6.9, 2.3, 7, 6, 2.2, 5, 1.5, 7, 6.9, 3.2, 5.7, 2.3, 7, 5.6, 2.8, 4.9, 2, 7, 7.7, 2.8, 6.7, 2, 7, 6.3, 2.7, 4.9, 1.8, 7, 6.7, 3.3, 5.7, 2.1, 7, 7.2, 3.2, 6, 1.8, 7, 6.2, 2.8, 4.8, 1.8, 7, 6.1, 3, 4.9, 1.8, 7, 6.4, 2.8, 5.6, 2.1, 7, 7.2, 3, 5.8, 1.6, 7, 7.4, 2.8, 6.1, 1.9, 7, 7.9, 3.8, 6.4, 2, 7, 6.4, 2.8, 5.6, 2.2, 7, 6.3, 2.8, 5.1, 1.5, 7, 6.1, 2.6, 5.6, 1.4, 7, 7.7, 3, 6.1, 2.3, 7, 6.3, 3.4, 5.6, 2.4, 7, 6.4, 3.1, 5.5, 1.8, 7, 6, 3, 4.8, 1.8, 7, 6.9, 3.1, 5.4, 2.1, 7, 6.7, 3.1, 5.6, 2.4, 7, 6.9, 3.1, 5.1, 2.3, 7, 5.8, 2.7, 5.1, 1.9, 7, 6.8, 3.2, 5.9, 2.3, 7, 6.7, 3.3, 5.7, 2.5, 7, 6.7, 3, 5.2, 2.3, 7, 6.3, 2.5, 5, 1.9, 7, 6.5, 3, 5.2, 2, 7, 6.2, 3.4, 5.4, 2.3, 7, 5.9, 3, 5.1, 1.8, 7 ) expect_equal(sheet_v[6:n_values], expected_v) ############################ SPECIAL DATA TYPES df <- data.frame( "Date" = as.Date("2016-12-5") - 0:19, "T" = TRUE, "F" = FALSE, "Time" = as.POSIXct("2016-12-05 20:31:12 AEDT") - 0:19 * 60 * 60, "Cash" = paste("$", 1:20), "Cash2" = 31:50, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(0, 1, length.out = 20), "TinyNumbers" = 1:20 / 1E9, stringsAsFactors = FALSE ) ## openxlsx will apply default Excel styling for these classes class(df$Cash) <- "currency" class(df$Cash2) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumbers) <- "scientific" writeDataTable(wb, "S3", x = df, startRow = 4, rowNames = TRUE, tableStyle = "TableStyleMedium9") ## Get all data sheet_data <- wb$worksheets[[3]]$sheet_data n_values <- (nrow(df) + 1) * (ncol(df) + 1) sheet_v <- sheet_data$v sheet_t <- sheet_data$t sheet_f <- sheet_data$f sheet_row <- sheet_data$rows sheet_col <- sheet_data$cols sheet_v <- as.numeric(sheet_v) expect_length(sheet_row, n_values) expect_length(sheet_t, n_values) ## rows/cols expect_equal(sheet_row, rep(4:24, each = ncol(df) + 1L)) expect_equal(sheet_col, rep(1:10, times = nrow(df) + 1L)) ## header types expect_equal(sheet_t[1:(ncol(df) + 1)], rep(1, ncol(df) + 1)) ## data.frame t & v expect_equal(sheet_t[(ncol(df) + 2):n_values], rep(c(1, 0, 2, 2, 0, 0, 0, 1, 0, 0), 20)) expect_equal(sheet_v[1:(ncol(df) + 1)], 8:17) expected_v <- c( 18, 42709, 1, 0, 42709.86, 1, 31, 19, 0, 0.000000001, 20, 42708, 1, 0, 42709.81, 2, 32, 19, 0.05263158, 0.000000002, 21, 42707, 1, 0, 42709.77, 3, 33, 19, 0.10526316, 0.000000003, 22, 42706, 1, 0, 42709.73, 4, 34, 19, 0.15789474, 0.000000004, 23, 42705, 1, 0, 42709.69, 5, 35, 19, 0.21052632, 0.000000005, 24, 42704, 1, 0, 42709.65, 6, 36, 19, 0.26315789, 0.000000006, 25, 42703, 1, 0, 42709.61, 7, 37, 19, 0.31578947, 0.000000007, 26, 42702, 1, 0, 42709.56, 8, 38, 19, 0.36842105, 0.000000008, 27, 42701, 1, 0, 42709.52, 9, 39, 19, 0.42105263, 0.000000009, 28, 42700, 1, 0, 42709.48, 10, 40, 19, 0.47368421, 0.00000001, 29, 42699, 1, 0, 42709.44, 11, 41, 19, 0.52631579, 0.000000011, 30, 42698, 1, 0, 42709.4, 12, 42, 19, 0.57894737, 0.000000012, 31, 42697, 1, 0, 42709.36, 13, 43, 19, 0.63157895, 0.000000013, 32, 42696, 1, 0, 42709.31, 14, 44, 19, 0.68421053, 0.000000014, 33, 42695, 1, 0, 42709.27, 15, 45, 19, 0.73684211, 0.000000015, 34, 42694, 1, 0, 42709.23, 16, 46, 19, 0.78947368, 0.000000016, 35, 42693, 1, 0, 42709.19, 17, 47, 19, 0.84210526, 0.000000017, 36, 42692, 1, 0, 42709.15, 18, 48, 19, 0.89473684, 0.000000018, 37, 42691, 1, 0, 42709.11, 19, 49, 19, 0.94736842, 0.000000019, 38, 42690, 1, 0, 42709.06, 20, 50, 19, 1, 0.00000002 ) # expect_equal(sheet_v[(ncol(df)+2):n_values], expected_v) }) test_that("Write zero rows & columns", { tempFile <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, "s1") addWorksheet(wb, "s2") ## ZERO ROWS ## headers only writeData(wb, sheet = 1, x = mtcars[0, ], colNames = TRUE, rowNames = FALSE) ## no headers writeData(wb, sheet = 1, x = mtcars[0, ], colNames = FALSE, rowNames = FALSE, startRow = 5) ## row names writeData(wb, sheet = 1, x = mtcars[0, ], colNames = TRUE, rowNames = TRUE, startRow = 10) ## row names only writeData(wb, sheet = 1, x = mtcars[0, ], colNames = FALSE, rowNames = TRUE, startRow = 15) ## ZERO COLS ## headers only writeData(wb, sheet = 2, x = mtcars[, 0], colNames = TRUE, rowNames = FALSE) ## no headers writeData(wb, sheet = 2, x = mtcars[, 0], colNames = FALSE, rowNames = FALSE, startRow = 5) ## row names writeData(wb, sheet = 2, x = mtcars[, 0], colNames = TRUE, rowNames = TRUE, startRow = 10) ## row names only writeData(wb, sheet = 2, x = mtcars[, 0], colNames = FALSE, rowNames = TRUE, startRow = 15) saveWorkbook(wb, tempFile, overwrite = TRUE) unlink(tempFile) }) test_that("too much data", { wb <- createWorkbook() addWorksheet(wb, "test1") addWorksheet(wb, "test2") df1 <- data.frame(Col1 = paste(rep(1, 32768 + 100), collapse = "")) df2 <- data.frame(Col1 = paste(rep(1, 32768), collapse = "")) expect_warning( writeData(wb, 1, df1), "1 is truncated. Number of characters exeed the limit of 32767." ) expect_warning( writeData(wb, 2, df2), "1 is truncated. Number of characters exeed the limit of 32767." ) }) # example from gh issue #200 test_that("write hyperlinks", { tmp <- openxlsx:::temp_xlsx() tmp_dir <- tempdir() # create data channels <- data.frame( channel = c("ABC", "BBC", "CBC"), homepage = c("https://www.abc.net.au/", "https://www.bbc.com/", "https://www.cbc.ca/"), stringsAsFactors = FALSE ) channels$formula <- paste0('=HYPERLINK("', channels$homepage, '","', channels$channel, '")') # create xlsx wb <- createWorkbook() addWorksheet(wb, "channels") writeDataTable(wb, "channels", channels, tableName = "channels") writeFormula(wb, "channels", channels$formula, startRow = 2, startCol = 1) freezePane(wb, "channels", firstRow = TRUE) setColWidths(wb, "channels", cols = seq_along(channels), widths = "auto") saveWorkbook(wb, file = tmp, overwrite = TRUE) # check the xls file for the correct string unzip(tmp, exdir = tmp_dir) sheet1 <- readLines(paste0(tmp_dir, "/xl/worksheets/sheet1.xml"), warn = FALSE) res <- sapply(replaceIllegalCharacters(channels$formula), FUN = function(str)grepl(str, x = sheet1, fixed = TRUE)) expect_true(all(res)) }) test_that("write list containing NA",{ data <- data.frame(i=1:3) data$x <- list(1, c(2, 3), c(4, NA, 5)) xlsx_file <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, "Sheet1") writeData(wb, sheet = 1, data, sep = ";", na.string = "") saveWorkbook(wb, file = xlsx_file, overwrite=TRUE) res <- read.xlsx(xlsx_file) exp <- data.frame(i = 1:3, x = c("1", "2;3", "4;;5"), stringsAsFactors = FALSE) expect_equal(exp, res) }) openxlsx/tests/testthat/test-read_from_created_wb.R0000644000176200001440000002770214155600364022365 0ustar liggesusers context("Reading from wb object is identical to reading from file") test_that("Reading from new workbook", { curr_wd <- getwd() wb <- createWorkbook() for (i in 1:4) { addWorksheet(wb, sprintf("Sheet %s", i)) } ## colNames = TRUE, rowNames = TRUE writeData(wb, sheet = 1, x = mtcars, colNames = TRUE, rowNames = TRUE, startRow = 10, startCol = 5) x <- read.xlsx(wb, 1, colNames = TRUE, rowNames = TRUE) expect_equal(object = mtcars, expected = x, check.attributes = TRUE) ## colNames = TRUE, rowNames = FALSE writeData(wb, sheet = 2, x = mtcars, colNames = TRUE, rowNames = FALSE, startRow = 10, startCol = 5) x <- read.xlsx(wb, sheet = 2, colNames = TRUE, rowNames = FALSE) expect_equal(object = mtcars, expected = x, check.attributes = FALSE) expect_equal(object = colnames(mtcars), expected = colnames(x), check.attributes = FALSE) ## colNames = FALSE, rowNames = TRUE writeData(wb, sheet = 3, x = mtcars, colNames = FALSE, rowNames = TRUE, startRow = 2, startCol = 2) x <- read.xlsx(wb, sheet = 3, colNames = FALSE, rowNames = TRUE) expect_equal(object = mtcars, expected = x, check.attributes = FALSE) expect_equal(object = rownames(mtcars), expected = rownames(x)) ## colNames = FALSE, rowNames = FALSE writeData(wb, sheet = 4, x = mtcars, colNames = FALSE, rowNames = FALSE, startRow = 12, startCol = 1) x <- read.xlsx(wb, sheet = 4, colNames = FALSE, rowNames = FALSE) expect_equal(object = mtcars, expected = x, check.attributes = FALSE) expect_equal(object = getwd(), curr_wd) rm(wb) }) test_that("Empty workbook", { curr_wd <- getwd() wb <- createWorkbook() addWorksheet(wb, "Sheet 1") expect_equal(NULL, suppressWarnings(read.xlsx(wb))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = FALSE))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = TRUE))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = FALSE))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = FALSE))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = FALSE))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE, rows = 4:10))) expect_equal(NULL, suppressWarnings(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = FALSE, cols = 4:10))) expect_warning(read.xlsx(wb)) expect_warning(read.xlsx(wb, sheet = 1, colNames = FALSE)) expect_warning(read.xlsx(wb, sheet = 1, colNames = TRUE)) expect_warning(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = FALSE)) expect_warning(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = FALSE)) expect_warning(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE)) expect_warning(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = FALSE)) expect_warning(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE, rows = 4:10)) expect_warning(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = FALSE, cols = 4:10)) ## 1 element writeData(wb, 1, "a") x <- read.xlsx(wb) expect_equal(nrow(x), 0) expect_equal(names(x), "a") x <- read.xlsx(wb, sheet = 1, colNames = FALSE) expect_equal(data.frame("X1" = "a", stringsAsFactors = FALSE), x) x <- read.xlsx(wb, sheet = 1, colNames = TRUE) expect_equal(nrow(x), 0) expect_equal(names(x), "a") x <- read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = FALSE) expect_equal(data.frame("X1" = "a", stringsAsFactors = FALSE), x) x <- read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = FALSE) expect_equal(nrow(x), 0) expect_equal(names(x), "a") writeData(wb, 1, Sys.Date(), startCol = 1, startRow = 1) x <- read.xlsx(wb) expect_equal(nrow(x), 0) expect_equal(convertToDate(as.integer(names(x)[1])), Sys.Date()) x <- read.xlsx(wb, sheet = 1, colNames = FALSE) expect_equal(nrow(x), 1) x <- read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE) expect_equal(class(x[[1]]), "Date") x <- read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE) expect_equal(x[[1]], Sys.Date()) x <- suppressWarnings(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE, rows = 4:10)) expect_equal(NULL, x) x <- suppressWarnings(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = FALSE, cols = 4:10)) expect_equal(NULL, x) addWorksheet(wb, "Sheet 2") removeWorksheet(wb, 1) ## 1 date writeData(wb, 1, Sys.Date(), colNames = FALSE) x <- read.xlsx(wb) expect_equal(convertToDate(names(x)), Sys.Date()) x <- read.xlsx(wb, sheet = 1, colNames = FALSE) x1 <- convertToDate(x[[1]]) expect_equal(x1, Sys.Date()) x <- read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE) expect_equal(class(x[[1]]), "Date") expect_equal(x[[1]], Sys.Date()) x <- read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = TRUE) expect_equal(as.Date(names(x)), Sys.Date()) x <- suppressWarnings(read.xlsx(wb, sheet = 1, colNames = FALSE, skipEmptyRows = TRUE, detectDates = TRUE, rows = 4:10)) expect_equal(NULL, x) x <- suppressWarnings(read.xlsx(wb, sheet = 1, colNames = TRUE, skipEmptyRows = TRUE, detectDates = FALSE, cols = 4:10)) expect_equal(NULL, x) expect_equal(object = getwd(), curr_wd) }) test_that("Reading NAs and NaN values", { fileName <- file.path(tempdir(), "NaN.xlsx") na.string <- "*" ## data a <- data.frame( X = c(-pi / 0, NA, NaN), Y = letters[1:3], Z = c(pi / 0, 99, NaN), Z2 = c(1, NaN, NaN), stringsAsFactors = FALSE ) b <- a b[b == -Inf] <- NaN b[b == Inf] <- NaN c <- b is_na <- sapply(c, is.na) is_nan <- sapply(c, is.nan) c[is_na & !is_nan] <- na.string is_nan_after <- sapply(c, is.nan) c[is_nan & !is_nan_after] <- NA wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, 1, a, keepNA = FALSE) addWorksheet(wb, "Sheet 2") writeData(wb, 2, a, keepNA = TRUE) addWorksheet(wb, "Sheet 3") writeData(wb, 3, a, keepNA = TRUE, na.string = na.string) saveWorkbook(wb, file = fileName, overwrite = TRUE) ## from file expected_df <- structure(list( X = c(NA_real_, NA_real_, NA_real_), Y = c("a", "b", "c"), Z = c(NA, 99, NA), Z2 = c(1, NA, NA) ), .Names = c("X", "Y", "Z", "Z2"), row.names = c(NA, 3L), class = "data.frame" ) expect_equal(read.xlsx(fileName), expected_df) ## from workbook expected_df <- structure(list( X = c(NA_real_, NA_real_, NA_real_), Y = c("a", "b", "c"), Z = c(NA, 99, NA), Z2 = c(1, NA, NA) ), .Names = c("X", "Y", "Z", "Z2"), row.names = c(NA, 3L), class = "data.frame" ) expect_equal(read.xlsx(wb), expected_df) ## keepNA = FALSE expect_equal(read.xlsx(wb), read.xlsx(fileName)) expect_equal(b, read.xlsx(wb)) expect_equal(b, read.xlsx(fileName)) ## keepNA = TRUE expect_equal(read.xlsx(wb), expected_df) expect_equal(read.xlsx(fileName), expected_df) expect_equal(b, read.xlsx(wb, sheet = 2)) expect_equal(b, read.xlsx(fileName, sheet = 2)) ## keepNA = TRUE, na.string = "*" expect_equal(c, read.xlsx(wb, sheet = 3)) expect_equal(c, read.xlsx(fileName, sheet = 3)) unlink(fileName, recursive = TRUE, force = TRUE) }) test_that("Reading from new workbook 2 ", { ## data genDf <- function() { data.frame( "Date" = Sys.Date() - 0:4, "Logical" = c(TRUE, FALSE, TRUE, TRUE, FALSE), "Currency" = -2:2, "Accounting" = -2:2, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(-1, 1, length.out = 5), "TinyNumber" = runif(5) / 1E9, stringsAsFactors = FALSE ) } df <- genDf() class(df$Currency) <- "currency" class(df$Accounting) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumber) <- "scientific" options("openxlsx.dateFormat" = NULL) fileName <- file.path(tempdir(), "allClasses.xlsx") wb <- write.xlsx(df, file = fileName, overwrite = TRUE) x <- read.xlsx(wb, sheet = 1, detectDates = FALSE) x[[1]] <- convertToDate(x[[1]]) expect_equal(object = x, expected = genDf(), check.attributes = FALSE) x <- read.xlsx(wb, sheet = 1, detectDates = TRUE) expect_equal(object = x, expected = genDf(), check.attributes = FALSE) unlink(fileName, recursive = TRUE, force = TRUE) }) test_that("Reading from new workbook cols/rows", { wb <- createWorkbook() for (i in 1:4) { addWorksheet(wb, sprintf("Sheet %s", i)) } tempFile <- temp_xlsx() ## 1 writeData(wb, sheet = 1, x = mtcars, colNames = TRUE, rowNames = FALSE) saveWorkbook(wb, tempFile, overwrite = TRUE) cols <- 1:3 rows <- 1:10 x <- read.xlsx(wb, 1, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols) y <- read.xlsx(tempFile, 1, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols) df <- mtcars[sort((rows - 1)[(rows - 1) <= nrow(mtcars)]), sort(cols[cols <= ncol(mtcars)])] rownames(df) <- seq_len(nrow(df)) expect_equal(object = x, expected = y) expect_equal(object = x, expected = df) ## 2 writeData(wb, sheet = 2, x = mtcars, colNames = TRUE, rowNames = FALSE, startRow = 10, startCol = 5) saveWorkbook(wb, tempFile, overwrite = TRUE) cols <- 1:300 rows <- 1:1000 x <- read.xlsx(wb, sheet = 2, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols) y <- read.xlsx(tempFile, sheet = 2, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols) # expect_equal(object = mtcars, expected = x, check.attributes = FALSE) expect_equal(object = x, expected = y, check.attributes = TRUE) expect_equal(object = colnames(mtcars), expected = colnames(x), check.attributes = FALSE) cols <- 1:3 rows <- 12:13 x <- suppressWarnings(read.xlsx(wb, sheet = 2, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols)) y <- suppressWarnings(read.xlsx(tempFile, sheet = 2, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols)) expect_equal(object = NULL, expected = x, check.attributes = FALSE) expect_equal(object = NULL, expected = y, check.attributes = TRUE) ## 3 writeData(wb, sheet = 3, x = mtcars, colNames = TRUE, rowNames = FALSE) saveWorkbook(wb, tempFile, overwrite = TRUE) cols <- c(2, 4, 6) rows <- seq(1, 31, by = 2) x <- read.xlsx(wb, sheet = 3, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols) y <- read.xlsx(tempFile, sheet = 3, colNames = TRUE, rowNames = FALSE, rows = rows, cols = cols) df <- mtcars[sort((rows - 1)[(rows - 1) <= nrow(mtcars)]), sort(cols[cols <= ncol(mtcars)])] rownames(df) <- seq_len(nrow(df)) expect_equal(object = x, expected = y, check.attributes = FALSE) expect_equal(object = df, expected = x, check.attributes = FALSE) ## 4 writeData(wb, sheet = 4, x = mtcars, colNames = TRUE, rowNames = TRUE) saveWorkbook(wb, tempFile, overwrite = TRUE) cols <- c(1, 6, 12) rows <- seq(1, 31, by = 2) x <- read.xlsx(wb, sheet = 4, colNames = TRUE, rowNames = TRUE, rows = rows, cols = cols) y <- read.xlsx(tempFile, sheet = 4, colNames = TRUE, rowNames = TRUE, rows = rows, cols = cols) df <- mtcars[sort((rows - 1)[(rows - 1) <= nrow(mtcars)]), cols[-1] - 1] expect_equal(object = x, expected = y, check.attributes = FALSE) expect_equal(object = df, expected = x, check.attributes = FALSE) rm(wb) unlink(tempFile, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-protect-workbook.R0000644000176200001440000000170314155600364021554 0ustar liggesusers context("Protection") test_that("Protect Workbook", { wb <- createWorkbook() addWorksheet(wb, "s1") wb$protectWorkbook(password = "abcdefghij") expect_true(wb$workbook$workbookProtection == "") wb$protectWorkbook(protect = FALSE, password = "abcdefghij", lockStructure = TRUE, lockWindows = TRUE) expect_true(wb$workbook$workbookProtection == "") }) test_that("Reading protected Workbook", { tmp_file <- file.path(tempdir(), "xlsx_read_protectedwb.xlsx") wb <- createWorkbook() addWorksheet(wb, "s1") protectWorkbook(wb, password = "abcdefghij") saveWorkbook(wb, tmp_file, overwrite = TRUE) wb2 <- loadWorkbook(file = tmp_file) # Check that the order of the sub-elements is preserved n1 <- names(wb2$workbook) n2 <- names(wb$workbook)[names(wb$workbook) != "apps"] expect_equal(n1, n2) unlink(tmp_file, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-write_data_to_sheetData_NAs.R0000644000176200001440000000662414155600364023620 0ustar liggesusers context("Writing NA to sheet_data") test_that("Writing to sheet_data with keepNA = FALSE", { a <- head(iris) a[2, 2] <- NA a[3, 5] <- NA a[5, 1] <- NA a[5, 5] <- NA wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, 1, a, keepNA = FALSE) sheet_data <- wb$worksheets[[1]]$sheet_data sheet_v <- sheet_data$v sheet_t <- sheet_data$t sheet_f <- sheet_data$f sheet_row <- sheet_data$rows sheet_col <- sheet_data$cols sheet_v <- as.numeric(sheet_v) sheet_v <- sheet_v[!is.na(sheet_v)] sheet_t <- sheet_t[!is.na(sheet_t)] sheet_f <- sheet_f[!is.na(sheet_f)] sheet_row <- sheet_row[!is.na(sheet_row)] sheet_col <- sheet_col[!is.na(sheet_col)] n_values <- prod(dim(a)) + ncol(a) expect_length(sheet_row, n_values) expect_length(sheet_col, n_values) expect_length(sheet_t, n_values - 4) expect_length(sheet_v, n_values - 4) expect_length(sheet_f, 0) ## rows/cols expect_equal(sheet_row, rep(1:7, each = 5)) expect_equal(sheet_col, rep(1:5, times = 7)) ## header types expect_equal(sheet_t[1:5], rep(1, ncol(a))) ## data.frame t & v expected_t <- c( "n", "n", "n", "n", "s", "n", "n", "n", "s", "n", "n", "n", "n", "n", "n", "n", "n", "s", "n", "n", "n", "n", "n", "n", "n", "s", NA, NA, NA, NA ) expected_t <- map_cell_types_to_integer(t = expected_t) expect_equal(sheet_t[6:n_values], expected_t) expect_equal(sheet_v[1:5], 0:4) expected_v <- c( 5.1, 3.5, 1.4, 0.2, 5, 4.9, 1.4, 0.2, 5, 4.7, 3.2, 1.3, 0.2, 4.6, 3.1, 1.5, 0.2, 5, 3.6, 1.4, 0.2, 5.4, 3.9, 1.7, 0.4, 5, NA, NA, NA, NA ) expect_equal(sheet_v[6:n_values], expected_v) }) test_that("Writing to sheet_data with keepNA = TRUE and na.string = '*'", { a <- head(iris) a[2, 2] <- NA a[3, 5] <- NA a[5, 1] <- NA a[5, 5] <- NA wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, 1, a, keepNA = TRUE, na.string = "*") sheet_data <- wb$worksheets[[1]]$sheet_data sheet_v <- sheet_data$v sheet_t <- sheet_data$t sheet_f <- sheet_data$f sheet_row <- sheet_data$rows sheet_col <- sheet_data$cols sheet_v <- as.numeric(sheet_v) sheet_v <- sheet_v[!is.na(sheet_v)] sheet_t <- sheet_t[!is.na(sheet_t)] sheet_f <- sheet_f[!is.na(sheet_f)] sheet_row <- sheet_row[!is.na(sheet_row)] sheet_col <- sheet_col[!is.na(sheet_col)] n_values <- prod(dim(a)) + ncol(a) expect_length(sheet_row, n_values) expect_length(sheet_col, n_values) expect_length(sheet_t, n_values) expect_length(sheet_v, n_values) expect_length(sheet_f, 0) ## rows/cols expect_equal(sheet_row, rep(1:7, each = 5)) expect_equal(sheet_col, rep(1:5, times = 7)) ## header types expect_equal(sheet_t[1:5], rep(1, ncol(a))) ## data.frame t & v expected_t <- c( "n", "n", "n", "n", "s", "n", "s", "n", "n", "s", "n", "n", "n", "n", "s", "n", "n", "n", "n", "s", "s", "n", "n", "n", "s", "n", "n", "n", "n", "s" ) expected_t <- map_cell_types_to_integer(t = expected_t) expect_equal(sheet_t[6:n_values], expected_t) expect_equal(sheet_v[1:5], 0:4) expected_v <- c( 5.1, 3.5, 1.4, 0.2, 5, 4.9, 6, 1.4, 0.2, 5, 4.7, 3.2, 1.3, 0.2, 6, 4.6, 3.1, 1.5, 0.2, 5, 6, 3.6, 1.4, 0.2, 6, 5.4, 3.9, 1.7, 0.4, 5 ) expect_equal(sheet_v[6:n_values], expected_v) }) openxlsx/tests/testthat/test-page_setup.R0000644000176200001440000000240614155600364020376 0ustar liggesusers context("Page setup") test_that("Page setup", { wb <- createWorkbook() addWorksheet(wb, "s1") addWorksheet(wb, "s2") pageSetup(wb, sheet = "s1", orientation = "landscape", scale = 100, left = 0.1, right = 0.1, top = .75, bottom = .75, header = 0.1, footer = 0.1, fitToWidth = TRUE, fitToHeight = TRUE, paperSize = 1, summaryRow = "below", summaryCol = "right" ) pageSetup(wb, sheet = 2, orientation = "landscape", scale = 100, left = 0.1, right = 0.1, top = .75, bottom = .75, header = 0.1, footer = 0.1, fitToWidth = TRUE, fitToHeight = TRUE, paperSize = 1, summaryRow = "below", summaryCol = "right" ) expect_equal(wb$worksheets[[1]]$pageSetup, wb$worksheets[[2]]$pageSetup) v <- gsub(" ", "", wb$worksheets[[1]]$pageSetup, fixed = TRUE) expect_true(grepl('paperSize="1"', v)) expect_true(grepl('orientation="landscape"', v)) expect_true(grepl('fitToWidth="1"', v)) expect_true(grepl('fitToHeight="1"', v)) pr <- wb$worksheets[[1]]$sheetPr # SheetPr will be a character vector of length 2; the first entry will # be for PageSetupPr, inserted by `fitToWidth`/`fitToHeight`. expect_true(grepl('', pr[2], fixed = TRUE)) }) openxlsx/tests/testthat/test-read_write_logicals.R0000644000176200001440000000232614155600364022245 0ustar liggesusers context("Readind and Writing Logicals") test_that("TRUE, FALSE, NA", { curr_wd <- getwd() fileName <- file.path(tempdir(), "T_F_NA.xlsx") x <- iris x$Species <- as.character(x$Species) x$all_t <- TRUE x$all_f <- FALSE x$tf <- sample(c(TRUE, FALSE), size = 150, replace = TRUE) x$t_na <- sample(c(TRUE, NA), size = 150, replace = TRUE) x$f_na <- sample(c(FALSE, NA), size = 150, replace = TRUE) x$tf_na <- sample(c(TRUE, FALSE, NA), size = 150, replace = TRUE) wb <- write.xlsx(x, file = fileName, colNames = TRUE) y <- read.xlsx(fileName, sheet = 1) expect_equal(x, y) ## T becomes false TRUE and NA exist in a columns expect_equal(x$t_na, y$t_na) expect_equal(x$f_na, y$f_na) expect_equal(is.na(x$f_na), is.na(y$f_na)) expect_equal(is.na(x$tf_na), is.na(y$tf_na)) ## From Workbook y <- read.xlsx(wb, sheet = 1) expect_equal(x, y) ## T becomes false TRUE and NA exist in a columns expect_equal(x$t_na, y$t_na) expect_equal(x$f_na, y$f_na) expect_equal(is.na(x$f_na), is.na(y$f_na)) expect_equal(is.na(x$tf_na), is.na(y$tf_na)) expect_equal(object = getwd(), curr_wd) unlink(fileName, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-cloneWorksheet.R0000644000176200001440000000155614155600364021243 0ustar liggesusers context("clone Worksheet") test_that("clone Worksheet with data", { wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, "Sheet 1", 1) cloneWorksheet(wb, "Sheet 2", clonedSheet = "Sheet 1") file_name <- system.file("extdata", "cloneWorksheetExample.xlsx", package = "openxlsx") refwb <- loadWorkbook(file = file_name) expect_equal(sheets(wb), sheets(refwb)) expect_equal(worksheetOrder(wb), worksheetOrder(refwb)) }) test_that("clone empty Worksheet", { wb <- createWorkbook() addWorksheet(wb, "Sheet 1") cloneWorksheet(wb, "Sheet 2", clonedSheet = "Sheet 1") file_name <- system.file("extdata", "cloneEmptyWorksheetExample.xlsx", package = "openxlsx") refwb <- loadWorkbook(file = file_name) expect_equal(sheets(wb), sheets(refwb)) expect_equal(worksheetOrder(wb), worksheetOrder(refwb)) }) openxlsx/tests/testthat/test-border_parsing.R0000644000176200001440000003523514155600364021250 0ustar liggesusers context("Style Parsing") test_that("parsing border xml", { wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) styles <- getStyles(wb = wb) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "medium", "medium", "medium", "medium", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "thin", NULL, "thin", "thin", NULL, "thin", "thin", "thin", "thin", "thin", "thin", "thin", NULL, "thin", "thin", "medium", "medium", "medium", "medium", "thin", "medium", "medium", "thin", NULL, "medium", "medium", "medium", "thin", "thin", "medium", "medium", "thin", "thin", "thick", NULL, "thick", "thick", "thick", NULL, NULL, NULL, NULL, NULL, "medium", "medium", NULL, "medium", "mediumDashed", "mediumDashed", "mediumDashed", NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderBottom")) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "thin", "thin", "thin", NULL, NULL, NULL, NULL, "medium", NULL, NULL, NULL, NULL, NULL, "thin", NULL, "thin", "thick", NULL, "medium", "thin", "thin", "thin", "thin", "thick", "thick", "thin", "thin", "thin", "medium", "medium", "thin", "thick", "thick", "medium", "thin", "thick", "thick", "medium", "thin", "thin", "medium", "thin", "thin", "thin", "medium", "medium", "medium", NULL, NULL, NULL, NULL, NULL, NULL, "mediumDashed", "mediumDashed", "mediumDashed", NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderTop")) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, NULL, "medium", NULL, "medium", NULL, NULL, NULL, NULL, NULL, NULL, "thin", NULL, NULL, "thin", NULL, NULL, "thin", "medium", NULL, NULL, NULL, NULL, "thin", "thin", "thin", NULL, "thin", NULL, NULL, NULL, "thin", "medium", "thin", "thin", "thin", "thin", "medium", "thin", "thin", NULL, "thin", "thick", "thin", "thick", "thick", "thin", "thin", "thin", "thin", "thick", NULL, "thin", "thin", "thin", "medium", NULL, NULL, "medium", NULL, "medium", NULL, "medium", NULL, "mediumDashed", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderLeft")) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, "medium", NULL, "medium", NULL, NULL, NULL, "medium", NULL, NULL, NULL, NULL, NULL, "thin", NULL, NULL, "thin", NULL, NULL, NULL, "thin", NULL, "thin", NULL, "thin", "thin", "thin", "thin", "thick", NULL, "thick", "medium", "thin", "thin", "thin", "thin", "medium", "thin", "thin", "medium", NULL, "medium", "thin", "thin", "medium", "medium", "thin", "thick", "medium", "medium", "thin", "medium", "thin", NULL, "thick", NULL, NULL, "medium", NULL, "medium", NULL, NULL, NULL, "medium", NULL, NULL, "mediumDashed", NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderRight")) ## COLOURS expected_borders <- list( NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, structure(list( indexed = "64" ), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, structure(list(theme = "6"), .Names = "theme"), NULL, structure(list(theme = "6"), .Names = "theme"), structure(list( theme = "6" ), .Names = "theme"), NULL, structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), NULL, structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "3" ), .Names = "theme"), structure(list(theme = "3"), .Names = "theme"), structure(list(theme = "3"), .Names = "theme"), structure(list( theme = "6" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(theme = "6"), .Names = "theme"), structure(list( theme = "6" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), NULL, structure(list(theme = "6"), .Names = "theme"), structure(list( theme = "7\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "7\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "9\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "9\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), NULL, structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), NULL, NULL, NULL, NULL, NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderBottomColour")) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, structure(list(theme = "6"), .Names = "theme"), structure(list(theme = "6"), .Names = "theme"), structure(list( theme = "6" ), .Names = "theme"), NULL, NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, NULL, NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "6" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(theme = "7\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(theme = "9\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, NULL, NULL, NULL, NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderTopColour")) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, NULL, NULL, NULL, NULL, NULL, structure(list(theme = "6"), .Names = "theme"), NULL, NULL, structure(list(theme = "6"), .Names = "theme"), NULL, NULL, structure(list(theme = "6"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), NULL, NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), NULL, structure(list( indexed = "64" ), .Names = "indexed"), NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "3" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "6" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderLeftColour")) expected_borders <- list( NULL, NULL, NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, NULL, NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, NULL, NULL, NULL, NULL, structure(list(theme = "6"), .Names = "theme"), NULL, NULL, structure(list(theme = "6"), .Names = "theme"), NULL, NULL, NULL, structure(list(theme = "6"), .Names = "theme"), NULL, structure(list(indexed = "64"), .Names = "indexed"), NULL, structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), NULL, structure(list(theme = "5\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(theme = "3"), .Names = "theme"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( theme = "6" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "6" ), .Names = "theme"), NULL, structure(list( theme = "6" ), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "7\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "7\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), structure(list(theme = "9\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(theme = "9\" tint=\"-0.249977111117893"), .Names = "theme"), structure(list(indexed = "64"), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), structure(list( indexed = "64" ), .Names = "indexed"), NULL, structure(list( theme = "5\" tint=\"-0.249977111117893" ), .Names = "theme"), NULL, NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, structure("9\" tint=\"-0.249977111117893", .Names = "theme"), NULL, NULL, NULL, NULL, NULL, NULL ) expect_equal(expected_borders, sapply(styles, "[[", "borderRightColour")) }) openxlsx/tests/testthat/test-skip_empty_rows.R0000644000176200001440000002071414155600364021502 0ustar liggesusers context("Skip Empty Rows") test_that("skip empty rows", { xlsxfile <- temp_xlsx() df <- data.frame("x" = c(1, NA, NA, 2), "y" = c(1, NA, NA, 3)) write.xlsx(df, xlsxfile) wb <- loadWorkbook(xlsxfile) df1 <- readWorkbook(xlsxfile, skipEmptyRows = FALSE) df2 <- readWorkbook(wb, skipEmptyRows = FALSE) expect_equal(df, df1) expect_equal(df, df2) v <- c("A1", "B1", "A2", "B2", "A5", "B5") expect_equal(calc_number_rows(x = v, skipEmptyRows = TRUE), 3) expect_equal(calc_number_rows(x = v, skipEmptyRows = FALSE), 5) ## DONT SKIP df1 <- readWorkbook(xlsxfile, skipEmptyRows = TRUE) df2 <- readWorkbook(wb, skipEmptyRows = TRUE) expect_equal(nrow(df1), 2) expect_equal(nrow(df2), 2) expect_equivalent(df[c(1, 4), ], df1) expect_equivalent(df[c(1, 4), ], df2) }) test_that("skip empty cols", { xlsxfile <- temp_xlsx() x <- data.frame("a" = c(1, NA, NA, 2), "b" = c(1, NA, NA, 3)) y <- data.frame("x" = c(1, NA, NA, 2), "y" = c(1, NA, NA, 3)) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, sheet = 1, x = x) writeData(wb, sheet = 1, x = y, startCol = 4) saveWorkbook(wb, file = xlsxfile) ## from file res <- readWorkbook(xlsxfile, skipEmptyRows = FALSE, skipEmptyCols = FALSE) expect_equal(ncol(res), 5) expect_equal(nrow(res), 4) ## from file res <- readWorkbook(xlsxfile, skipEmptyRows = TRUE, skipEmptyCols = TRUE) expect_equal(ncol(res), 4) expect_equal(nrow(res), 2) expect_equivalent(cbind(x, y)[c(1, 4), ], res) ## from file res <- readWorkbook(xlsxfile, skipEmptyRows = FALSE, skipEmptyCols = TRUE) expect_equal(ncol(res), 4) expect_equal(nrow(res), 4) expect_equivalent(cbind(x, y), res) ## from file res <- readWorkbook(xlsxfile, skipEmptyRows = TRUE, skipEmptyCols = FALSE) expect_equal(ncol(res), 5) expect_equal(nrow(res), 2) expect_true(all(is.na(res$X3))) ############################################################################# ## Workbook object ## Workbook object wb <- loadWorkbook(xlsxfile) ## from workbook object res <- readWorkbook(wb, skipEmptyRows = FALSE, skipEmptyCols = FALSE) expect_equal(ncol(res), 5) expect_equal(nrow(res), 4) ## from workbook object res <- readWorkbook(wb, skipEmptyRows = TRUE, skipEmptyCols = TRUE) expect_equal(ncol(res), 4) expect_equal(nrow(res), 2) expect_equivalent(cbind(x, y)[c(1, 4), ], res) ## from workbook object res <- readWorkbook(wb, skipEmptyRows = FALSE, skipEmptyCols = TRUE) expect_equal(ncol(res), 4) expect_equal(nrow(res), 4) expect_equivalent(cbind(x, y), res) ## from workbook object res <- readWorkbook(wb, skipEmptyRows = TRUE, skipEmptyCols = FALSE) expect_equal(ncol(res), 5) expect_equal(nrow(res), 2) expect_true(all(is.na(res$X3))) }) test_that("Version 4 fixes from File", { fl <- system.file("extdata", "readTest.xlsx", package = "openxlsx") x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 4L) x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 4L) ############################################################## ## FALSE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## FALSE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[2, ]))) ############################################################## ## FALSE TRUE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## FALSE TRUE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## TRUE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## TRUE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[2, ]))) }) test_that("Version 4 fixes from Workbook Objects", { fl <- loadWorkbook(system.file("extdata", "readTest.xlsx", package = "openxlsx")) x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 4L) x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 4L) ############################################################## ## FALSE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## FALSE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[2, ]))) ############################################################## ## FALSE TRUE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## FALSE TRUE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## TRUE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## TRUE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[2, ]))) }) openxlsx/tests/testthat/test-loading_workbook_tables.R0000644000176200001440000000335614155600364023133 0ustar liggesusers context("Load Workbook Object Tables") test_that("Tables loaded correctly", { wb <- loadWorkbook(system.file("extdata", "loadExample.xlsx", package = "openxlsx")) expect_equal(unname(attr(wb$tables, "tableName")), c("Table2", "Table3")) expect_equal(names(attr(wb$tables, "tableName")), c("A1:E51", "A1:K30")) expect_equal(attr(wb$tables, "sheet"), c(1, 3)) expect_equal(wb$worksheets[[1]]$tableParts, "", check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[1]]$tableParts, "tableName")), "Table2") expect_equal(names(attr(wb$worksheets[[1]]$tableParts, "tableName")), "A1:E51") expect_equal(wb$worksheets[[3]]$tableParts, "", check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[3]]$tableParts, "tableName")), "Table3") expect_equal(names(attr(wb$worksheets[[3]]$tableParts, "tableName")), "A1:K30") ## now remove a table expect_equal(unname(getTables(wb, 1)), "Table2", check.attributes = FALSE) expect_equal(unname(getTables(wb, 3)), "Table3", check.attributes = FALSE) removeTable(wb, sheet = 1, table = "Table2") expect_equal(getTables(wb, sheet = 1), character(0), check.attributes = FALSE) expect_equal(length(wb$worksheets[[1]]$tableParts), 0) expect_equal(wb$worksheets[[1]]$tableParts, character(0), check.attributes = FALSE) expect_equal(wb$worksheets[[3]]$tableParts, "", check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[3]]$tableParts, "tableName")), "Table3") expect_equal(names(attr(wb$worksheets[[3]]$tableParts, "tableName")), "A1:K30") expect_error(removeTable(wb, sheet = 1, table = "Table2"), regexp = "table 'Table2' does not exist") }) openxlsx/tests/testthat/test-worksheet_renaming.R0000644000176200001440000000356014155600364022137 0ustar liggesusers context("Renaming worksheets.") test_that("Can rename worksheets under all conditions", { tempFile <- file.path(tempdir(), "renaming.xlsx") wb <- createWorkbook() addWorksheet(wb, "sheet 1") addWorksheet(wb, "sheet 2") addWorksheet(wb, "sheet 3") addWorksheet(wb, "sheet 4") addWorksheet(wb, "sheet 5") renameWorksheet(wb, sheet = 2, "THis is SHEET 2") expect_equal(names(wb), c("sheet 1", "THis is SHEET 2", "sheet 3", "sheet 4", "sheet 5")) renameWorksheet(wb, sheet = "THis is SHEET 2", "THis is STILL SHEET 2") expect_equal(names(wb), c("sheet 1", "THis is STILL SHEET 2", "sheet 3", "sheet 4", "sheet 5")) renameWorksheet(wb, sheet = 5, "THis is SHEET 5") expect_equal(names(wb), c("sheet 1", "THis is STILL SHEET 2", "sheet 3", "sheet 4", "THis is SHEET 5")) renameWorksheet(wb, sheet = 5, "THis is STILL SHEET 5") expect_equal(names(wb), c("sheet 1", "THis is STILL SHEET 2", "sheet 3", "sheet 4", "THis is STILL SHEET 5")) renameWorksheet(wb, sheet = 2, "Sheet 2") expect_equal(names(wb), c("sheet 1", "Sheet 2", "sheet 3", "sheet 4", "THis is STILL SHEET 5")) renameWorksheet(wb, sheet = 5, "Sheet 5") expect_equal(names(wb), c("sheet 1", "Sheet 2", "sheet 3", "sheet 4", "Sheet 5")) ## re-ordering worksheetOrder(wb) <- c(4, 3, 2, 5, 1) saveWorkbook(wb, tempFile, overwrite = TRUE) wb <- loadWorkbook(file = tempFile) renameWorksheet(wb, sheet = 2, "THIS is SHEET 3") wb <- loadWorkbook(tempFile) renameWorksheet(wb, sheet = "Sheet 5", "THIS is NOW SHEET 5") expect_equal(names(wb), c("sheet 4", "sheet 3", "Sheet 2", "THIS is NOW SHEET 5", "sheet 1")) names(wb)[[1]] <- "THIS IS NOW SHEET 4" expect_equal(names(wb), c("THIS IS NOW SHEET 4", "sheet 3", "Sheet 2", "THIS is NOW SHEET 5", "sheet 1")) unlink(tempFile, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-read_sources.R0000644000176200001440000000321314155600364020715 0ustar liggesusers context("Read Sources") test_that("read.xlsx from different sources", { ## URL xlsxFile <- "https://github.com/ycphs/openxlsx/raw/master/inst/extdata/readTest.xlsx" df_url <- read.xlsx(xlsxFile) ## File xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") df_file <- read.xlsx(xlsxFile) expect_true(all.equal(df_url, df_file), label = "Read from URL") ## Non-existing URL xlsxFile <- "https://github.com/ycphs/openxlsx/raw/master/inst/extdata/readTest2.xlsx" expect_error(suppressWarnings(read.xlsx(xlsxFile))) ## Non-existing File xlsxFile <- file.path(dirname(system.file("extdata", "readTest.xlsx", package = "openxlsx")), "readTest00.xlsx") expect_error(read.xlsx(xlsxFile), regexp = "File does not exist.") }) test_that("loadWorkbook from different sources", { ## URL xlsxFile <- "https://github.com/ycphs/openxlsx/raw/master/inst/extdata/readTest.xlsx" wb_url <- loadWorkbook(xlsxFile) ## File xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") wb_file <- loadWorkbook(xlsxFile) ## check expect_true(all.equal.Workbook(wb_url, wb_file), "Loading from URL vs local not equal") }) test_that("getDateOrigin from different sources", { ## URL xlsxFile <- "https://github.com/ycphs/openxlsx/raw/master/inst/extdata/readTest.xlsx" origin_url <- getDateOrigin(xlsxFile) ## File xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") origin_file <- getDateOrigin(xlsxFile) ## check expect_equal(origin_url, origin_file) expect_equal(origin_url, "1900-01-01") }) openxlsx/tests/testthat/test-writing_sheet_data.R0000644000176200001440000013174514155600364022117 0ustar liggesusers context("Writing Sheet Data XML") test_that("Writing sheetData rows XML - iris", { temp_file <- temp_xlsx() openxlsx::write.xlsx(iris, temp_file) unzip(temp_file, exdir = tempdir()) x <- readUTF8(file.path(tempdir(), "xl", "worksheets", "sheet1.xml")) rows <- unlist(regmatches(x = x, gregexpr("", x))) expected_rows <- c( "01234", "5.13.51.40.25", "4.931.40.25", "4.73.21.30.25", "4.63.11.50.25", "53.61.40.25", "5.43.91.70.45", "4.63.41.40.35", "53.41.50.25", "4.42.91.40.25", "4.93.11.50.15", "5.43.71.50.25", "4.83.41.60.25", "4.831.40.15", "4.331.10.15", "5.841.20.25", "5.74.41.50.45", "5.43.91.30.45", "5.13.51.40.35", "5.73.81.70.35", "5.13.81.50.35", "5.43.41.70.25", "5.13.71.50.45", "4.63.610.25", "5.13.31.70.55", "4.83.41.90.25", "531.60.25", "53.41.60.45", "5.23.51.50.25", "5.23.41.40.25", "4.73.21.60.25", "4.83.11.60.25", "5.43.41.50.45", "5.24.11.50.15", "5.54.21.40.25", "4.93.11.50.25", "53.21.20.25", "5.53.51.30.25", "4.93.61.40.15", "4.431.30.25", "5.13.41.50.25", "53.51.30.35", "4.52.31.30.35", "4.43.21.30.25", "53.51.60.65", "5.13.81.90.45", "4.831.40.35", "5.13.81.60.25", "4.63.21.40.25", "5.33.71.50.25", "53.31.40.25", "73.24.71.46", "6.43.24.51.56", "6.93.14.91.56", "5.52.341.36", "6.52.84.61.56", "5.72.84.51.36", "6.33.34.71.66", "4.92.43.316", "6.62.94.61.36", "5.22.73.91.46", "523.516", "5.934.21.56", "62.2416", "6.12.94.71.46", "5.62.93.61.36", "6.73.14.41.46", "5.634.51.56", "5.82.74.116", "6.22.24.51.56", "5.62.53.91.16", "5.93.24.81.86", "6.12.841.36", "6.32.54.91.56", "6.12.84.71.26", "6.42.94.31.36", "6.634.41.46", "6.82.84.81.46", "6.7351.76", "62.94.51.56", "5.72.63.516", "5.52.43.81.16", "5.52.43.716", "5.82.73.91.26", "62.75.11.66", "5.434.51.56", "63.44.51.66", "6.73.14.71.56", "6.32.34.41.36", "5.634.11.36", "5.52.541.36", "5.52.64.41.26", "6.134.61.46", "5.82.641.26", "52.33.316", "5.62.74.21.36", "5.734.21.26", "5.72.94.21.36", "6.22.94.31.36", "5.12.531.16", "5.72.84.11.36", "6.33.362.57", "5.82.75.11.97", "7.135.92.17", "6.32.95.61.87", "6.535.82.27", "7.636.62.17", "4.92.54.51.77", "7.32.96.31.87", "6.72.55.81.87", "7.23.66.12.57", "6.53.25.127", "6.42.75.31.97", "6.835.52.17", "5.72.5527", "5.82.85.12.47", "6.43.25.32.37", "6.535.51.87", "7.73.86.72.27", "7.72.66.92.37", "62.251.57", "6.93.25.72.37", "5.62.84.927", "7.72.86.727", "6.32.74.91.87", "6.73.35.72.17", "7.23.261.87", "6.22.84.81.87", "6.134.91.87", "6.42.85.62.17", "7.235.81.67", "7.42.86.11.97", "7.93.86.427", "6.42.85.62.27", "6.32.85.11.57", "6.12.65.61.47", "7.736.12.37", "6.33.45.62.47", "6.43.15.51.87", "634.81.87", "6.93.15.42.17", "6.73.15.62.47", "6.93.15.12.37", "5.82.75.11.97", "6.83.25.92.37", "6.73.35.72.57", "6.735.22.37", "6.32.551.97", "6.535.227", "6.23.45.42.37", "5.935.11.87" ) for (i in seq_along(expected_rows)) { expect_equal(rows[i], expected = expected_rows[i]) } unlink(x = temp_file) }) test_that("Writing sheetData rows XML - mtcars", { temp_file <- temp_xlsx() openxlsx::write.xlsx(mtcars, temp_file, rowNames = TRUE) unzip(temp_file, exdir = tempdir()) x <- readUTF8(file.path(tempdir(), "xl", "worksheets", "sheet1.xml")) rows <- unlist(regmatches(x = x, gregexpr("", x))) expected_rows <- c( "01234567891011", "122161601103.92.6216.460144", "132161601103.92.87517.020144", "1422.84108933.852.3218.611141", "1521.462581103.083.21519.441031", "1618.783601753.153.4417.020032", "1718.162251052.763.4620.221031", "1814.383602453.213.5715.840034", "1924.44146.7623.693.19201042", "2022.84140.8953.923.1522.91042", "2119.26167.61233.923.4418.31044", "2217.86167.61233.923.4418.91044", "2316.48275.81803.074.0717.40033", "2417.38275.81803.073.7317.60033", "2515.28275.81803.073.78180033", "2610.484722052.935.2517.980034", "2710.4846021535.42417.820034", "2814.784402303.235.34517.420034", "2932.4478.7664.082.219.471141", "3030.4475.7524.931.61518.521142", "3133.9471.1654.221.83519.91141", "3221.54120.1973.72.46520.011031", "3315.583181502.763.5216.870032", "3415.283041503.153.43517.30032", "3513.383502453.733.8415.410034", "3619.284001753.083.84517.050032", "3727.3479664.081.93518.91141", "38264120.3914.432.1416.70152", "3930.4495.11133.771.51316.91152", "4015.883512644.223.1714.50154", "4119.761451753.622.7715.50156", "421583013353.543.5714.60158", "4321.441211094.112.7818.61142" ) for (i in seq_along(expected_rows)) { expect_equal(rows[i], expected = expected_rows[i]) } }) openxlsx/tests/testthat/test-date_time_conversion.R0000644000176200001440000000176414155600364022450 0ustar liggesusers context("Date/Time Conversions") test_that("convert to date", { dates <- as.Date("2015-02-07") + -10:10 origin <- 25569L n <- as.integer(dates) + origin expect_equal(convertToDate(n), dates) earlyDate <- as.Date("1900-01-03") serialDate <- 3 expect_equal(convertToDate(serialDate), earlyDate) }) test_that("convert to datetime", { x <- 43037 + 2 / 1440 expect_equal(object = convertToDateTime(x, tx = Sys.timezone()), expected = as.POSIXct("2017-10-29 00:02:00", tz = Sys.timezone())) x <- 43037 + 2 / 1440 + 1 / 86400 expect_equal(object = convertToDateTime(x, tx = Sys.timezone()), expected = as.POSIXct("2017-10-29 00:02:01", tz = Sys.timezone())) x <- 43037 + 2.50 / 1440 expect_equal(object = convertToDateTime(x, tx = Sys.timezone()), expected = as.POSIXct("2017-10-29 00:02:30", tz = Sys.timezone())) x <- 43037 + 2 / 1440 + 12.12 / 86400 x_datetime <- convertToDateTime(x, tx = "UTC") attr(x_datetime, "tzone") <- "UTC" }) openxlsx/tests/testthat/test-freeze_pane.R0000644000176200001440000001013614155600364020524 0ustar liggesusers context("Freeze Panes") test_that("Freeze Panes", { wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstActiveRow = 3, firstActiveCol = 3) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstActiveRow = 1, firstActiveCol = 3) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstActiveRow = 2, firstActiveCol = 1) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstActiveRow = 2, firstActiveCol = 4) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstCol = TRUE) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstRow = TRUE) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") freezePane(wb, 1, firstRow = TRUE, firstCol = TRUE) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") addWorksheet(wb, "Sheet 2") addWorksheet(wb, "Sheet 3") addWorksheet(wb, "Sheet 4") addWorksheet(wb, "Sheet 5") addWorksheet(wb, "Sheet 6") addWorksheet(wb, "Sheet 7") freezePane(wb, sheet = 1, firstActiveRow = 3, firstActiveCol = 3) freezePane(wb, sheet = 2, firstActiveRow = 1, firstActiveCol = 3) freezePane(wb, sheet = 3, firstActiveRow = 2, firstActiveCol = 1) freezePane(wb, sheet = 4, firstActiveRow = 2, firstActiveCol = 4) freezePane(wb, sheet = 5, firstCol = TRUE) freezePane(wb, sheet = 6, firstRow = TRUE) freezePane(wb, sheet = 7, firstRow = TRUE, firstCol = TRUE) expected <- "" expect_equal(wb$worksheets[[1]]$freezePane, expected) expected <- "" expect_equal(wb$worksheets[[2]]$freezePane, expected) expected <- "" expect_equal(wb$worksheets[[3]]$freezePane, expected) expected <- "" expect_equal(wb$worksheets[[4]]$freezePane, expected) expected <- "" expect_equal(wb$worksheets[[5]]$freezePane, expected) expected <- "" expect_equal(wb$worksheets[[6]]$freezePane, expected) expected <- "" expect_equal(wb$worksheets[[7]]$freezePane, expected) }) openxlsx/tests/testthat/test-skip_empty_cols.R0000644000176200001440000001466414155600364021457 0ustar liggesusers context("Skip Empty Cols") test_that("skip empty rows", { xlsxfile <- temp_xlsx() df <- data.frame("x" = c(1, NA, NA, 2), "y" = c(1, NA, NA, 3)) write.xlsx(df, xlsxfile) wb <- loadWorkbook(xlsxfile) df1 <- readWorkbook(xlsxfile, skipEmptyRows = FALSE) df2 <- readWorkbook(wb, skipEmptyRows = FALSE) expect_equal(df, df1) expect_equal(df, df2) v <- c("A1", "B1", "A2", "B2", "A5", "B5") expect_equal(calc_number_rows(x = v, skipEmptyRows = TRUE), 3) expect_equal(calc_number_rows(x = v, skipEmptyRows = FALSE), 5) ## DONT SKIP df1 <- readWorkbook(xlsxfile, skipEmptyRows = TRUE) df2 <- readWorkbook(wb, skipEmptyRows = TRUE) expect_equal(nrow(df1), 2) expect_equal(nrow(df2), 2) expect_equivalent(df[c(1, 4), ], df1) expect_equivalent(df[c(1, 4), ], df2) }) test_that("Version 4 fixes from File", { fl <- system.file("extdata", "readTest.xlsx", package = "openxlsx") x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 4L) x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 4L) ############################################################## ## FALSE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## FALSE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[2, ]))) ############################################################## ## FALSE TRUE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## FALSE TRUE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## TRUE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## TRUE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[2, ]))) }) test_that("Version 4 fixes from Workbook Objects", { fl <- loadWorkbook(system.file("extdata", "readTest.xlsx", package = "openxlsx")) x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 4L) x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 4L) ############################################################## ## FALSE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## FALSE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[2, ]))) ############################################################## ## FALSE TRUE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## FALSE TRUE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## TRUE FALSE FALSE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## TRUE FALSE TRUE x <- read.xlsx(xlsxFile = fl, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[2, ]))) }) openxlsx/tests/testthat/test-CommentClass.R0000644000176200001440000000130514155600364020627 0ustar liggesuserstest_that("createComment() works", { # error checking expect_error(createComment("hi", width = 1), NA) expect_error(createComment("hi", width = 1L), NA) expect_error(createComment("hi", width = 1:2), "width") expect_error(createComment("hi", height = 1), NA) expect_error(createComment("hi", height = 1L), NA) expect_error(createComment("hi", height = 1:2), "height") expect_error(createComment("hi", visible = NULL)) expect_error(createComment("hi", visible = c(TRUE, FALSE)), "visible") expect_error(createComment("hi", author = 1)) expect_error(createComment("hi", author = c("a", "a")), "author") expect_s4_class(createComment("hello"), "Comment") }) openxlsx/tests/testthat/test-saveWorkbook.R0000644000176200001440000000327714155600364020725 0ustar liggesusers context("save workbook") test_that("test return values for saveWorkbook", { tempFile <- temp_xlsx() wb<-createWorkbook() addWorksheet(wb,"name") expect_true( saveWorkbook(wb,tempFile,returnValue = TRUE)) expect_error( saveWorkbook(wb,tempFile,returnValue = TRUE)) expect_invisible( saveWorkbook(wb,tempFile,returnValue = FALSE ,overwrite = TRUE)) unlink(tempFile, recursive = TRUE, force = TRUE) } ) # regression test for a typo test_that("regression test for #248", { # Basic data frame df <- data.frame(number = 1:3, percent = 4:6/100) tempFile <- temp_xlsx() # no formatting expect_silent(write.xlsx(df, tempFile, borders = "columns", overwrite = TRUE)) # Change column class to percentage class(df$percent) <- "percentage" expect_silent(write.xlsx(df, tempFile, borders = "columns", overwrite = TRUE)) }) # test for hyperrefs test_that("creating hyperlinks", { # prepare a file tempFile <- temp_xlsx() wb <- createWorkbook() sheet <- "test" addWorksheet(wb, sheet) img <- "D:/somepath/somepicture.png" # warning: col and row provided, but not required expect_warning( linkString <- makeHyperlinkString(col = 1, row = 4, text = "test.png", file = img)) linkString2 <- makeHyperlinkString(text = "test.png", file = img) # col and row not needed expect_equal(linkString, linkString2) # write file without errors writeFormula(wb, sheet, x = linkString, startCol = 1, startRow = 1) expect_silent(saveWorkbook(wb, tempFile, overwrite = TRUE)) # TODO: add a check that the written xlsx file contains linkString }) openxlsx/tests/testthat/test-conditionalFormatting.R0000644000176200001440000000656414155600364022611 0ustar liggesusers context("Testing 'topN' and 'bottomN' conditions in conditionalFormatting") TNBN_test_data <- data.frame(col1 = 1:10, col2 = 1:10, col3 = seq(10, 100, 10), col4 = seq(10, 100, 10), col5 = 1:10, col6 = 1:10) bg_blue <- createStyle(bgFill = "skyblue") wb <- createWorkbook() sht <- "TopN_BottomN_TEST" addWorksheet(wb, sht) writeData(wb, sht, TNBN_test_data) conditionalFormatting(wb, sht, cols = 1, rows = 2:11, type = "topN", rank = 3, style = bg_blue, percent = FALSE) conditionalFormatting(wb, sht, cols = 2, rows = 2:11, type = "bottomN", rank = 3, style = bg_blue, percent = FALSE) conditionalFormatting(wb, sht, cols = 3, rows = 2:11, type = "topN", rank = 50, style = bg_blue, percent = TRUE) conditionalFormatting(wb, sht, cols = 4, rows = 2:11, type = "bottomN", rank = 50, style = bg_blue, percent = TRUE) conditionalFormatting(wb, sht, cols = 5, rows = 2:11, type = "topN", rank = 3, style = bg_blue) conditionalFormatting(wb, sht, cols = 6, rows = 2:11, type = "bottomN", rank = 3, style = bg_blue) test_that("Number of conditionalFormatting rules added equal to 6", { expect_equal(object = length(wb$worksheets[[1]]$conditionalFormatting), expected = 6) }) test_that("topN conditions do not have the 'bottom' argument", { expect_false(object = grepl(paste('bottom'), wb$worksheets[[1]]$conditionalFormatting[1])) expect_false(object = grepl(paste('bottom'), wb$worksheets[[1]]$conditionalFormatting[3])) }) test_that("bottomN conditions have the 'bottom' argument set to '1'", { expect_true(object = grepl(paste('bottom="1"'), wb$worksheets[[1]]$conditionalFormatting[2])) expect_true(object = grepl(paste('bottom="1"'), wb$worksheets[[1]]$conditionalFormatting[4])) }) test_that("topN/bottomN rank conditions have the 'percent=FALSE' argument set to '0'", { expect_true(object = grepl(paste('percent="0"'), wb$worksheets[[1]]$conditionalFormatting[1])) expect_true(object = grepl(paste('percent="0"'), wb$worksheets[[1]]$conditionalFormatting[2])) }) test_that("topN/bottomN rank conditions do not have the 'percent' argument is set to 'NULL'", { expect_true(object = grepl(paste('percent="NULL"'), wb$worksheets[[1]]$conditionalFormatting[5])) expect_true(object = grepl(paste('percent="NULL"'), wb$worksheets[[1]]$conditionalFormatting[6])) }) test_that("topN/bottomN percent conditions have the 'percent' argument set to '1'", { expect_true(object = grepl(paste('percent="1"'), wb$worksheets[[1]]$conditionalFormatting[3])) expect_true(object = grepl(paste('percent="1"'), wb$worksheets[[1]]$conditionalFormatting[4])) }) test_that("topN/bottomN conditions correspond to 'top10' type", { expect_true(object = grepl(paste('type="top10"'), wb$worksheets[[1]]$conditionalFormatting[1])) expect_true(object = grepl(paste('type="top10"'), wb$worksheets[[1]]$conditionalFormatting[2])) expect_true(object = grepl(paste('type="top10"'), wb$worksheets[[1]]$conditionalFormatting[3])) expect_true(object = grepl(paste('type="top10"'), wb$worksheets[[1]]$conditionalFormatting[4])) expect_true(object = grepl(paste('type="top10"'), wb$worksheets[[1]]$conditionalFormatting[5])) expect_true(object = grepl(paste('type="top10"'), wb$worksheets[[1]]$conditionalFormatting[6])) }) openxlsx/tests/testthat/test-writeData.R0000644000176200001440000000177214155600364020173 0ustar liggesuserstest_that("writeData() forces evaluation of x (#264)", { wbfile <- temp_xlsx() op <- options(stringsAsFactors = FALSE) x <- format(123.4) df <- data.frame(d = format(123.4)) df2 <- data.frame(e = x) wb <- createWorkbook() addWorksheet(wb, "sheet") writeData(wb, "sheet", startCol = 1, data.frame(a = format(123.4))) writeData(wb, "sheet", startCol = 2, data.frame(b = as.character(123.4))) writeData(wb, "sheet", startCol = 3, data.frame(c = "123.4")) writeData(wb, "sheet", startCol = 4, df) writeData(wb, "sheet", startCol = 5, df2) saveWorkbook(wb, wbfile) out <- read.xlsx(wbfile) # Possibly overkill with(out, { expect_identical(a, b) expect_identical(a, c) expect_identical(a, d) expect_identical(a, e) expect_identical(b, c) expect_identical(b, d) expect_identical(b, e) expect_identical(c, d) expect_identical(c, e) expect_identical(d, e) }) options(op) file.remove(wbfile) }) openxlsx/tests/testthat/test-write_read_equality.R0000644000176200001440000002072214155600364022305 0ustar liggesusers context("Writing and reading returns similar objects") test_that("Writing then reading returns identical data.frame 1", { curr_wd <- getwd() ## data genDf <- function() { set.seed(1) data.frame( "Date" = Sys.Date() - 0:4, "Logical" = c(TRUE, FALSE, TRUE, TRUE, FALSE), "Currency" = -2:2, "Accounting" = -2:2, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(-1, 1, length.out = 5), "TinyNumber" = runif(5) / 1E9, stringsAsFactors = FALSE ) } df <- genDf() df class(df$Currency) <- "currency" class(df$Accounting) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumber) <- "scientific" op <- options() options("openxlsx.dateFormat" = "yyyy-mm-dd") fileName <- file.path(tempdir(), "allClasses.xlsx") write.xlsx(df, file = fileName, overwrite = TRUE) x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE) expect_equal(object = x, expected = genDf(), check.attributes = FALSE) unlink(fileName, recursive = TRUE, force = TRUE) expect_equal(object = getwd(), curr_wd) options(op) }) test_that("Writing then reading returns identical data.frame 2", { curr_wd <- getwd() ## data.frame of dates dates <- data.frame("d1" = Sys.Date() - 0:500) dates[nrow(dates)+1,] = as.Date("1900-01-02") for (i in 1:3) dates <- cbind(dates, dates) names(dates) <- paste0("d", 1:8) ## Date Formatting wb <- createWorkbook() addWorksheet(wb, "Date Formatting", gridLines = FALSE) writeData(wb, 1, dates) ## write without styling ## set default date format options("openxlsx.dateFormat" = "yyyy/mm/dd") ## numFmt == "DATE" will use the date format specified by the above addStyle(wb, 1, style = createStyle(numFmt = "DATE"), rows = 2:11, cols = 1, gridExpand = TRUE) ## some custom date format examples sty <- createStyle(numFmt = "yyyy/mm/dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) sty <- createStyle(numFmt = "yyyy/mmm/dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) sty <- createStyle(numFmt = "yy / mmmm / dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) sty <- createStyle(numFmt = "ddddd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 5, gridExpand = TRUE) sty <- createStyle(numFmt = "yyyy-mmm-dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 6, gridExpand = TRUE) sty <- createStyle(numFmt = "mm/ dd yyyy") addStyle(wb, 1, style = sty, rows = 2:11, cols = 7, gridExpand = TRUE) sty <- createStyle(numFmt = "mm/dd/yy") addStyle(wb, 1, style = sty, rows = 2:11, cols = 8, gridExpand = TRUE) setColWidths(wb, 1, cols = 1:10, widths = 23) fileName <- file.path(tempdir(), "DateFormatting.xlsx") write.xlsx(dates, file = fileName, overwrite = TRUE) x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE) expect_equal(object = x, expected = dates, check.attributes = FALSE) xNoDateDetection <- read.xlsx(xlsxFile = fileName, detectDates = FALSE) dateOrigin <- getDateOrigin(fileName) expect_equal(object = dateOrigin, expected = "1900-01-01", check.attributes = FALSE) for (i in seq_len(ncol(x))) { xNoDateDetection[[i]] <- convertToDate(xNoDateDetection[[i]], origin = dateOrigin) } expect_equal(object = xNoDateDetection, expected = dates, check.attributes = FALSE) expect_equal(object = getwd(), curr_wd) unlink(fileName, recursive = TRUE, force = TRUE) }) test_that("Writing then reading rowNames, colNames combinations", { op <- options() options(stringsAsFactors = FALSE) fileName <- temp_xlsx() curr_wd <- getwd() mt <- utils::head(mtcars) # don't need the whole thing # write the row and column names for testing write.xlsx(mt, file = fileName, overwrite = TRUE, rowNames = TRUE, colNames = TRUE) # rowNames = colNames = TRUE # Row names = first column # Col names = first row x <- read.xlsx(fileName, sheet = 1, rowNames = TRUE) expect_equal(x, mt) # rowNames = TRUE, colNames = FALSE # Row names = first column # Col names = X1, X2, etc # need to create an expected output y <- as.data.frame(rbind(colnames(mt), as.matrix(mt))) colnames(y) <- c(make.names(seq_along(mt))) x <- read.xlsx(fileName, sheet = 1, rowNames = TRUE, colNames = FALSE) expect_equal(x, y) # rowNames = FALSE, colNames = TRUE # Row names = "" # Cl names = first row y2 <- cbind(row.names(mt), mt) colnames(y2)[1] <- "" row.names(y2) <- NULL x <- read.xlsx(fileName, sheet = 1, rowNames = FALSE, colNames = TRUE) expect_equal(x, y2) # rowNames = FALSE, colNames = FALSE # Row names = "" # Col names = X1, X2, etc y3 <- cbind(row.names(y), y) colnames(y3) <- make.names(seq_along(y3)) row.names(y3) <- NULL x <- read.xlsx(fileName, sheet = 1, rowNames = FALSE, colNames = FALSE) expect_equal(x, y3) # Check wd expect_equal(getwd(), curr_wd) unlink(fileName, recursive = TRUE, force = TRUE) options(op) }) test_that("Writing then reading returns identical data.frame 3", { op <- options() options(openxlsx.dateFormat = "yyyy-mm-dd") ## data df <- data.frame( Date = as.Date("2021-05-21") - 0:4, Logical = c(TRUE, FALSE, TRUE, TRUE, FALSE), Currency = -2:2, Accounting = -2:2, hLink = "https://CRAN.R-project.org/", Percentage = seq.int(-1, 1, length.out = 5), TinyNumber = runif(5) / 1E9, stringsAsFactors = FALSE ) class(df$Currency) <- "currency" class(df$Accounting) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumber) <- "scientific" fileName <- tempfile("allClasses", fileext = ".xlsx") write.xlsx(df, file = fileName, overwrite = TRUE) ## rows, cols combinations rows <- 1:4 cols <- c(1, 3, 5) x <- read.xlsx(fileName, detectDates = TRUE, rows = rows, cols = cols) exp <- df[sort((rows - 1)[(rows - 1) <= nrow(df)]), sort(cols[cols <= ncol(df)])] expect_equal(x, exp) rows <- 1:4 cols <- 1:9 x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols) exp <- df[sort((rows - 1)[(rows - 1) <= nrow(df)]), sort(cols[cols <= ncol(df)])] expect_equal(x, exp) rows <- 1:200 cols <- c(5, 99, 2) x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols) exp <- df[sort((rows - 1)[(rows - 1) <= nrow(df)]), sort(cols[cols <= ncol(df)])] expect_equal(x, exp) rows <- 1000:900 cols <- c(5, 99, 2) suppressWarnings(x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols)) expect_identical(x, NULL) unlink(fileName, recursive = TRUE, force = TRUE) options(op) }) test_that("Writing then reading returns identical data.frame 4", { ## data df <- head(iris[, 1:4]) df[1, 2] <- NA df[3, 1] <- NA df[6, 4] <- NA tf <- temp_xlsx() write.xlsx(x = df, file = tf, keepNA = TRUE) x <- read.xlsx(tf) expect_equal(object = x, expected = df, check.attributes = TRUE) unlink(tf, recursive = TRUE, force = TRUE) tf <- temp_xlsx() write.xlsx(x = df, file = tf, keepNA = FALSE) x <- read.xlsx(tf) expect_equal(object = x, expected = df, check.attributes = TRUE) unlink(tf, recursive = TRUE, force = TRUE) }) test_that("Writing then reading returns identical data.frame 5", { ## data df <- head(iris[, 1:4]) df[1, 2] <- NA df[3, 1] <- NA df[6, 4] <- NA na.string <- "*" df_expected <- df df_expected[1, 2] <- na.string df_expected[3, 1] <- na.string df_expected[6, 4] <- na.string tf <- temp_xlsx() write.xlsx(x = df, file = tf, keepNA = TRUE, na.string = na.string) x <- read.xlsx(tf) expect_equal(object = x, expected = df_expected, check.attributes = TRUE) unlink(tf, recursive = TRUE, force = TRUE) }) test_that("Special characters in sheet names", { tf <- temp_xlsx() ## data sheet_name <- "A & B < D > D" wb <- createWorkbook() addWorksheet(wb, sheetName = sheet_name) addWorksheet(wb, sheetName = "test") writeData(wb, sheet = 1, x = 1:10) saveWorkbook(wb = wb, file = tf, overwrite = TRUE) expect_equal(getSheetNames(tf)[1], sheet_name) expect_equal(getSheetNames(tf)[2], "test") expect_equal(read.xlsx(tf, colNames = FALSE)[[1]], 1:10) unlink(tf, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-loading_workbook.R0000644000176200001440000022421514155600364021600 0ustar liggesusers context("Load Workbook Object") test_that("Loading readTest.xlsx Sheet 1", { fl <- system.file("extdata", "readTest.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) sheet_data <- wb$worksheets[[2]]$sheet_data sheet_v <- sheet_data$v sheet_t <- sheet_data$t sheet_f <- sheet_data$f sheet_row <- sheet_data$rows sheet_col <- sheet_data$cols ## Sheet 2 expected_row <- c( 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 5L, 5L, 5L, 5L, 6L, 6L, 6L, 6L, 6L, 6L, 6L, 7L, 9L, 10L, 10L, 10L, 11L, 12L, 13L, 13L, 13L, 14L, 14L, 14L, 14L, 15L, 15L, 16L, 16L, 16L, 16L, 17L, 17L, 17L, 17L, 18L, 19L, 19L, 20L, 20L, 21L, 22L, 22L, 23L, 23L, 24L, 25L, 25L, 26L, 26L, 26L, 27L, 27L, 28L, 28L, 28L, 29L, 30L, 31L, 31L, 31L, 32L, 33L, 33L, 33L, 34L, 35L ) expect_equal(sheet_row, expected_row) expected_col <- c( 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 1L, 1L, 1L, 2L, 3L, 3L, 1L, 4L, 5L, 6L, 1L, 5L, 6L, 8L, 1L, 2L, 2L, 6L, 7L, 8L, 2L, 3L, 5L, 6L, 2L, 2L, 4L, 2L, 3L, 4L, 5L, 6L, 2L, 5L, 5L, 4L, 6L, 2L, 3L, 7L, 1L, 8L, 2L, 3L, 7L, 7L, 4L, 5L, 6L, 7L, 8L, 7L, 8L, 9L, 8L, 1L ) expect_equal(sheet_col, expected_col) expected_t <- c( 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) expect_equal(sheet_t, expected_t) expected_v <- c( "0", "1", "2", "3", "4", "5", "6", "7", "8", "1", "2", "3", "4", "5", "6", "7", "8", "9", "1", "2", "3", "4", "5", "6", "7", "8", "9", "1", "2", "3", "4", "5", "6", "7", "8", "8", "2", "2", "3", "4", "4", "5", "6", "1", "1", "2", "2", "2", "3", "3", "1", "2", "2", "34", "3", "4", "2", "2", "2", "3", "2", "6", "3", "3", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "2", "35" ) expect_equal(sheet_v, expected_v) ## Sheet 3 expected_col_widths <- structure(c("41.430625", "11.29", "11.0009375", "8.71578125"), .Names = c("3", "4", "5", "6") ) attr(expected_col_widths, "hidden") <- rep("0", 4) expect_equal(wb$colWidths[[3]], expected_col_widths) }) test_that("Loading readTest.xlsx Sheet 1", { fl <- system.file("extdata", "readTest.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) sheet_data <- wb$worksheets[[1]]$sheet_data sheet_v <- sheet_data$v sheet_t <- sheet_data$t sheet_f <- sheet_data$f sheet_row <- sheet_data$rows sheet_col <- sheet_data$cols ## sheet 1 expected_row <- c( 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 4L, 5L, 5L, 5L, 5L, 6L, 6L, 6L, 6L, 6L, 7L, 7L, 7L, 7L, 7L, 8L, 9L, 9L, 9L, 9L, 9L, 10L, 10L, 10L, 10L, 10L, 11L, 11L, 11L ) expect_equal(sheet_row, expected_row) expected_col <- c( 1L, 2L, 4L, 5L, 6L, 7L, 8L, 1L, 2L, 4L, 5L, 6L, 7L, 8L, 1L, 4L, 5L, 6L, 8L, 1L, 2L, 4L, 5L, 6L, 8L, 1L, 2L, 5L, 6L, 1L, 2L, 4L, 5L, 6L, 1L, 2L, 4L, 5L, 6L, 6L, 1L, 2L, 4L, 5L, 6L, 1L, 2L, 4L, 5L, 6L, 2L, 4L, 6L ) expect_equal(sheet_col, expected_col) expected_t <- c( 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 1, 0, 3, 4, 2, 4, 1, 0, 4, 2, 0, 0, 1, 0, 4, 2, 0, 4, NA, 2, 0, 0, 1, NA, 2, 0, 0, 1, 0, 0, 2, 0, 0, 1, 0, 2, 0, 0, 1, 0, 0, 0, 0 ) expect_equal(sheet_t, expected_t) expected_v <- c( "2096", "2097", "2098", "2099", "2107", "2108", "2109", "1", "1", "1", "2100", "42042", "3209324 This", "#DIV/0!", "1", "#NUM!", "2101", "42041", "#N/A", "1", "2", "1.34", "2102", "42040", "#NUM!", "0", "2", "#NUM!", NA, "0", "3", "1.56", "2103", NA, "0", "1", "1.7", "2104", "42037", "42036", "0", "2", "23", "2105", "42035", "0", "3", "67.3", "2106", "42034", "1", "123", "42033" ) expect_equal(sheet_v, expected_v) expected_f <- c( NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "\"3209324\" & \" This\"", "1/0", NA, NA, NA, NA, "#N/A", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA ) expect_equal(sheet_f, expected_f) ## Column Widths expected_col_widths <- structure("10.8603125", .Names = "6") attr(expected_col_widths, "hidden") <- "0" expect_equal(wb$colWidths[[1]], expected_col_widths) expected_shared_strings <- structure(c( "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "bool", "Date", "value", "word", "N-Z-P-S-Y", "C-G-D-X-H", "B-K-A-O-W", "H-P-G-O-K", "F-P-C-L-T", "A-N-Q-P-V", "Y-E-B-K-O", "V-S-N-T-R", "F-K-Z-U-S", "O-E-Z-T-G", "Q-X-F-L-N", "E-D-Y-Z-N", "W-F-L-C-I", "P-S-W-Y-E", "P-H-N-Q-Z", "S-O-L-W-J", "J-E-F-Q-K", "D-N-O-P-Z", "H-Z-K-S-U", "B-P-A-Y-R", "Z-I-X-J-V", "Y-S-I-M-X", "V-A-C-R-O", "O-V-S-C-Q", "A-K-S-V-W", "B-G-U-S-J", "Z-E-J-V-T", "P-F-C-N-T", "L-T-Z-D-V", "K-Q-Y-N-O", "U-S-Z-O-E", "Y-F-Z-C-P", "P-Y-M-I-K", "D-Y-A-L-T", "W-I-F-A-B", "I-H-S-W-K", "U-D-J-F-K", "B-K-G-J-V", "Y-J-E-N-B", "X-L-V-S-U", "A-I-B-S-P", "U-L-D-O-M", "M-D-V-R-X", "O-Q-K-S-B", "O-R-X-C-W", "O-F-M-A-X", "J-K-V-E-X", "W-B-S-O-A", "R-N-D-G-S", "W-J-K-M-R", "K-I-H-F-M", "U-F-X-P-A", "M-R-C-H-L", "H-A-E-X-J", "C-B-K-L-S", "V-A-I-S-L", "B-L-N-J-G", "J-X-A-D-O", "I-S-W-P-U", "D-R-M-G-C", "M-V-U-D-W", "U-S-A-Z-B", "T-E-N-F-P", "K-E-S-Z-Y", "D-J-O-T-A", "F-U-Y-T-R", "Q-U-N-P-J", "D-C-V-A-X", "S-B-F-E-V", "U-R-P-H-A", "R-C-Z-J-B", "L-B-M-F-I", "D-Y-B-S-Q", "X-Y-D-W-P", "Z-L-I-V-R", "Z-W-T-M-B", "Z-U-S-E-G", "V-I-M-C-O", "E-K-D-R-Z", "Z-F-H-Y-D", "O-I-X-M-A", "F-K-U-G-T", "I-P-X-J-M", "F-N-Z-E-C", "F-A-M-X-E", "R-V-C-O-P", "X-Y-C-V-H", "K-G-T-Y-I", "N-M-E-D-F", "M-K-N-U-W", "L-K-J-A-B", "S-M-T-D-A", "W-D-G-F-U", "X-R-Z-F-E", "H-L-N-G-P", "Z-X-W-M-R", "E-F-G-J-V", "X-I-M-Z-V", "T-A-O-L-Q", "F-T-X-N-B", "O-G-D-P-A", "B-K-Z-V-M", "M-J-O-S-X", "O-D-M-S-G", "W-O-V-A-D", "I-D-W-T-H", "E-C-I-A-L", "P-I-W-U-T", "Y-P-U-L-C", "Q-N-A-B-E", "C-F-X-Y-H", "Q-P-G-I-J", "A-Q-J-W-F", "G-N-R-U-D", "G-L-I-F-V", "Y-R-P-K-X", "V-W-B-G-S", "E-Q-D-N-F", "E-R-U-D-O", "O-E-G-X-L", "D-Q-G-A-K", "U-Z-N-C-V", "O-K-T-W-X", "L-W-G-K-Q", "I-F-O-X-Q", "J-B-V-W-T", "U-H-I-P-Q", "R-W-M-S-U", "F-U-M-W-H", "F-A-Q-U-K", "Q-R-D-K-I", "L-P-K-V-S", "G-I-B-U-Q", "Z-W-L-G-E", "Q-C-I-B-A", "J-N-Y-W-D", "T-Y-G-W-S", "L-M-A-G-K", "O-D-S-T-K", "O-G-L-T-Z", "N-Q-E-B-F", "B-F-W-A-X", "U-G-Q-B-M", "B-O-V-U-A", "R-K-X-H-A", "B-P-Q-T-R", "I-P-Z-L-V", "C-T-L-W-D", "L-Q-D-M-U", "N-P-H-A-G", "F-O-P-G-M", "N-M-Z-W-L", "G-V-K-Z-T", "F-J-T-C-U", "J-P-R-A-C", "Z-X-V-C-W", "B-Z-K-I-Q", "E-N-L-I-Y", "C-D-P-R-B", "I-W-X-P-V", "T-M-P-I-O", "W-Y-D-J-K", "A-O-C-L-B", "S-R-O-Y-C", "A-O-Y-N-W", "P-C-O-D-Y", "E-S-A-L-Y", "E-O-Q-W-C", "U-O-A-X-Q", "W-E-N-Y-D", "A-W-J-Z-X", "P-L-R-U-K", "V-J-E-K-Z", "V-A-M-G-D", "N-C-F-M-G", "H-W-N-K-R", "G-T-A-F-O", "K-F-V-G-S", "N-V-H-G-I", "F-Y-T-S-G", "A-H-O-C-V", "Q-W-J-S-C", "W-G-I-X-E", "D-B-V-A-N", "P-N-L-Y-Q", "O-I-R-Q-U", "D-R-W-E-O", "W-X-U-V-P", "X-Y-D-A-W", "M-J-B-S-G", "K-R-G-N-Y", "O-B-K-L-M", "G-N-Y-X-D", "K-F-A-B-T", "S-Z-R-L-A", "K-Y-W-X-A", "O-Q-X-I-D", "I-L-R-M-X", "S-D-V-U-N", "B-W-F-A-T", "A-W-X-R-J", "D-H-G-X-L", "E-C-V-U-T", "D-W-P-Z-F", "V-O-M-P-R", "P-L-B-X-N", "Z-U-F-D-V", "M-K-Z-S-Y", "X-P-N-T-D", "U-Q-D-T-S", "N-I-X-S-O", "S-C-K-F-D", "N-V-I-R-D", "Z-X-Y-B-P", "U-W-R-D-V", "G-V-I-N-K", "D-Y-N-T-J", "P-K-F-U-W", "U-I-P-D-Q", "R-T-Q-N-Z", "Z-R-D-V-O", "Z-S-F-T-D", "X-K-Z-B-W", "U-F-Z-S-Y", "X-I-T-Z-K", "A-X-H-N-Z", "R-U-Q-W-J", "C-H-P-V-Y", "R-O-A-T-E", "L-F-B-X-A", "Z-X-E-C-G", "R-B-C-Q-W", "A-O-Z-U-B", "R-W-P-S-H", "R-Y-B-A-W", "K-X-U-I-M", "O-X-F-P-A", "U-Y-P-D-M", "A-D-K-R-M", "R-U-D-T-M", "H-Q-Y-K-J", "T-B-H-N-U", "P-I-V-X-W", "S-H-X-C-U", "I-O-Y-G-W", "A-U-Z-J-R", "Q-U-H-M-A", "B-W-M-I-C", "P-X-Z-Y-N", "Y-J-W-N-B", "V-Y-U-S-B", "K-W-S-Q-M", "I-K-X-H-S", "F-L-M-Q-T", "S-Z-O-K-L", "O-P-N-G-E", "P-H-R-Q-T", "M-L-A-D-T", "D-S-X-V-H", "F-C-O-A-B", "P-I-N-O-H", "H-X-Y-I-C", "S-I-R-P-Q", "P-M-F-H-Y", "S-R-O-T-Q", "X-H-O-B-R", "W-P-A-Q-V", "E-L-Q-G-Y", "I-S-T-W-C", "B-M-R-G-Y", "S-J-A-K-Q", "E-P-B-G-J", "R-Y-E-L-D", "A-R-C-N-S", "Y-H-B-M-I", "T-Q-C-K-N", "L-T-Z-V-C", "Q-L-N-J-K", "T-E-J-M-Y", "E-M-F-R-C", "M-A-Y-D-I", "G-M-Y-F-Q", "A-X-B-E-N", "F-Y-E-H-L", "S-Z-R-M-O", "U-V-W-S-I", "D-S-E-L-K", "B-C-X-V-F", "Q-V-L-F-H", "H-Z-I-J-P", "W-U-O-M-D", "A-Y-T-O-X", "Y-C-Z-V-D", "E-L-O-X-Y", "D-U-K-X-A", "S-W-K-N-E", "D-F-T-E-Y", "J-T-B-C-L", "E-T-Z-V-F", "Q-D-U-P-E", "M-Q-X-T-J", "A-M-I-K-P", "J-T-B-E-R", "L-X-J-O-F", "X-B-V-W-P", "Y-N-H-G-Z", "M-F-K-S-P", "K-B-V-Z-L", "T-L-Y-G-A", "N-U-X-C-D", "W-B-O-X-Z", "U-Y-J-F-A", "T-V-J-I-S", "T-L-W-X-Z", "V-K-X-C-N", "G-C-I-S-Z", "C-T-K-S-Z", "F-I-D-N-E", "E-C-M-Y-J", "N-C-Y-S-I", "B-N-I-L-D", "Y-F-T-M-V", "R-A-E-O-M", "M-G-P-W-X", "H-B-C-D-R", "A-J-N-C-H", "R-O-F-D-N", "H-E-R-T-K", "M-U-S-V-A", "L-V-F-A-H", "E-Q-N-Y-C", "T-N-L-X-S", "C-F-X-A-N", "M-X-Y-C-Z", "A-N-F-B-K", "J-Q-R-Z-D", "Q-P-Y-G-N", "D-Y-O-R-J", "K-R-Y-N-O", "A-H-K-J-B", "O-G-J-A-S", "T-N-K-X-S", "V-P-X-Y-F", "R-P-V-G-T", "O-B-F-E-D", "Z-P-I-T-F", "T-S-D-X-G", "Y-J-P-Z-U", "H-W-Y-K-J", "H-Z-C-F-Q", "D-I-B-N-X", "K-B-U-H-E", "H-Y-U-Q-A", "N-Q-G-T-I", "J-B-V-E-G", "M-F-O-E-H", "B-P-K-M-I", "T-Z-B-Q-Y", "X-Y-T-P-O", "E-K-I-W-C", "C-M-R-S-Y", "F-Y-M-W-L", "A-S-E-U-J", "I-P-W-N-G", "V-G-R-E-T", "H-O-W-G-Y", "O-H-B-C-P", "C-Y-D-Q-X", "X-Y-I-Z-U", "R-I-Q-Y-P", "E-M-L-D-Y", "B-G-P-Y-T", "Z-H-X-D-V", "S-B-R-J-F", "O-T-X-P-W", "Y-C-T-M-E", "J-A-D-O-P", "B-W-Q-D-I", "Y-T-X-K-Q", "F-D-P-X-J", "Z-G-Y-O-N", "J-N-Z-Q-P", "W-C-E-I-U", "L-R-K-F-H", "X-I-G-B-O", "M-C-Q-Y-Z", "S-T-W-J-E", "G-O-N-Y-Q", "O-Q-R-Z-B", "X-G-E-C-I", "P-B-D-F-Q", "Q-H-D-I-V", "H-I-J-D-Q", "C-B-S-I-G", "M-A-F-D-B", "G-Z-R-U-K", "N-U-L-Q-R", "C-A-K-M-T", "F-S-R-B-K", "O-D-X-S-W", "H-J-N-P-C", "N-G-Y-L-J", "D-X-K-O-E", "F-E-H-D-L", "E-M-D-P-Z", "Q-K-I-J-V", "D-O-N-M-X", "C-P-N-E-K", "L-H-T-U-P", "M-T-Z-P-H", "B-T-L-Z-G", "Z-V-R-C-I", "J-M-R-D-G", "I-V-L-Q-T", "O-V-X-A-M", "V-L-N-A-T", "M-B-R-U-O", "O-B-Q-F-X", "H-O-E-K-G", "S-H-G-B-D", "N-Z-C-L-D", "M-F-J-H-K", "A-O-D-W-B", "I-X-G-K-W", "B-S-O-K-Q", "X-T-Z-I-D", "N-D-G-B-L", "Z-O-U-I-X", "W-B-A-R-H", "S-G-Q-J-F", "M-B-J-F-A", "M-B-X-A-P", "F-M-S-Y-V", "T-K-B-G-C", "H-V-C-G-X", "V-A-S-I-T", "Z-X-G-U-S", "U-J-Y-M-H", "D-Y-H-X-S", "T-Q-X-I-E", "S-V-K-M-T", "S-Z-P-O-Y", "V-Y-Q-L-F", "A-E-R-V-G", "E-C-M-G-O", "T-B-U-M-V", "M-R-V-A-E", "C-A-R-W-N", "Y-U-D-Z-X", "Y-G-Z-C-Q", "T-X-R-A-D", "S-A-U-R-K", "E-A-D-R-V", "P-R-T-W-F", "A-Z-Y-O-N", "O-P-W-A-I", "H-V-U-R-F", "W-D-G-P-I", "M-Z-Q-C-I", "Y-S-H-W-I", "F-J-C-N-S", "H-C-Z-Y-K", "W-J-K-Z-I", "Q-T-G-C-X", "S-Q-T-O-Y", "Z-G-T-W-X", "X-M-J-R-W", "S-Y-N-F-H", "C-F-V-G-A", "W-R-J-I-B", "I-V-B-A-M", "R-C-D-L-U", "S-T-B-A-N", "F-W-J-M-A", "J-I-R-E-H", "Q-D-O-E-R", "R-B-M-O-J", "B-X-G-V-U", "R-J-L-Y-M", "R-F-A-X-J", "N-I-J-V-Q", "J-O-D-Q-Z", "K-I-F-D-H", "N-H-Q-Z-M", "I-K-R-Z-X", "O-D-U-T-I", "K-G-S-L-Y", "X-D-O-K-A", "C-X-I-P-J", "X-A-Y-V-T", "I-V-T-J-O", "X-D-M-H-U", "M-L-R-Y-J", "N-L-I-R-B", "A-J-Z-O-Q", "G-Y-I-S-E", "N-F-X-Z-T", "U-G-C-F-K", "R-N-W-S-M", "A-O-T-N-U", "W-O-P-G-V", "D-W-E-H-O", "B-D-S-E-Z", "C-L-V-M-I", "Y-N-C-W-B", "C-Q-L-P-W", "X-H-L-B-M", "H-V-D-K-Q", "O-S-C-U-H", "V-I-J-K-A", "D-M-B-I-F", "O-H-N-J-V", "R-L-Q-J-Y", "Q-P-B-S-X", "J-K-V-H-F", "A-T-L-Q-B", "E-D-S-B-G", "K-T-X-J-Q", "W-L-U-B-H", "T-Y-Z-G-V", "G-S-C-X-P", "D-C-M-Z-Y", "V-G-F-D-E", "Q-F-X-O-U", "E-V-L-F-G", "M-T-V-U-Y", "Z-X-S-U-E", "J-I-F-E-R", "C-V-J-R-W", "W-R-V-U-K", "M-A-S-U-Q", "J-K-O-F-I", "I-Y-G-U-Z", "B-Y-V-D-R", "M-G-Q-N-J", "A-F-N-K-M", "R-B-D-Q-P", "H-T-N-U-L", "J-W-E-A-B", "N-M-K-R-O", "H-Q-A-N-E", "U-B-Y-L-Q", "J-E-N-T-Q", "D-N-L-R-E", "K-S-O-L-Z", "Z-D-Y-A-N", "D-Q-B-X-Z", "J-E-F-O-Q", "U-E-Q-T-R", "U-H-F-N-L", "C-T-I-V-X", "V-L-Q-O-K", "G-Q-D-P-V", "D-T-V-G-S", "I-G-H-Z-L", "A-E-I-Y-B", "S-V-N-B-R", "M-P-J-Y-N", "D-T-W-Q-Z", "U-V-F-A-S", "M-B-T-Y-Q", "F-I-C-D-X", "G-X-O-K-J", "J-M-I-E-D", "C-B-S-F-A", "A-Y-O-Z-P", "R-T-H-L-S", "P-X-B-S-O", "B-I-C-P-T", "F-K-H-Z-N", "R-D-Y-T-P", "S-U-P-G-R", "M-K-R-Q-V", "Z-E-O-U-T", "M-W-Y-X-C", "Q-J-U-T-B", "L-H-S-J-U", "P-M-X-R-N", "S-Y-T-G-W", "W-P-N-V-O", "E-U-I-L-M", "V-S-K-J-R", "P-E-T-C-X", "E-V-C-S-Y", "M-S-F-T-Q", "L-D-P-K-T", "D-Z-U-Q-P", "D-H-L-W-N", "V-U-Q-I-A", "L-D-G-H-V", "G-I-U-Q-E", "G-E-D-R-H", "N-T-L-K-H", "J-U-K-F-V", "G-J-Y-K-W", "E-A-I-G-Q", "S-U-H-R-T", "L-S-W-H-C", "P-V-I-Y-O", "E-L-K-X-N", "Y-B-S-T-N", "N-U-V-E-Z", "B-V-K-M-O", "L-V-H-A-K", "M-U-T-J-K", "V-J-T-F-R", "T-Y-E-U-W", "C-D-B-A-L", "K-E-U-S-A", "D-H-R-X-Z", "M-B-Z-G-C", "P-E-T-S-Y", "M-G-O-J-F", "C-Y-E-P-X", "R-V-D-C-N", "S-Y-A-K-Z", "K-S-G-T-D", "D-F-G-U-K", "F-B-P-T-M", "P-G-O-D-W", "U-L-I-R-J", "F-Q-N-X-J", "D-Q-F-V-B", "R-P-E-Z-H", "A-H-X-M-L", "I-H-F-G-W", "V-C-M-H-Y", "V-H-L-Y-F", "H-I-L-P-V", "L-Q-W-K-A", "D-W-J-R-L", "W-E-V-J-L", "F-Z-X-U-H", "K-U-Q-I-R", "S-D-N-E-V", "G-T-E-L-Y", "S-P-E-B-D", "U-N-L-S-O", "Z-G-W-I-X", "M-C-X-S-E", "P-C-S-X-Y", "B-Z-K-R-H", "D-J-W-Y-U", "J-O-Q-F-P", "I-A-V-G-Y", "U-B-V-G-N", "W-H-Q-M-E", "J-R-O-D-F", "W-M-C-O-P", "R-I-F-M-Q", "Q-L-D-W-X", "M-A-Q-P-F", "O-J-T-L-A", "X-S-I-P-G", "G-W-D-Y-F", "T-B-M-N-J", "Q-W-N-Z-C", "M-F-C-O-H", "Z-N-Q-X-P", "Q-G-A-Y-C", "R-F-G-P-E", "X-J-F-R-C", "J-S-Q-E-L", "O-K-P-F-D", "R-J-W-G-T", "Y-P-J-N-D", "N-S-F-Y-T", "M-L-N-H-U", "V-A-H-G-Q", "H-L-W-K-P", "U-P-G-V-O", "V-N-P-I-Y", "A-O-E-F-L", "W-F-Q-J-G", "B-A-L-V-Q", "Y-J-F-V-S", "O-F-E-J-A", "X-O-F-M-B", "B-M-L-H-W", "Z-X-F-T-B", "W-X-E-M-A", "F-J-V-W-L", "P-C-U-R-O", "S-K-F-D-V", "K-F-Z-Q-C", "J-S-R-M-Q", "E-H-L-Q-N", "W-F-M-E-X", "P-R-E-N-A", "D-F-G-N-Y", "I-S-O-V-T", "R-I-C-N-L", "I-T-C-Y-P", "R-W-I-K-X", "P-B-J-X-G", "D-W-F-N-E", "M-G-C-B-K", "E-T-H-F-W", "A-E-L-F-Z", "Z-V-W-S-R", "O-T-L-D-Q", "S-E-M-Z-O", "N-R-Y-A-U", "Y-D-M-A-R", "S-M-P-N-K", "C-T-B-L-Z", "X-A-L-I-V", "B-V-M-G-S", "N-R-K-Q-D", "F-O-L-X-Y", "Y-T-F-A-S", "X-G-O-U-A", "Z-F-I-B-T", "V-H-B-N-W", "V-K-B-W-S", "V-C-T-L-G", "X-N-L-Y-Q", "N-D-L-I-Z", "L-K-G-N-E", "D-L-M-K-Z", "E-I-P-Z-U", "H-X-B-C-D", "B-H-D-C-V", "F-O-L-D-R", "B-Z-J-Y-V", "E-C-R-B-S", "E-X-V-B-S", "P-K-I-W-G", "A-I-F-V-O", "D-F-R-I-E", "X-L-I-N-O", "P-Y-Q-C-S", "C-P-A-X-L", "W-O-U-A-X", "H-M-R-E-B", "K-Y-P-G-A", "O-E-V-D-C", "Z-A-K-M-W", "S-F-M-Z-E", "X-U-I-C-J", "C-V-T-B-N", "Z-Q-Y-V-G", "T-W-G-Q-D", "K-Y-L-R-F", "W-O-S-E-A", "V-T-Q-F-G", "G-V-J-M-U", "P-R-A-N-C", "I-R-A-F-T", "X-Z-U-W-N", "A-G-R-D-Y", "J-U-T-A-Q", "K-Y-T-H-U", "P-Q-L-Z-G", "N-K-Y-X-W", "A-T-M-R-Z", "M-B-P-C-L", "M-Q-K-N-R", "I-W-H-G-R", "I-F-D-Q-A", "V-G-F-C-X", "H-Q-F-D-T", "N-R-T-Q-G", "X-V-P-B-G", "V-H-B-N-X", "G-Q-T-J-Y", "P-F-A-N-H", "C-D-I-W-K", "T-R-J-B-P", "E-P-X-L-S", "O-K-B-L-M", "Z-T-B-R-V", "V-N-Y-Z-U", "E-W-V-F-O", "D-S-A-Z-J", "R-O-W-A-Y", "V-L-K-J-Q", "Q-T-J-S-Z", "M-G-L-Y-D", "G-U-W-I-C", "G-O-D-T-I", "R-L-Z-P-V", "W-G-M-T-I", "S-F-H-Z-J", "Z-L-O-V-N", "G-D-A-U-H", "Y-K-V-R-E", "Y-O-L-R-I", "X-V-Z-I-B", "N-Z-S-D-O", "N-X-Z-J-M", "S-X-A-H-C", "V-T-R-F-H", "K-E-Q-B-J", "V-R-J-U-G", "Q-K-B-P-Z", "Y-I-S-X-K", "U-O-R-Q-J", "Q-T-X-F-D", "P-O-F-B-J", "C-M-K-L-F", "N-W-Y-A-V", "E-H-I-G-U", "L-J-X-A-C", "Q-K-U-D-B", "A-S-R-D-T", "S-Z-E-Y-U", "A-K-R-S-G", "S-F-R-J-Q", "A-Y-J-F-U", "L-I-J-R-H", "K-C-V-Q-F", "H-U-A-T-D", "E-W-L-I-C", "Y-P-R-F-V", "L-M-P-Z-U", "T-Q-U-P-V", "Q-W-T-M-K", "P-D-G-Y-K", "F-I-K-C-P", "I-T-Y-K-L", "H-T-K-I-R", "H-K-B-M-F", "J-P-D-Z-Q", "D-M-K-C-V", "E-K-Y-F-R", "P-L-H-A-J", "R-C-Z-V-T", "I-W-G-A-T", "Y-N-X-K-R", "M-N-E-C-Q", "J-B-W-R-X", "R-M-D-T-F", "R-V-L-Y-G", "M-V-E-Z-Q", "S-H-Q-X-G", "H-P-Y-Q-G", "F-N-K-T-W", "I-B-Z-P-F", "G-P-N-S-F", "B-Y-S-N-A", "P-I-Z-A-S", "X-I-K-B-Y", "B-Q-F-W-M", "Y-E-J-P-M", "V-E-T-G-O", "M-N-L-K-I", "N-D-W-B-V", "F-P-S-M-X", "K-H-Q-M-F", "Z-B-O-I-L", "L-F-D-S-E", "W-Y-P-B-A", "L-P-S-V-U", "D-G-L-J-P", "K-U-F-Y-E", "J-G-R-M-N", "J-U-P-H-O", "O-U-N-M-W", "X-V-J-K-E", "C-W-G-L-K", "D-H-A-O-K", "H-G-C-X-P", "B-C-I-J-D", "N-L-T-D-S", "X-D-C-A-T", "Z-D-U-N-E", "P-W-A-I-L", "N-U-G-H-C", "F-Q-E-V-T", "X-O-M-S-U", "Z-V-S-Q-R", "K-Z-U-D-L", "A-O-Z-C-T", "S-K-U-T-Q", "V-Q-I-B-Z", "A-K-Z-N-Y", "T-G-V-Y-O", "G-K-R-A-J", "Y-J-F-T-U", "E-P-K-G-F", "U-P-X-L-V", "H-C-S-M-I", "K-D-X-W-N", "F-E-P-V-R", "C-P-V-W-L", "I-S-M-E-B", "D-E-L-C-O", "A-D-V-U-W", "D-I-N-M-Z", "O-Z-K-S-N", "F-J-W-L-S", "H-C-V-W-I", "B-A-L-V-Y", "N-K-Z-W-X", "Z-C-X-K-A", "S-X-H-G-Y", "L-G-M-V-Q", "Z-L-G-Y-Q", "J-W-E-A-D", "G-S-A-M-U", "F-M-D-K-O", "B-O-G-R-K", "V-S-U-Q-B", "R-X-O-N-F", "Y-N-M-H-I", "T-J-W-Q-L", "R-W-P-B-H", "D-A-I-P-E", "D-P-Q-T-N", "Y-K-Q-U-X", "A-Y-M-C-R", "M-O-I-L-B", "Y-O-K-J-F", "O-C-J-X-H", "W-J-X-L-Z", "F-P-H-A-L", "M-T-F-U-P", "W-H-F-C-X", "R-C-D-Z-L", "Y-B-A-I-L", "S-I-Y-W-P", "K-D-X-G-Z", "O-W-Q-M-G", "M-T-V-L-G", "F-Z-Y-J-L", "V-L-A-S-N", "I-P-A-S-N", "G-T-Q-D-F", "G-R-L-W-V", "I-R-Z-V-P", "M-L-B-A-I", "D-K-Z-F-M", "M-O-G-X-V", "Y-V-D-Z-W", "M-S-O-G-T", "Z-F-M-U-X", "V-N-Z-P-I", "N-D-F-U-J", "G-O-U-E-S", "Z-C-G-W-B", "O-T-E-N-V", "W-P-H-C-V", "X-R-D-P-G", "Y-C-E-F-T", "V-O-G-K-I", "S-I-W-M-L", "M-H-C-O-A", "Q-C-N-Z-D", "A-N-L-S-T", "X-W-I-L-K", "A-Y-V-S-K", "D-W-F-L-K", "U-Z-I-R-Q", "V-W-B-A-T", "I-U-R-E-W", "N-W-U-Q-O", "F-H-L-N-O", "D-I-T-J-H", "W-K-Q-D-T", "A-O-R-I-U", "M-E-V-A-F", "U-J-M-X-S", "O-E-R-K-H", "A-V-Z-T-D", "C-Z-B-D-L", "D-J-T-Q-G", "A-O-C-I-Z", "J-L-A-Y-C", "P-O-H-E-V", "A-M-Z-I-R", "W-C-R-H-V", "Z-G-I-V-N", "K-M-W-T-Q", "O-I-F-D-N", "X-D-A-V-T", "T-S-M-B-D", "F-C-D-U-Q", "B-H-M-Q-G", "M-K-I-W-B", "R-M-Q-W-P", "N-Z-L-F-G", "I-P-J-B-W", "O-Y-W-U-V", "C-V-Z-B-O", "O-Q-S-F-X", "B-G-F-M-W", "N-X-R-P-S", "L-T-A-H-R", "G-O-E-L-X", "R-I-U-X-Y", "Q-O-Z-F-V", "O-P-J-C-M", "D-W-G-S-L", "T-K-W-L-O", "E-I-O-K-D", "K-E-A-I-S", "M-I-W-F-K", "S-W-G-T-N", "P-L-J-D-A", "O-M-T-J-P", "C-R-Z-L-G", "F-S-D-V-K", "N-J-H-V-I", "D-I-A-F-J", "U-R-T-V-X", "J-N-T-R-S", "K-V-Z-T-Q", "J-L-C-R-M", "Z-A-R-L-E", "Y-L-W-I-S", "B-W-E-N-U", "B-S-W-J-F", "D-G-I-P-S", "D-Q-M-E-R", "K-T-R-A-D", "P-Q-L-E-U", "U-D-W-A-O", "Q-R-P-D-Y", "C-X-V-S-I", "F-E-U-I-K", "H-Z-T-L-P", "P-I-U-B-O", "S-Q-G-W-K", "K-V-S-U-L", "F-N-Q-O-P", "M-Q-F-C-L", "V-X-G-C-A", "N-S-E-C-V", "R-K-J-A-X", "D-L-H-W-S", "Z-C-Y-L-G", "M-D-W-F-U", "R-A-U-S-F", "Y-W-H-V-Q", "B-K-G-J-W", "B-V-H-E-C", "D-J-X-S-H", "W-C-H-Z-Q", "X-B-A-I-U", "D-A-J-C-F", "B-P-U-S-W", "A-D-R-Z-K", "S-H-P-A-Z", "J-O-U-P-S", "W-C-H-P-T", "C-U-Z-O-W", "P-E-H-M-S", "B-W-L-Y-N", "N-S-F-R-Y", "W-R-K-A-N", "G-X-V-D-E", "I-G-D-P-T", "W-N-J-R-L", "P-Q-H-A-V", "U-Q-R-P-W", "I-H-Q-X-U", "W-K-R-M-N", "U-H-Q-B-Y", "X-P-D-V-W", "E-K-F-R-C", "T-E-B-N-L", "I-S-K-Q-H", "Y-Z-V-Q-L", "W-L-U-P-I", "Y-P-F-H-O", "M-C-Y-T-I", "Q-A-V-Z-O", "I-H-U-K-F", "Q-Z-C-W-Y", "U-M-Q-C-R", "U-H-S-M-W", "R-C-M-Y-G", "R-C-J-A-V", "X-B-Z-C-Y", "P-C-O-J-I", "E-C-O-W-R", "E-T-Y-X-F", "Q-M-C-K-S", "H-I-O-J-K", "R-P-N-J-V", "H-Y-P-N-V", "R-J-I-L-S", "N-K-T-Z-O", "H-B-K-A-P", "P-G-J-A-B", "L-S-O-I-Y", "L-U-R-Z-D", "F-I-K-Q-B", "Q-K-X-B-M", "A-C-L-B-I", "J-E-O-D-K", "Y-W-V-G-D", "S-N-T-H-Q", "K-M-D-R-H", "B-U-N-A-T", "A-J-F-O-C", "J-L-V-R-D", "X-A-Q-J-R", "I-V-E-W-Z", "Q-G-K-P-X", "O-H-K-J-D", "N-K-I-J-B", "Y-R-N-G-C", "K-L-E-B-S", "O-P-T-D-G", "O-R-E-J-V", "X-V-B-Z-U", "Y-H-I-O-C", "B-O-Y-M-P", "J-Z-R-Q-P", "A-E-K-M-D", "D-X-L-F-U", "J-X-U-A-Z", "Y-R-G-X-Q", "Y-R-P-W-C", "K-Q-C-Y-L", "K-H-L-V-U", "P-A-N-I-C", "M-D-Y-F-A", "H-V-D-F-U", "Z-D-P-U-J", "O-E-I-Z-Q", "L-Q-A-O-I", "L-V-C-K-N", "E-Z-R-Q-K", "N-T-V-U-D", "U-B-O-H-Z", "F-X-D-H-U", "T-W-R-O-G", "G-A-O-Z-L", "Z-C-B-G-P", "O-I-B-S-N", "Z-R-I-O-Y", "K-B-L-C-Z", "Z-B-T-F-D", "B-H-P-J-C", "A-T-L-N-M", "O-P-W-M-S", "Q-I-L-G-J", "O-D-Q-J-M", "P-B-F-Z-V", "Q-H-J-I-C", "K-V-W-Z-D", "Y-A-R-Z-Q", "M-U-H-Q-N", "O-D-K-J-Z", "I-C-Z-J-H", "E-K-R-G-M", "D-B-G-Y-K", "U-Q-B-C-L", "Y-Q-M-W-L", "A-Z-J-B-S", "C-W-K-O-X", "G-E-V-B-R", "L-N-Q-T-H", "H-E-X-A-J", "U-O-Z-S-B", "P-G-W-K-D", "J-W-L-C-A", "L-C-E-V-Z", "T-K-Y-Q-P", "Z-J-Q-U-V", "V-C-P-K-X", "U-X-A-H-E", "I-J-E-Z-W", "O-M-W-F-S", "N-K-Y-P-M", "C-G-K-L-R", "M-T-V-A-E", "D-V-H-Q-F", "M-N-P-W-L", "G-E-F-T-M", "G-X-D-Q-O", "X-G-W-E-Z", "U-C-B-P-Z", "F-K-Z-W-H", "P-C-D-F-L", "N-V-G-R-T", "I-N-B-J-T", "B-T-A-M-L", "Q-D-N-Y-I", "B-I-K-X-A", "C-U-F-S-K", "W-X-G-P-L", "H-A-P-L-C", "W-T-K-P-F", "G-B-U-T-K", "U-S-Q-Y-Z", "G-C-E-Z-W", "Z-Y-Q-B-M", "D-Y-N-C-E", "E-J-Q-K-I", "P-R-O-B-V", "D-E-A-S-U", "I-A-M-E-L", "M-K-U-F-Y", "L-Q-H-S-O", "Z-F-V-T-P", "V-N-M-R-J", "Q-E-P-K-O", "O-H-J-U-K", "U-Z-Q-O-F", "R-X-K-O-C", "L-R-K-C-G", "V-F-B-S-K", "L-Z-O-H-E", "D-S-Y-V-K", "R-J-V-A-C", "P-L-B-Q-Z", "U-Q-Z-X-Y", "E-P-L-F-T", "Z-F-C-G-B", "W-V-C-A-J", "O-N-C-Y-H", "B-Y-C-D-Z", "E-X-G-H-Z", "Y-H-X-J-I", "O-K-P-J-D", "G-E-K-T-B", "G-A-K-Y-P", "T-V-X-P-H", "W-Y-D-K-V", "Z-W-B-P-Q", "A-J-F-U-P", "V-A-X-E-Z", "F-T-J-E-I", "V-M-L-Z-U", "H-I-B-L-Q", "C-D-V-K-M", "G-Z-F-J-R", "C-T-N-U-H", "W-Y-U-X-M", "T-F-I-W-V", "X-O-P-Y-W", "L-Z-P-B-K", "A-E-T-Q-K", "K-P-C-T-Z", "C-A-H-G-E", "B-W-G-D-V", "A-S-D-B-I", "T-D-G-C-P", "J-B-F-T-K", "V-R-L-U-F", "O-P-Q-X-M", "K-M-T-E-D", "R-B-H-X-G", "L-C-O-Y-B", "R-Z-U-E-I", "K-I-W-O-G", "Z-X-W-F-K", "Y-C-B-S-V", "L-M-R-N-Z", "B-Q-M-Z-K", "P-S-Z-O-T", "Y-A-Z-N-R", "L-O-A-P-J", "O-Z-Y-M-P", "C-P-S-J-Q", "Y-F-Q-A-R", "U-Z-K-B-X", "Q-R-T-H-X", "N-U-E-Q-A", "X-Z-R-J-C", "U-X-E-A-Z", "Y-M-O-A-V", "N-K-D-W-M", "V-Q-E-F-S", "L-U-A-Z-D", "X-S-R-P-E", "I-B-O-Z-L", "J-G-K-H-Z", "T-P-O-Z-S", "L-I-R-G-N", "Y-C-O-Z-A", "H-B-D-Q-R", "C-L-J-E-T", "K-Z-N-U-X", "P-F-S-Z-H", "S-N-D-R-T", "V-R-S-T-G", "N-S-F-U-Y", "D-P-L-C-A", "B-X-V-M-S", "B-M-Z-E-Q", "K-Y-S-P-B", "K-T-V-F-G", "U-Z-B-W-E", "J-X-O-C-Y", "X-C-Q-P-D", "X-L-Y-J-Z", "Y-F-O-N-H", "M-J-B-Z-X", "G-X-R-A-D", "E-I-L-O-A", "B-R-C-I-K", "K-W-F-N-P", "C-Y-R-A-H", "J-H-D-N-C", "P-Y-C-I-D", "P-D-C-U-E", "H-A-I-E-P", "B-N-E-H-S", "C-Y-P-E-F", "K-F-X-B-S", "M-Q-D-I-Z", "V-S-P-K-O", "L-W-M-T-I", "K-N-B-V-D", "A-Z-N-X-G", "U-T-D-A-Y", "Q-Z-D-F-E", "J-Q-I-Y-C", "G-L-E-O-R", "V-J-Z-K-F", "O-W-F-X-H", "H-R-P-X-L", "B-F-X-G-P", "I-C-A-M-R", "P-N-W-Z-L", "R-G-O-H-D", "G-M-B-W-K", "G-D-E-H-P", "Z-K-I-P-N", "K-V-C-E-W", "O-K-N-T-R", "K-B-N-X-M", "E-V-A-X-P", "Q-J-I-Y-C", "I-T-U-J-D", "W-U-T-O-Q", "O-E-H-J-A", "D-K-P-A-I", "D-J-A-F-X", "T-X-D-V-A", "B-W-T-H-V", "H-G-Q-E-U", "B-U-F-Z-Y", "O-S-R-T-D", "F-W-S-I-G", "Z-O-X-C-S", "H-V-K-N-E", "Z-M-L-E-O", "Q-V-T-M-P", "T-N-D-C-S", "L-F-Q-R-Y", "U-F-S-I-O", "S-R-M-B-K", "D-P-W-U-A", "Y-W-A-T-E", "I-W-A-F-J", "V-J-G-A-Q", "Z-S-C-Y-L", "T-C-N-O-S", "J-R-Y-X-Q", "A-F-P-N-J", "R-Y-N-H-M", "S-W-Q-B-V", "N-Q-U-G-K", "T-A-Y-R-Z", "I-L-O-U-V", "W-B-L-F-Q", "N-T-E-W-G", "L-S-I-W-X", "P-A-Z-J-U", "Z-N-F-X-G", "P-B-O-E-N", "D-U-T-C-K", "G-Y-I-K-S", "F-H-W-X-P", "O-B-T-E-Z", "N-K-T-S-X", "E-S-T-N-V", "E-A-J-Q-F", "P-Q-D-X-B", "R-F-J-E-Q", "X-Z-S-M-V", "E-Z-Q-D-X", "M-O-T-U-D", "O-I-X-Z-H", "H-T-V-I-M", "U-E-A-D-W", "Z-D-F-A-X", "E-D-V-P-N", "K-J-R-M-H", "V-A-K-U-D", "S-B-L-A-N", "Q-C-Z-D-P", "U-Z-A-W-C", "V-D-M-G-B", "Z-H-A-I-X", "W-F-H-R-X", "G-E-V-F-K", "H-A-V-L-E", "K-P-E-X-Y", "T-O-Y-R-P", "H-T-F-U-K", "R-V-N-C-X", "K-H-Z-R-A", "Q-P-J-G-N", "T-F-L-K-E", "E-T-O-D-G", "J-K-E-S-N", "K-Q-D-C-B", "Q-R-A-F-D", "J-W-A-Q-I", "N-T-F-G-D", "E-R-I-D-N", "M-G-P-Q-V", "D-P-R-G-N", "N-C-F-T-A", "D-F-Q-X-O", "U-D-K-Q-X", "N-G-M-Q-A", "I-X-F-D-V", "R-T-C-K-I", "S-G-E-R-O", "R-B-J-G-Z", "J-X-G-K-R", "U-K-O-L-C", "X-Q-U-S-B", "B-A-M-V-N", "N-A-V-G-U", "I-Y-U-Z-C", "Y-R-I-M-V", "Y-I-R-P-N", "E-C-Q-A-P", "P-Z-B-O-A", "D-A-R-F-Y", "Q-E-T-L-N", "Z-D-F-Q-C", "X-T-Q-W-D", "D-P-Y-U-J", "S-J-Q-X-V", "E-O-P-W-N", "W-Q-B-O-Y", "Y-Z-W-Q-F", "H-Q-C-X-O", "S-B-W-V-L", "G-R-Y-V-J", "W-F-G-K-S", "F-N-P-D-S", "L-C-Q-T-Z", "V-S-Y-O-B", "L-F-X-U-Q", "G-D-R-M-E", "P-Q-R-Y-G", "N-E-H-L-P", "Z-R-F-M-U", "I-X-C-F-O", "L-E-X-K-O", "G-V-Y-X-F", "O-P-V-A-Z", "P-K-S-L-Y", "Q-R-K-M-D", "A-N-B-J-R", "C-S-G-R-M", "H-C-E-O-D", "E-I-N-F-O", "F-S-P-O-B", "T-S-A-X-B", "R-K-Y-L-M", "F-G-O-C-T", "C-F-H-Y-Z", "M-P-Z-A-V", "N-U-X-B-T", "Q-A-V-N-P", "D-R-V-C-K", "U-P-X-M-F", "S-P-I-T-Z", "O-Z-C-U-Y", "B-G-X-A-D", "Y-U-L-A-W", "O-J-Q-W-G", "V-B-I-F-D", "V-J-Q-K-A", "H-X-W-S-A", "E-D-F-T-I", "H-Q-V-R-L", "M-U-Z-K-Q", "V-Y-U-Q-X", "H-M-I-U-O", "N-Q-P-U-R", "Y-F-I-V-D", "F-A-L-W-M", "G-Q-M-L-B", "X-B-L-P-W", "I-A-R-Q-F", "U-E-J-T-V", "O-B-Q-U-S", "B-P-U-S-M", "N-Z-D-O-V", "A-X-S-T-V", "O-Y-C-F-K", "D-S-I-M-Y", "B-Y-N-G-I", "G-P-U-M-F", "V-B-G-N-W", "C-K-I-S-Y", "U-B-I-R-V", "L-R-M-C-Z", "T-Z-D-U-M", "R-M-F-T-O", "S-V-D-F-Y", "M-F-R-C-J", "G-P-O-X-W", "M-R-X-H-Z", "N-D-J-R-Y", "B-J-W-U-L", "T-E-U-D-J", "M-V-J-R-B", "G-X-Z-U-Y", "L-C-P-M-D", "H-Z-W-E-T", "O-H-Y-C-K", "M-B-Q-N-E", "B-D-E-U-V", "V-P-F-Y-R", "U-M-C-J-L", "P-X-L-Z-A", "D-E-F-X-H", "P-I-Z-C-V", "V-N-O-B-G", "M-T-U-D-P", "I-A-E-O-Q", "V-R-S-L-A", "S-I-Q-N-T", "A-W-T-O-U", "O-T-V-A-Q", "D-R-O-Z-F", "Z-M-F-Q-I", "B-H-E-L-M", "Z-S-D-L-G", "E-B-N-A-Q", "K-Z-O-C-D", "Y-J-M-C-Z", "K-O-F-A-G", "J-U-L-Y-G", "A-F-C-V-D", "S-B-H-T-U", "J-F-V-T-Q", "H-L-Z-K-M", "F-Q-O-K-S", "P-S-U-V-A", "Y-L-E-D-F", "Z-L-U-K-V", "J-N-M-P-F", "G-M-F-Y-Z", "W-P-N-D-U", "J-V-Q-H-P", "Q-B-H-O-R", "U-R-J-K-W", "J-Q-S-O-F", "S-J-N-T-O", "A-S-B-Q-W", "W-X-Q-H-I", "K-R-V-S-X", "Q-S-O-R-W", "R-Q-N-G-Z", "Z-P-A-S-R", "F-N-X-K-D", "S-I-X-A-W", "K-E-J-H-G", "D-N-Z-W-U", "X-Y-T-J-O", "X-I-J-Z-S", "R-V-L-W-C", "X-T-A-M-I", "A-W-B-K-M", "E-C-Q-T-X", "A-B-V-W-X", "K-O-B-W-Q", "V-Y-F-P-C", "H-O-E-X-D", "I-Y-A-H-Q", "J-U-N-D-F", "O-V-N-P-Y", "X-R-O-C-J", "W-I-D-R-M", "Q-C-O-H-N", "L-Z-Y-W-T", "L-H-J-U-I", "X-M-E-R-U", "R-J-K-Z-E", "Q-A-I-P-R", "A-D-L-Q-M", "F-E-M-B-Z", "S-J-N-H-P", "I-Y-C-L-F", "W-Y-O-N-I", "S-E-N-B-H", "K-Z-E-U-V", "D-N-Y-Z-O", "Q-S-B-J-P", "J-N-V-T-B", "U-H-Q-L-S", "U-C-N-Q-L", "A-L-R-Q-F", "J-Y-K-P-L", "W-S-O-M-U", "L-P-S-U-T", "U-V-P-F-M", "C-J-W-Q-N", "A-V-C-P-L", "P-V-N-Y-E", "A-S-M-K-G", "T-U-I-W-H", "E-K-G-P-M", "Z-F-O-H-E", "C-G-I-X-R", "R-F-D-O-X", "C-R-F-M-Q", "H-F-M-N-W", "M-Y-F-I-X", "A-B-U-X-S", "C-F-G-R-D", "M-A-G-L-C", "W-L-Y-F-U", "U-M-C-R-H", "A-C-P-Y-K", "A-E-V-C-P", "M-C-A-J-B", "M-O-Z-G-A", "T-R-E-Y-M", "F-K-E-S-C", "F-Q-B-J-T", "R-Y-D-V-B", "J-M-C-R-W", "L-S-E-Z-R", "J-A-E-P-N", "Y-S-K-B-O", "S-Y-N-G-C", "B-Q-I-W-H", "W-T-U-J-Y", "O-J-W-G-C", "Q-B-G-I-F", "W-E-B-T-N", "M-A-U-R-G", "Q-R-O-X-P", "H-T-Y-N-P", "L-Q-A-G-X", "U-S-D-R-C", "I-J-F-R-Q", "K-T-G-C-E", "M-K-P-C-T", "W-Z-L-N-K", "B-O-G-U-P", "C-F-V-D-P", "G-A-P-R-X", "E-I-Y-K-F", "W-H-G-P-S", "G-J-W-M-B", "R-Y-B-F-L", "T-F-Y-Q-I", "R-L-O-C-F", "I-X-S-G-O", "F-R-M-A-X", "H-B-L-Z-A", "A-L-M-N-C", "F-D-Q-X-L", "Y-K-S-Q-D", "I-Z-B-O-D", "R-M-A-S-C", "J-S-U-H-T", "Q-B-A-P-Z", "I-D-X-Q-O", "F-H-S-W-Z", "P-V-K-R-G", "L-B-A-H-I", "I-P-T-O-Q", "N-F-K-Y-V", "T-U-M-A-G", "S-N-X-W-F", "I-V-Y-O-C", "M-T-Z-G-O", "R-V-B-J-I", "O-X-N-R-V", "W-N-M-F-J", "I-L-R-D-B", "E-G-F-V-Z", "A-P-S-L-Y", "U-A-X-G-O", "Y-D-B-O-M", "T-Q-K-M-D", "L-K-D-X-G", "V-Z-J-A-C", "T-N-I-W-H", "Z-C-S-R-E", "X-P-C-S-J", "O-A-F-C-R", "P-Z-X-W-E", "N-L-Q-W-Z", "M-J-W-D-T", "R-S-Q-G-O", "Z-C-Q-Y-S", "S-J-Y-R-V", "B-H-M-G-A", "Y-K-D-O-N", "Y-F-G-W-J", "I-G-T-C-V", "U-D-T-B-F", "N-Q-X-R-V", "F-O-N-L-V", "R-C-X-F-I", "C-G-H-U-X", "T-I-J-W-G", "U-B-G-O-Y", "G-C-H-Q-V", "S-U-D-A-H", "F-B-S-I-H", "X-R-S-O-B", "M-H-C-E-K", "Y-J-A-T-N", "Z-B-I-D-X", "R-I-U-E-B", "C-S-O-V-X", "S-Y-G-E-M", "N-O-D-H-Z", "U-D-V-T-S", "U-I-H-Q-M", "L-E-S-U-Y", "L-Q-Y-W-P", "J-K-U-C-V", "A-Z-R-S-N", "Z-F-A-Y-L", "C-L-G-R-I", "A-N-Q-C-Z", "A-X-P-R-Z", "W-N-I-M-X", "O-I-L-B-N", "P-U-Q-S-Y", "N-M-W-L-D", "D-V-X-C-R", "P-G-J-I-X", "Y-G-C-T-H", "R-S-V-N-X", "Y-H-O-Q-D", "Q-N-Y-Z-H", "D-Q-J-B-H", "N-X-O-Z-T", "R-Q-B-X-J", "V-S-U-E-K", "H-X-U-C-K", "W-X-E-I-P", "Z-H-J-N-L", "A-R-N-V-H", "R-W-L-O-N", "Q-Z-K-E-G", "M-X-L-W-F", "Z-R-L-K-J", "S-E-G-Y-N", "R-H-K-V-L", "A-V-Q-R-I", "Z-L-J-B-O", "W-S-R-C-V", "S-L-Q-M-R", "T-P-Y-A-K", "W-R-H-U-O", "Z-B-Q-P-J", "I-H-Q-Y-U", "W-K-U-I-S", "V-Y-N-M-W", "O-C-P-J-V", "M-S-Z-W-G", "T-M-E-X-F", "E-W-A-P-N", "Z-S-N-B-K", "G-D-X-R-V", "O-D-J-X-I", "F-V-G-U-L", "R-U-I-Z-M", "G-E-O-L-I", "K-T-B-F-X", "I-M-R-C-T", "B-Q-M-F-C", "E-J-F-W-B", "W-P-N-H-G", "E-F-G-O-J", "P-C-O-S-H", "C-L-A-N-W", "J-N-B-F-K", "S-X-A-E-Z", "G-B-F-T-P", "K-C-G-D-E", "T-I-O-G-U", "B-R-W-Q-Z", "L-C-R-G-N", "F-A-B-V-G", "R-E-Q-W-L", "L-Z-J-H-S", "E-R-L-D-N", "N-D-A-O-F", "V-O-A-C-L", "A-J-S-M-W", "K-B-V-T-O", "J-D-P-K-L", "J-Y-T-D-X", "Z-A-E-K-B", "L-D-R-Z-Q", "O-X-L-B-G", "B-P-V-O-K", "Y-J-D-T-E", "C-F-R-N-W", "U-Y-M-Q-E", "Y-O-V-X-H", "Z-K-D-V-Q", "A-Q-B-P-S", "W-S-O-Z-B", "W-D-U-F-S", "A-H-P-R-E", "W-N-V-J-K", "D-F-U-Y-S", "O-A-G-P-V", "V-J-R-E-P", "I-P-R-E-U", "F-T-C-O-G", "K-F-U-M-H", "V-N-F-X-U", "E-R-C-H-B", "D-U-C-V-X", "Q-Z-U-C-A", "B-G-I-O-T", "L-C-S-Z-M", "S-Y-A-W-P", "K-M-D-G-N", "O-I-D-R-Q", "D-P-J-Q-C", "T-N-I-B-L", "I-G-M-D-A", "O-D-C-L-Y", "B-D-Q-H-F", "H-M-W-P-X", "Z-Q-U-T-L", "K-F-I-O-R", "U-W-L-M-V", "M-A-N-D-Y", "M-E-X-I-K", "Y-S-F-L-V", "Q-Y-O-L-E", "T-K-G-A-O", "I-S-J-V-Q", "J-K-P-B-G", "D-P-F-I-T", "I-Q-B-J-M", "E-Q-H-V-W", "J-D-C-E-Q", "Q-U-V-D-J", "G-D-S-Z-T", "U-M-X-Z-C", "Q-R-Z-G-W", "R-C-L-Q-J", "E-I-O-V-W", "S-E-R-D-Q", "V-J-E-D-T", "K-A-D-N-T", "P-C-R-Z-X", "P-B-I-T-E", "H-W-Q-L-C", "C-J-K-E-G", "R-Y-D-P-Q", "R-X-U-L-K", "R-T-Q-G-Y", "X-H-J-T-D", "U-E-F-I-A", "L-K-O-C-M", "K-Y-H-M-J", "T-M-K-L-Y", "B-D-U-L-P", "D-G-N-F-H", "H-K-O-J-B", "L-A-C-X-D", "L-D-P-Q-J", "A-F-T-U-R", "M-W-H-Q-V", "T-F-J-S-G", "C-T-W-E-N", "K-U-F-A-D", "S-U-Q-T-P", "K-V-Y-B-T", "E-V-Y-O-S", "D-J-E-I-C", "Y-P-M-X-O", "Q-X-W-K-S", "G-W-O-B-C", "A-V-G-B-D", "I-L-V-D-K", "R-V-G-B-W", "Z-S-O-M-G", "B-A-C-X-W", "S-P-X-G-Q", "A-I-S-C-B", "Q-N-S-J-P", "E-T-W-J-B", "R-X-Y-T-E", "X-G-Z-V-S", "C-M-B-A-N", "N-L-W-B-I", "R-H-E-F-X", "E-U-I-Z-C", "N-F-E-Z-V", "T-S-O-J-F", "R-U-B-S-K", "R-A-S-K-G", "O-F-K-R-V", "N-X-J-A-M", "E-A-Z-T-X", "H-G-S-J-M", "H-E-A-J-U", "N-Z-M-Q-O", "B-M-R-E-S", "X-Y-Z-E-U", "W-I-H-R-J", "N-W-M-Y-Q", "I-N-O-T-P", "C-N-T-H-W", "O-C-M-H-L", "V-U-X-N-Q", "M-B-W-A-N", "H-Q-Z-B-L", "H-E-C-O-Y", "A-D-W-F-Y", "C-G-L-M-O", "A-D-U-O-C", "U-O-V-R-H", "Z-B-Y-G-L", "L-X-C-E-P", "L-P-K-M-R", "A-F-T-M-B", "E-Z-B-K-U", "D-F-E-A-Y", "J-A-R-M-O", "T-N-I-U-X", "F-Q-B-D-J", "N-J-M-R-Z", "Z-G-C-A-K", "U-G-V-X-H", "D-I-R-Z-W", "E-O-M-R-T", "L-G-S-F-M", "A-J-C-K-N", "Z-N-D-L-H", "X-Y-S-U-V", "B-Q-I-L-Y", "J-C-D-Z-E", "S-U-K-T-G", "P-W-T-X-U", "D-I-H-B-A", "Y-W-L-U-E", "F-O-A-S-V", "B-H-R-Z-J", "B-V-C-Y-L", "Z-C-J-F-H", "Z-Y-V-A-T", "R-F-D-E-Y", "J-O-L-A-M", "L-B-Z-F-P", "X-P-S-D-R", "O-E-B-R-S", "J-P-B-Q-X", "Q-E-Z-I-U", "Q-N-G-U-P", "W-O-L-I-T", "D-O-B-K-Z", "Z-E-A-V-J", "K-D-E-W-A", "Y-B-M-C-Z", "B-N-Y-J-O", "L-U-F-E-J", "B-V-X-O-F", "V-X-S-A-P", "L-B-Y-D-E", "E-U-D-V-T", "J-P-T-D-A", "Q-C-R-X-V", "I-D-L-A-O", "R-G-Q-V-K", "H-S-W-R-F", "C-E-F-Y-O", "S-P-B-Q-M", "B-J-Z-F-Q", "U-E-C-P-Z", "D-H-O-E-B", "G-J-L-R-S", "L-C-V-T-S", "R-V-W-K-I", "A-M-X-W-P", "W-B-K-A-C", "P-O-I-L-S", "A-K-T-B-Y", "K-B-X-R-T", "S-P-T-Q-W", "D-Z-U-W-B", "L-S-E-H-Q", "G-F-W-U-N", "J-T-E-Z-F", "V-X-N-T-B", "T-K-D-N-R", "L-K-Q-I-T", "N-E-X-D-Q", "Y-B-S-X-A", "S-D-W-J-Q", "U-H-J-W-I", "X-Z-A-J-B", "E-S-D-O-H", "G-O-Y-N-Q", "U-F-I-B-A", "M-H-W-C-O", "F-Z-C-B-M", "S-C-A-O-V", "G-R-H-Q-U", "V-N-J-Q-P", "V-P-Y-M-U", "R-J-O-Q-V", "G-C-U-E-I", "C-A-T-L-R", "O-N-S-G-H", "P-Z-K-U-V", "R-Z-H-D-N", "G-E-X-C-L", "Q-K-E-P-S", "N-K-I-H-A", "A-N-T-R-M", "T-R-G-E-F", "I-B-T-S-D", "G-R-J-L-W", "I-P-Y-K-L", "S-J-M-F-W", "U-H-D-P-T", "A-D-H-M-B", "S-T-Z-P-U", "N-I-G-R-S", "U-V-M-Q-S", "E-F-S-J-R", "F-H-O-S-Z", "O-G-R-E-Z", "B-M-A-N-T", "Z-H-D-L-Q", "Q-R-N-J-K", "C-X-H-Y-L", "I-X-W-G-E", "T-B-M-Q-S", "A-H-Z-V-B", "R-Z-M-X-G", "M-U-K-H-E", "G-B-L-A-W", "R-X-T-S-F", "D-W-Q-M-S", "P-B-F-Q-K", "R-H-I-J-V", "I-F-J-V-C", "U-T-K-R-F", "P-T-L-U-R", "I-L-P-N-F", "D-H-K-X-J", "A-Q-B-M-S", "B-V-L-Q-F", "C-H-K-V-A", "T-Y-C-P-U", "B-U-P-Q-H", "M-H-Q-E-I", "E-Z-S-U-K", "H-W-Q-R-M", "C-I-B-J-K", "E-I-O-S-H", "Y-I-E-X-L", "S-Q-O-L-N", "J-H-L-T-G", "H-M-Q-J-I", "V-I-Z-J-Q", "B-G-R-J-P", "Z-I-W-B-O", "R-V-B-G-N", "B-I-N-J-R", "F-T-W-M-L", "F-S-K-I-J", "S-N-V-G-Z", "X-I-G-L-H", "K-B-X-V-A", "F-M-A-H-N", "Y-Z-I-W-A", "P-M-R-C-Z", "N-U-B-R-A", "wordZ2", "Var1", "Var2", "Var3", "Var4", "a", "b", "c", "e", "f", "h", "i", "Var5", "Var6", "Var7", "col 1", "col 2", "g", "x" ), uniqueCount = 2114L) expect_equal(expected_shared_strings, wb$sharedStrings) }) test_that("Loading multiple pivot tables: loadPivotTables.xlsx works",{ ## loadPivotTables.xlsx is a file with 3 pivot tables and 2 of them have the same reference data (pivotCacheDefinition) fl <- system.file("extdata", "loadPivotTables.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) # Check that wb is correctly loaded sheet_names <- c("iris", "iris_pivot", "penguins", "penguins_pivot1", "penguins_pivot2") expect_equal(wb$sheet_names, sheet_names) # Check number of 'pivotTables' expect_equal(length(wb$pivotTables), 3) # Check number of 'pivotCacheDefinition' expect_equal(length(wb$pivotDefinitions), 2) }) test_that("Load and saving a file with Threaded Comments works", { ## loadThreadComment.xlsx is a simple xlsx file that uses Threaded Comment. fl <- system.file("extdata", "loadThreadComment.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) # Check that wb can be saved without error expect_silent(saveWorkbook(wb, file = temp_xlsx())) }) test_that("Read and save file with inlineStr", { ## loadThreadComment.xlsx is a simple xlsx file that uses Threaded Comment. fl <- system.file("extdata", "inlineStr.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) wb_df <- readWorkbook(wb) df <- data.frame( this = c("is an xlsx file", "written with writexl::write_xlsx"), it = c("cannot be read", "with open.xlsx::read.xlsx"), stringsAsFactors = FALSE) # compare file imported with inlineStr expect_true(all.equal(df, wb_df, compare.attributes = FALSE)) df_read_xlsx <- read.xlsx(fl) df_readWorkbook <- readWorkbook(fl) expect_true(all.equal(df, df_read_xlsx, compare.attributes = FALSE)) expect_true(all.equal(df, df_readWorkbook, compare.attributes = FALSE)) tmp_xlsx <- temp_xlsx() # Check that wb can be saved without error and reimported expect_silent(saveWorkbook(wb, file = tmp_xlsx)) wb_df_re <- readWorkbook(loadWorkbook(tmp_xlsx)) expect_true(all.equal(wb_df, wb_df_re, compare.attributes = FALSE)) }) # tests for getChildlessNode returns the content of every node, single node or not. the name has only historical meaning test_that("read nodes", { # read single node test <- "" that <- openxlsx:::getChildlessNode(test, "xf") expect_equal(test, that) # real life example and ... mixed cellXfs <- "" that <- openxlsx:::getChildlessNode(cellXfs, "xf") test <- c("", "", "", "", "", "", "", "", "" ) expect_equal(test, that) # test test <- "" that <- openxlsx:::getChildlessNode(test, "xf") expect_equal(character(0), that) # test test <- "" that <- openxlsx:::getChildlessNode(test, "b") test <- c( "", "" ) expect_equal(test, that) # test ... test <- "a" that <- openxlsx:::getChildlessNode(test, "b") test <- c("a", "") expect_equal(test, that) # test test <- "" that <- openxlsx:::getChildlessNode(test, "xf") test <- "" expect_equal(test, that) }) test_that("sheet visibility", { # example is rather slow (lots of hidden cols) fl <- system.file("extdata", "ColorTabs3.xlsx", package = "openxlsx") tmp_dir <- temp_xlsx() exp_sheets <- c("Nums", "Chars", "hidden") exp_vis <- c("visible", "visible", "hidden") # after load wb <- loadWorkbook(fl) wb_sheets <- openxlsx::sheets(wb) wb_vis <- openxlsx::sheetVisibility(wb) # save saveWorkbook(wb, tmp_dir) # re-import wb2 <- loadWorkbook(tmp_dir) wb2_sheets <- openxlsx::sheets(wb) wb2_vis <- openxlsx::sheetVisibility(wb) expect_equal(exp_sheets, wb_sheets) expect_equal(exp_vis, wb_vis) expect_equal(exp_sheets, wb2_sheets) expect_equal(exp_vis, wb2_vis) }) openxlsx/tests/testthat/test-write_xlsx_vector_args.R0000644000176200001440000000715614155600364023057 0ustar liggesusers context("write.xlsx vector arguments") test_that("Writing then reading returns identical data.frame 1", { tmp_file <- file.path(tempdir(), "xlsx_vector_args.xlsx") df1 <- data.frame(1:2) df2 <- data.frame(1:3) x <- list(df1, df2) write.xlsx( file = tmp_file, x = x, gridLines = c(F, T), sheetName = c("a", "b"), zoom = c(50, 90), tabColour = c("red", "blue") ) wb <- loadWorkbook(tmp_file) expect_equal(getSheetNames(tmp_file), expected = c("a", "b")) expect_equal(names(wb), expected = c("a", "b")) expect_true(grepl('rgb="FFFF0000"', wb$worksheets[[1]]$sheetPr)) expect_true(grepl('rgb="FF0000FF"', wb$worksheets[[2]]$sheetPr)) expect_true(grepl('zoomScale="50"', wb$worksheets[[1]]$sheetViews)) expect_true(grepl('zoomScale="90"', wb$worksheets[[2]]$sheetViews)) expect_true(grepl('showGridLines="0"', wb$worksheets[[1]]$sheetViews)) expect_true(grepl('showGridLines="1"', wb$worksheets[[2]]$sheetViews)) expect_equal(read.xlsx(tmp_file, sheet = 1), df1) expect_equal(read.xlsx(tmp_file, sheet = 2), df2) unlink(tmp_file, recursive = TRUE, force = TRUE) }) test_that("write.xlsx() passes withFilter and colWidths [151]", { df <- data.frame(x = 1, b = 2) x <- buildWorkbook(df) y <- buildWorkbook(df, withFilter = TRUE, colWidths = 15) expect_equal(x$worksheets[[1]]$autoFilter, character()) expect_equal(y$worksheets[[1]]$autoFilter, "") expect_equal(x$colWidths, list(list())) expect_equal( y$colWidths[[1]], structure(c(`1` = "15", `2` = "15"), hidden = c("0", "0")) ) }) test_that("write.xlsx() correctly passes default asTable and withFilters", { df <- data.frame(x = 1, b = 2) # asTable = TRUE >> writeDataTable >> withFilter = TRUE # asTable = FALSE >> writeData >> withFilter = FALSE x <- buildWorkbook(df, asTable = FALSE) y <- buildWorkbook(df, asTable = TRUE) # Save the workbook tf <- temp_xlsx() saveWorkbook(y, tf) y2 <- loadWorkbook(tf) expect_identical(x$worksheets[[1]]$autoFilter, character()) # not autoFilter for tables -- not named in buildWorkbook expect_equal( y$worksheets[[1]]$tableParts, structure("", tableName = "Table3") ) expect_equal( y2$worksheets[[1]]$tableParts, structure("", tableName = c(`A1:B2` = "Table3")) ) file.remove(tf) }) test_that("write.xlsx() correctly handles colWidths", { x <- data.frame(a = 1, b = 2, c = 3) zero3 <- rep("0", 3) # No warning when passing "auto" expect_warning(buildWorkbook(rep_len(list(x), 3), colWidths = "auto"), NA) # single value is repeated for all columns wb <- buildWorkbook(rep_len(list(x), 3), colWidths = 13) exp <- rep_len(list(structure(c(`1` = "13", `2` = "13", `3` = "13"), hidden = zero3)), 3) expect_equal(wb$colWidths, exp) # sets are repeated wb <- buildWorkbook(rep_len(list(x), 3), colWidths = list(c(10, 20, 30))) exp <- rep_len(list(structure(c(`1` = "10", `2` = "20", `3` = "30"), hidden = zero3)), 3) expect_equal(wb$colWidths, exp) # 3 distinct sets wb <- buildWorkbook( rep_len(list(x), 3), colWidths = list( c(10, 20, 30), c(100, 200, 300), c(1, 2, 3) )) expect_equal( wb$colWidths, list( structure(c(`1` = "10", `2` = "20", `3` = "30"), hidden = zero3), structure(c(`1` = "100", `2` = "200", `3` = "300"), hidden = zero3), structure(c(`1` = "1", `2` = "2", `3` = "3"), hidden = zero3) ) ) }) openxlsx/tests/testthat/test-Worksheet_naming.R0000644000176200001440000000204414155600364021544 0ustar liggesusers context("Worksheet naming") test_that("Worksheet names", { ### test for names without special character wb <- createWorkbook() sheetname <- "test" addWorksheet(wb, sheetname) expect_equal(sheetname,names(wb)) ### test for names with & wb <- createWorkbook() sheetname <- "S&P 500" addWorksheet(wb, sheetname) expect_equal(sheetname,names(wb)) expect_equal("S&P 500",wb$sheet_names) ### test for names with < wb <- createWorkbook() sheetname <- "<24 h" addWorksheet(wb, sheetname) expect_equal(sheetname,names(wb)) expect_equal("<24 h",wb$sheet_names) ### test for names with > wb <- createWorkbook() sheetname <- ">24 h" addWorksheet(wb, sheetname) expect_equal(sheetname,names(wb)) expect_equal(">24 h",wb$sheet_names) ### test for names with " wb <- createWorkbook() sheetname <- 'test "A"' addWorksheet(wb, sheetname) expect_equal(sheetname,names(wb)) expect_equal("test "A"",wb$sheet_names) }) openxlsx/tests/testthat/test-encoding.R0000644000176200001440000000503714155600364020033 0ustar liggesusers context("Encoding Tests") test_that("Write read encoding equality", { tempFile <- temp_xlsx() wb <- createWorkbook() for (i in 1:4) { addWorksheet(wb, sprintf("Sheet %s", i)) } df <- data.frame("X" = c("测试", "一下"), stringsAsFactors = FALSE) writeDataTable(wb, sheet = 1, x = df) saveWorkbook(wb, tempFile, overwrite = TRUE) x <- read.xlsx(tempFile) expect_equal(x, df) x <- read.xlsx(wb) expect_equal(x, df) ## reload wb <- loadWorkbook(tempFile) x <- read.xlsx(wb) expect_equal(x, df) saveWorkbook(wb, tempFile, overwrite = TRUE) x <- read.xlsx(tempFile) expect_equal(x, df) unlink(tempFile, recursive = TRUE, force = TRUE) rm(wb) }) test_that("Support non-ASCII strings not in UTF-8 encodings", { non_ascii <- c("\u4f60\u597d", "\u4e2d\u6587", "\u6c49\u5b57", "\u5de5\u4f5c\u7c3f1", "\u6d4b\u8bd5\u540d\u5b571", "\u6d4b2", "\u5de52", "\u5de5\u4f5c3") # Ideally, we should test agains native encodings. However, the testing machine's # locale encoding may not be able to represent the non-ascii letters, when # it's the case, we use the UTF-8 encoding as it is. if (identical( enc2utf8(enc2native(non_ascii)), non_ascii )) { non_ascii <- enc2native(non_ascii) } non_ascii_df <- data.frame( X = non_ascii, Y = seq_along(non_ascii), stringsAsFactors = FALSE ) colnames(non_ascii_df) <- non_ascii[3:4] file <- temp_xlsx() wb <- createWorkbook(creator = non_ascii[1]) ws <- addWorksheet(wb, non_ascii[2]) writeDataTable(wb, ws, non_ascii_df, tableName = non_ascii[3]) writeData(wb, ws, non_ascii_df, xy = list("D", 1), name = non_ascii[4]) writeComment(wb, ws, 1, 1, comment = createComment(non_ascii[5], non_ascii[6])) writeFormula(wb, ws, x = sprintf('"%s"&"%s"', non_ascii[1], non_ascii[2]), xy = list("G", 1)) createNamedRegion(wb, ws, 7, 1, name = non_ascii[7]) saveWorkbook(wb, file) wb2 <- loadWorkbook(file) expect_equal( getCreators(wb2), non_ascii[1] ) expect_equal( getSheetNames(file), non_ascii[2] ) expect_equivalent( getTables(wb2, ws), non_ascii[3] ) expect_equivalent( getNamedRegions(wb2), non_ascii[c(4, 7)] ) expect_equal( wb2$comments[[1]][[1]][c("comment", "author")], setNames(as.list(non_ascii[5:6]), c("comment", "author")) ) expect_equal( read.xlsx(file, ws, cols = 1:2), non_ascii_df ) expect_equal( read.xlsx(file, ws, cols = 4:5), non_ascii_df ) }) openxlsx/tests/testthat/test-Workbook_properties.R0000644000176200001440000000343414155600364022315 0ustar liggesusers context("Workbook properties") test_that("Workbook properties", { ## check creator wb <- createWorkbook(creator = "Alex", title = "title here", subject = "this & that", category = "some category") expect_true(grepl("Alex", wb$core)) expect_true(grepl("title here", wb$core)) expect_true(grepl("this & that", wb$core)) expect_true(grepl("some category", wb$core)) fl <- tempfile(fileext = ".xlsx") wb <- write.xlsx( x = iris, file = fl, creator = "Alex 2", title = "title here 2", subject = "this & that 2", category = "some category 2" ) expect_true(grepl("Alex 2", wb$core)) expect_true(grepl("title here 2", wb$core)) expect_true(grepl("this & that 2", wb$core)) expect_true(grepl("some category 2", wb$core)) ## maintain on load wb_loaded <- loadWorkbook(fl) expect_equal(object = wb_loaded$core, expected = paste0(wb$core, collapse = "")) wb <- createWorkbook(creator = "Philipp", title = "title here", subject = "this & that", category = "some category") addCreator(wb, "test") expect_true(grepl("Philipp;test", wb$core)) expect_equal(getCreators(wb), c("Philipp", "test")) setLastModifiedBy(wb, "Philipp 2") expect_true(grepl("Philipp 2", wb$core)) }) test_that("Workbook can print with 0 sheets [240]", { compare_text <- "A Workbook object.\n \nWorksheets:\n No worksheets attached\n" printed_text <- capture_output(x <- createWorkbook()$show()) expect_null(x) expect_equal(compare_text, printed_text) }) openxlsx/tests/testthat/test-getCellRefs.R0000644000176200001440000000065014155600364020440 0ustar liggesusers context("Check Cell Ref") test_that("Provide tests for single getCellRefs", { expect_equal(getCellRefs(data.frame(1, 2)), "B1") expect_error(getCellRefs(c(1, 2))) expect_error(getCellRefs(c(1, "a"))) }) test_that("Provide tests for multiple getCellRefs", { expect_equal(getCellRefs(data.frame(1:3, 2:4)), c("B1", "C2", "D3")) expect_error(getCellRefs(c(1:2, c("a", "b")))) }) openxlsx/tests/testthat/test-fontSizeLookupTables.R0000644000176200001440000000026114155600364022365 0ustar liggesuserstest_that("lookup tables dimensions", { expect_equal(dim(openxlsxFontSizeLookupTable), c(29L, 225L)) expect_equal(dim(openxlsxFontSizeLookupTableBold), c(29L, 225L)) }) openxlsx/tests/testthat/test-validate_table_name.R0000644000176200001440000000455514155600364022211 0ustar liggesusers test_that("Validate Table Names", { wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## case expect_equal(wb$validate_table_name("test"), "test") expect_equal(wb$validate_table_name("TEST"), "test") expect_equal(wb$validate_table_name("Test"), "test") ## length expect_error(wb$validate_table_name(paste(sample(LETTERS, size = 300, replace = TRUE), collapse = "")), regexp = "tableName must be less than 255 characters") ## look like cell ref expect_error(wb$validate_table_name("R1C2"), regexp = "tableName cannot be the same as a cell reference, such as R1C1", fixed = TRUE) expect_error(wb$validate_table_name("A1"), regexp = "tableName cannot be the same as a cell reference", fixed = TRUE) expect_error(wb$validate_table_name("R06821C9682"), regexp = "tableName cannot be the same as a cell reference, such as R1C1", fixed = TRUE) expect_error(wb$validate_table_name("ABD918751"), regexp = "tableName cannot be the same as a cell reference", fixed = TRUE) expect_error(wb$validate_table_name("A$100"), regexp = "'$' character cannot exist in a tableName", fixed = TRUE) expect_error(wb$validate_table_name("A12$100"), regexp = "'$' character cannot exist in a tableName", fixed = TRUE) tbl_nm <- "性別" expect_equal(wb$validate_table_name(tbl_nm), tbl_nm) }) test_that("Existing Table Names", { wb <- createWorkbook() addWorksheet(wb, "Sheet 1") ## Existing names - case in-sensitive writeDataTable(wb, sheet = 1, x = head(iris), tableName = "Table1") expect_error(wb$validate_table_name("Table1"), regexp = "Table with name 'table1' already exists", fixed = TRUE) expect_error(writeDataTable(wb, sheet = 1, x = head(iris), tableName = "Table1", startCol = 10), regexp = "Table with name 'table1' already exists", fixed = TRUE) expect_error(wb$validate_table_name("TABLE1"), regexp = "Table with name 'table1' already exists", fixed = TRUE) expect_error(writeDataTable(wb, sheet = 1, x = head(iris), tableName = "TABLE1", startCol = 20), regexp = "Table with name 'table1' already exists", fixed = TRUE) expect_error(wb$validate_table_name("table1"), regexp = "Table with name 'table1' already exists", fixed = TRUE) expect_error(writeDataTable(wb, sheet = 1, x = head(iris), tableName = "table1", startCol = 30), regexp = "Table with name 'table1' already exists", fixed = TRUE) }) openxlsx/tests/testthat/test-read_from_loaded_workbook.R0000644000176200001440000002405514155600364023431 0ustar liggesusers context("Reading from workbook is identical to reading from file readTest.xlsx") test_that("Reading example workbook readTest.xlsx", { curr_wd <- getwd() xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") wb <- loadWorkbook(xlsxFile) ## sheet 1 sheet <- 1 x <- read.xlsx(xlsxFile, sheet) y <- read.xlsx(wb, sheet) expect_equal(dim(x), c(10, 7)) expect_equal(dim(y), c(10, 7)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, detectDates = TRUE) y <- read.xlsx(wb, sheet, detectDates = TRUE) expect_equal(dim(x), c(10, 7)) expect_equal(dim(y), c(10, 7)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(9, 5)) expect_equal(dim(y), c(9, 5)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE) expect_equal(dim(x), c(2, 6)) expect_equal(dim(y), c(2, 6)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(3, 6)) expect_equal(dim(y), c(3, 6)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE) expect_equal(dim(x), c(3, 6)) expect_equal(dim(y), c(3, 6)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) y <- read.xlsx(wb, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) expect_equal(dim(x), c(9, 2)) expect_equal(dim(y), c(9, 2)) expect_equal(x, y) ## sheet 2 sheet <- 2 x <- read.xlsx(xlsxFile, sheet) y <- read.xlsx(wb, sheet) expect_equal(dim(x), c(33, 9)) expect_equal(dim(y), c(33, 9)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(32, 9)) expect_equal(dim(y), c(32, 9)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE) expect_equal(dim(x), c(2, 9)) expect_equal(dim(y), c(2, 9)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(3, 9)) expect_equal(dim(y), c(3, 9)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE) expect_equal(dim(x), c(3, 9)) expect_equal(dim(y), c(3, 9)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) y <- read.xlsx(wb, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) expect_equal(dim(x), c(21, 3)) expect_equal(dim(y), c(21, 3)) expect_equal(x, y) ## sheet 3 sheet <- 3 x <- read.xlsx(xlsxFile, sheet) y <- read.xlsx(wb, sheet) expect_equal(dim(x), c(2083, 5)) expect_equal(dim(y), c(2083, 5)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(2084, 5)) expect_equal(dim(y), c(2084, 5)) expect_equal(x, y) x <- suppressWarnings(read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE)) y <- suppressWarnings(read.xlsx(wb, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE)) expect_equal(dim(x), NULL) expect_equal(dim(y), NULL) expect_equal(x, y) x <- suppressWarnings(read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE)) y <- suppressWarnings(read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE)) expect_equal(dim(x), NULL) expect_equal(dim(y), NULL) expect_equal(x, NULL) expect_equal(y, NULL) expect_equal(x, y) x <- suppressWarnings(read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE)) y <- suppressWarnings(read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE)) expect_equal(dim(x), NULL) expect_equal(dim(y), NULL) expect_equal(x, NULL) expect_equal(y, NULL) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) y <- read.xlsx(wb, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) expect_equal(dim(x), c(2084, 2)) expect_equal(dim(y), c(2084, 2)) expect_equal(x, y) ## sheet 5 sheet <- 5 x <- read.xlsx(xlsxFile, sheet) y <- read.xlsx(wb, sheet) expect_equal(dim(x), c(271, 297)) expect_equal(dim(y), c(271, 297)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, startRow = 3, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(270, 297)) expect_equal(dim(y), c(270, 297)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = TRUE, detectDates = TRUE) expect_equal(dim(x), c(2, 297)) expect_equal(dim(y), c(2, 297)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = TRUE) expect_equal(dim(x), c(3, 297)) expect_equal(dim(y), c(3, 297)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE) y <- read.xlsx(wb, sheet, rows = 2:4, colNames = FALSE, detectDates = FALSE) expect_equal(dim(x), c(3, 297)) expect_equal(dim(y), c(3, 297)) expect_equal(x, y) x <- read.xlsx(xlsxFile, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) y <- read.xlsx(wb, sheet, colNames = FALSE, detectDates = FALSE, cols = 2:4) expect_equal(dim(x), c(272, 3)) expect_equal(dim(y), c(272, 3)) expect_equal(x, y) expect_equal(object = getwd(), curr_wd) }) test_that("Load read - Skip Empty rows/cols", { curr_wd <- getwd() xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") wb <- loadWorkbook(xlsxFile) x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 4L) x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 4L) ############################################################## ## FALSE FALSE FALSE x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## FALSE FALSE TRUE x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ## NA rows expect_true(all(is.na(x[2, ]))) ############################################################## ## FALSE TRUE FALSE x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = FALSE) expect_equal(nrow(x), 5L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## FALSE TRUE TRUE x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = FALSE, skipEmptyRows = TRUE, colNames = TRUE) expect_equal(nrow(x), 5L - 1L) expect_equal(ncol(x), 8L) ## NA columns expect_true(all(is.na(x$X1))) expect_true(all(is.na(x$X2))) expect_true(all(is.na(x$X3))) expect_true(all(is.na(x$X7))) ############################################################## ## TRUE FALSE FALSE x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = FALSE) expect_equal(nrow(x), 6L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[3, ]))) ############################################################## ## TRUE FALSE TRUE x <- read.xlsx(xlsxFile = xlsxFile, sheet = 4, skipEmptyCols = TRUE, skipEmptyRows = FALSE, colNames = TRUE) expect_equal(nrow(x), 6L - 1L) expect_equal(ncol(x), 4L) ## NA rows expect_true(all(is.na(x[2, ]))) expect_equal(object = getwd(), curr_wd) }) context("Reading from workbook is identical to reading from file read_failure_test.xlsx") test_that("Reading example workbook read_failure_test.xlsx", { curr_wd <- getwd() fl <- system.file("extdata", "read_failure_test.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) x <- read.xlsx(fl, sheet = 1, skipEmptyCols = TRUE) y <- read.xlsx(wb, sheet = 1, skipEmptyCols = TRUE) expect_true(all.equal(x, y)) x <- read.xlsx(fl, sheet = 1, skipEmptyCols = FALSE) y <- read.xlsx(wb, sheet = 1, skipEmptyCols = FALSE) expect_true(all.equal(x, y)) expect_equal(object = getwd(), curr_wd) }) openxlsx/tests/testthat/test-deleting_tables.R0000644000176200001440000002101614155600364021365 0ustar liggesusers context(desc = "Deleting tables from worksheets") test_that("Deleting a Table Object", { wb <- createWorkbook() addWorksheet(wb, sheetName = "Sheet 1") addWorksheet(wb, sheetName = "Sheet 2") writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) # Get table ---- expect_equal(length(getTables(wb, sheet = 1)), 2L) expect_equal(length(getTables(wb, sheet = "Sheet 1")), 2L) expect_equal(length(getTables(wb, sheet = 2)), 0) expect_equal(length(getTables(wb, sheet = "Sheet 2")), 0) expect_error(getTables(wb, sheet = 3)) expect_error(getTables(wb, sheet = "Sheet 3")) expect_equal(getTables(wb, sheet = 1), c("iris", "mtcars"), check.attributes = FALSE) expect_equal(getTables(wb, sheet = "Sheet 1"), c("iris", "mtcars"), check.attributes = FALSE) expect_equal(attr(getTables(wb, sheet = 1), "refs"), c("A1:E151", "J1:T33")) expect_equal(attr(getTables(wb, sheet = "Sheet 1"), "refs"), c("A1:E151", "J1:T33")) expect_equal(length(wb$tables), 2L) ## Deleting a worksheet ---- removeWorksheet(wb, 1) expect_equal(length(wb$tables), 2L) expect_equal(length(getTables(wb, sheet = 1)), 0) expect_equal(attr(wb$tables, "tableName"), c("iris_openxlsx_deleted", "mtcars_openxlsx_deleted")) expect_equal(attr(wb$tables, "sheet"), c(0, 0)) ################################################################################### ## write same tables again writeDataTable(wb, sheet = 1, x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) expect_equal(attr(wb$tables, "tableName"), c("iris_openxlsx_deleted", "mtcars_openxlsx_deleted", "iris", "mtcars")) expect_equal(attr(wb$tables, "sheet"), c(0, 0, 1, 1)) expect_equal(length(getTables(wb, sheet = 1)), 2L) expect_equal(length(getTables(wb, sheet = "Sheet 2")), 2L) expect_error(getTables(wb, sheet = 2)) expect_error(getTables(wb, sheet = "Sheet 1")) expect_equal(getTables(wb, sheet = 1), c("iris", "mtcars"), check.attributes = FALSE) expect_equal(getTables(wb, sheet = "Sheet 2"), c("iris", "mtcars"), check.attributes = FALSE) expect_equal(attr(getTables(wb, sheet = 1), "refs"), c("A1:E151", "J1:T33")) expect_equal(attr(getTables(wb, sheet = "Sheet 2"), "refs"), c("A1:E151", "J1:T33")) expect_equal(length(wb$tables), 4L) ################################################################################### ## removeTable ## remove iris and re-write it removeTable(wb = wb, sheet = 1, table = "iris") expect_equal(length(wb$tables), 4L) expect_equal(wb$worksheets[[1]]$tableParts, "", check.attributes = FALSE) expect_equal(attr(wb$worksheets[[1]]$tableParts, "tableName"), "mtcars") expect_equal(attr(wb$tables, "tableName"), c( "iris_openxlsx_deleted", "mtcars_openxlsx_deleted", "iris_openxlsx_deleted", "mtcars" )) ## removeTable clears table object and all data writeDataTable(wb, sheet = 1, x = iris, tableName = "iris", startCol = 1) expect_equal(wb$worksheets[[1]]$tableParts, c("", ""), check.attributes = FALSE) expect_equal(attr(wb$worksheets[[1]]$tableParts, "tableName"), c("mtcars", "iris")) removeTable(wb = wb, sheet = 1, table = "iris") expect_equal(length(wb$tables), 5L) expect_equal(wb$worksheets[[1]]$tableParts, "", check.attributes = FALSE) expect_equal(attr(wb$worksheets[[1]]$tableParts, "tableName"), "mtcars") expect_equal(attr(wb$tables, "tableName"), c( "iris_openxlsx_deleted", "mtcars_openxlsx_deleted", "iris_openxlsx_deleted", "mtcars", "iris_openxlsx_deleted" )) expect_equal(getTables(wb, sheet = 1), "mtcars", check.attributes = FALSE) }) test_that("Save and load Table Deletion", { temp_file <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb, sheetName = "Sheet 1") addWorksheet(wb, sheetName = "Sheet 2") writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) ################################################################################### ## Deleting a worksheet removeWorksheet(wb, 1) expect_equal(length(wb$tables), 2L) expect_equal(length(getTables(wb, sheet = 1)), 0) expect_equal(attr(wb$tables, "tableName"), c("iris_openxlsx_deleted", "mtcars_openxlsx_deleted")) expect_equal(attr(wb$tables, "sheet"), c(0, 0)) ## both table were written to sheet 1 and are expected to not exist after load saveWorkbook(wb = wb, file = temp_file, overwrite = TRUE) wb <- loadWorkbook(file = temp_file) expect_null(wb$tables) unlink(temp_file) ################################################################################### ## Deleting a table wb <- createWorkbook() addWorksheet(wb, sheetName = "Sheet 1") addWorksheet(wb, sheetName = "Sheet 2") writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) ## remove iris and re-write it removeTable(wb = wb, sheet = 1, table = "iris") expect_equal(attr(wb$tables, "tableName"), c("iris_openxlsx_deleted", "mtcars")) temp_file <- temp_xlsx() saveWorkbook(wb = wb, file = temp_file, overwrite = TRUE) wb <- loadWorkbook(file = temp_file) expect_equal(length(wb$tables), 1L) expect_equal(unname(attr(wb$tables, "tableName")), "mtcars") expect_equal(wb$worksheets[[1]]$tableParts, "", check.attributes = FALSE) ## rId reset expect_equal(unname(attr(wb$worksheets[[1]]$tableParts, "tableName")), "mtcars") unlink(temp_file) ## now delete the other table wb <- createWorkbook() addWorksheet(wb, sheetName = "Sheet 1") addWorksheet(wb, sheetName = "Sheet 2") writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) writeDataTable(wb, sheet = 2, x = mtcars, tableName = "mtcars2", startCol = 3) removeTable(wb = wb, sheet = 1, table = "iris") removeTable(wb = wb, sheet = 1, table = "mtcars") expect_equal(attr(wb$tables, "tableName"), c("iris_openxlsx_deleted", "mtcars_openxlsx_deleted", "mtcars2")) temp_file <- temp_xlsx() saveWorkbook(wb = wb, file = temp_file, overwrite = TRUE) wb <- loadWorkbook(file = temp_file) expect_equal(length(wb$tables), 1L) expect_equal(unname(attr(wb$tables, "tableName")), "mtcars2") expect_length(wb$worksheets[[1]]$tableParts, 0) expect_equal(wb$worksheets[[2]]$tableParts, "", check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[2]]$tableParts, "tableName")), "mtcars2") unlink(temp_file) ## write tables back in writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) expect_equal(length(wb$tables), 3L) expect_equal(unname(attr(wb$tables, "tableName")), c("mtcars2", "iris", "mtcars")) expect_length(wb$worksheets[[1]]$tableParts, 2) expect_equal(wb$worksheets[[1]]$tableParts, c("", ""), check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[1]]$tableParts, "tableName")), c("iris", "mtcars")) expect_length(wb$worksheets[[2]]$tableParts, 1) expect_equal(wb$worksheets[[2]]$tableParts, c(""), check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[2]]$tableParts, "tableName")), "mtcars2") saveWorkbook(wb = wb, file = temp_file, overwrite = TRUE) ## Ids should get reset after load wb <- loadWorkbook(file = temp_file) expect_equal(length(wb$tables), 3L) expect_equal(unname(attr(wb$tables, "tableName")), c("iris", "mtcars", "mtcars2")) expect_length(wb$worksheets[[1]]$tableParts, 2) expect_equal(wb$worksheets[[1]]$tableParts, c("", ""), check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[1]]$tableParts, "tableName")), c("iris", "mtcars")) expect_length(wb$worksheets[[2]]$tableParts, 1) expect_equal(wb$worksheets[[2]]$tableParts, c(""), check.attributes = FALSE) expect_equal(unname(attr(wb$worksheets[[2]]$tableParts, "tableName")), "mtcars2") unlink(temp_file) }) openxlsx/tests/testthat/test-worksheet_ordering.R0000644000176200001440000001640614155600364022153 0ustar liggesusers context("Re-ordering worksheets.") test_that("Worksheet ordering from new Workbook", { genWS <- function(wb, sheetName) { addWorksheet(wb, sheetName) writeDataTable(wb, sheetName, data.frame("X" = sprintf("This is sheet: %s", sheetName)), colNames = FALSE) } wb <- createWorkbook() genWS(wb, "Sheet 1") genWS(wb, "Sheet 2") genWS(wb, "Sheet 3") tempFile <- temp_xlsx("orderingTest") ## no ordering saveWorkbook(wb, file = tempFile, overwrite = TRUE) expect_equal(names(wb), sprintf("Sheet %s", 1:3)) wb <- loadWorkbook(tempFile) expect_equal(names(wb), sprintf("Sheet %s", 1:3)) ## re-order doesnt do anything worksheetOrder(wb) <- c(3, 2, 1) expect_equal(names(wb), sprintf("Sheet %s", 1:3)) saveWorkbook(wb, file = tempFile, overwrite = TRUE) expect_equal(names(wb), sprintf("Sheet %s", 1:3)) ## reloading - reordered wb <- loadWorkbook(file = tempFile) expect_equal(names(wb), sprintf("Sheet %s", 3:1)) x <- read.xlsx(tempFile, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(tempFile, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(tempFile, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 1") ## reloading - reordered - reading from the workbook object x <- read.xlsx(wb, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(wb, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(wb, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 1") ## save and re-load again saveWorkbook(wb, tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) expect_equal(names(wb), sprintf("Sheet %s", 3:1)) x <- read.xlsx(wb, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(wb, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(wb, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 1") x <- read.xlsx(wb, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(wb, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(wb, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 1") ###### re-order again worksheetOrder(wb) <- c(2, 3, 1) saveWorkbook(wb, tempFile, overwrite = TRUE) x <- read.xlsx(tempFile, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(tempFile, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 1") x <- read.xlsx(tempFile, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 3") wb <- loadWorkbook(tempFile) expect_equal(names(wb), sprintf("Sheet %s", c(2, 1, 3))) x <- read.xlsx(wb, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(wb, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 1") x <- read.xlsx(wb, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 3") ## add a worksheet genWS(wb, sheetName = "Sheet 4") x <- read.xlsx(wb, sheet = 4)[[1]] expect_equal(x, "This is sheet: Sheet 4") ## re-order and add worksheet then save worksheetOrder(wb) <- c(3, 1, 4, 2) names(wb) saveWorkbook(wb, tempFile, overwrite = TRUE) ## read from file x <- read.xlsx(tempFile, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(tempFile, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(tempFile, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 4") x <- read.xlsx(tempFile, sheet = 4)[[1]] expect_equal(x, "This is sheet: Sheet 1") x <- read.xlsx(tempFile, sheet = "Sheet 3")[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(tempFile, sheet = "Sheet 2")[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(tempFile, sheet = "Sheet 4")[[1]] expect_equal(x, "This is sheet: Sheet 4") x <- read.xlsx(tempFile, sheet = "Sheet 1")[[1]] expect_equal(x, "This is sheet: Sheet 1") ## read from workbook wb <- loadWorkbook(tempFile) x <- read.xlsx(wb, sheet = 1)[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(wb, sheet = 2)[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(wb, sheet = 3)[[1]] expect_equal(x, "This is sheet: Sheet 4") x <- read.xlsx(wb, sheet = 4)[[1]] expect_equal(x, "This is sheet: Sheet 1") ## read from workbook using name wb <- loadWorkbook(tempFile) x <- read.xlsx(wb, sheet = "Sheet 3")[[1]] expect_equal(x, "This is sheet: Sheet 3") x <- read.xlsx(wb, sheet = "Sheet 2")[[1]] expect_equal(x, "This is sheet: Sheet 2") x <- read.xlsx(wb, sheet = "Sheet 1")[[1]] expect_equal(x, "This is sheet: Sheet 1") x <- read.xlsx(wb, sheet = "Sheet 4")[[1]] expect_equal(x, "This is sheet: Sheet 4") writeData(wb, sheet = "Sheet 3", iris[1:10, 1:4], startRow = 5) x <- read.xlsx(wb, sheet = "Sheet 3", startRow = 5, colNames = TRUE) expect_equal(x, iris[1:10, 1:4]) writeData(wb, sheet = 4, iris[1:20, 1:4], startRow = 5) x <- read.xlsx(wb, sheet = 4, startRow = 5, colNames = TRUE) expect_equal(x, iris[1:20, 1:4]) writeData(wb, sheet = 2, iris[1:30, 1:4], startRow = 5) x <- read.xlsx(wb, sheet = 2, startRow = 5, colNames = TRUE) expect_equal(x, iris[1:30, 1:4]) ## reading from saved file saveWorkbook(wb, tempFile, TRUE) x <- read.xlsx(tempFile, sheet = "Sheet 3", startRow = 5, colNames = TRUE) expect_equal(x, iris[1:10, 1:4]) x <- read.xlsx(tempFile, sheet = 4, startRow = 5, colNames = TRUE) expect_equal(x, iris[1:20, 1:4]) x <- read.xlsx(tempFile, sheet = 2, startRow = 5, colNames = TRUE) expect_equal(x, iris[1:30, 1:4]) ## And finally load again wb <- loadWorkbook(tempFile) x <- read.xlsx(wb, sheet = "Sheet 3", startRow = 5, colNames = TRUE) expect_equal(x, iris[1:10, 1:4]) x <- read.xlsx(wb, sheet = 4, startRow = 5, colNames = TRUE) expect_equal(x, iris[1:20, 1:4]) x <- read.xlsx(wb, sheet = 2, startRow = 5, colNames = TRUE) expect_equal(x, iris[1:30, 1:4]) unlink(tempFile, recursive = TRUE, force = TRUE) rm(wb) }) test_that("Worksheet ordering from new Workbook", { tempFile <- temp_xlsx() wb <- createWorkbook() addWorksheet(wb = wb, sheetName = "Sheet 1", gridLines = FALSE) writeDataTable(wb = wb, sheet = 1, x = iris) addWorksheet(wb = wb, sheetName = "mtcars (Sheet 2)", gridLines = FALSE) writeData(wb = wb, sheet = 2, x = mtcars) addWorksheet(wb = wb, sheetName = "Sheet 3", gridLines = FALSE) writeData(wb = wb, sheet = 3, x = Formaldehyde) worksheetOrder(wb) names(wb) worksheetOrder(wb) <- c(1, 3, 2) # switch position of sheets 2 & 3 names(wb) writeData(wb, 2, 'This is still the "mtcars" worksheet', startCol = 15) names(wb) writeData(wb, "Sheet 3", "writing to sheet 3", startCol = 15) worksheetOrder(wb) names(wb) ## ordering within workbook is not changed saveWorkbook(wb, tempFile, overwrite = TRUE) worksheetOrder(wb) <- c(3, 2, 1) saveWorkbook(wb, tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) worksheetOrder(wb) <- c(3, 2, 1) unlink(tempFile, recursive = TRUE, force = TRUE) rm(wb) }) openxlsx/tests/testthat/test-trying_to_break_openxlsx.R0000644000176200001440000001553514155600364023373 0ustar liggesusers context("Images and Tables.") test_that("Images and Tables - reordering and removing", { if (FALSE) { options("stringsAsFactors" = FALSE) tempFile <- temp_xlsx("break") getPlot <- function(i) { n <- 5000 plot(1:n, rnorm(n)) title(main = sprintf("Plot for Sheet: %s", i)) } df1 <- iris[1:5, 1:4] df2 <- mtcars df3 <- data.frame( "Date" = Sys.Date() - 0:10, "Logical" = sample(c(TRUE, FALSE), 1, replace = TRUE), "Currency" = as.numeric(-5:5) * 100, "Accounting" = as.numeric(-5:5), "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(-5, 5, length.out = 11), "TinyNumber" = runif(11) / 1E9, stringsAsFactors = FALSE ) df3U <- df3 class(df3$Currency) <- "currency" class(df3$Accounting) <- "accounting" class(df3$hLink) <- "hyperlink" class(df3$Percentage) <- "percentage" class(df3$TinyNumber) <- "scientific" df4 <- data.frame("X" = 1:10000, "Y" = sample(LETTERS, size = 10000, replace = TRUE)) df5 <- USJudgeRatings hs <- createStyle(fontColour = "blue", textRotation = 45) wb <- createWorkbook() expect_equal(names(wb), character(0)) addWorksheet(wb = wb, sheetName = "Sheet 1", gridLines = FALSE, tabColour = "red", zoom = 75) writeDataTable(wb, sheet = 1, x = df1, startCol = 7, startRow = 10, tableName = "Sheet1Table1") expect_equal(names(wb), "Sheet 1") addWorksheet(wb, sheetName = "Sheet 2", tabColour = "purple") writeDataTable(wb, sheet = "Sheet 2", x = df2, startCol = 2, startRow = 2, rowNames = TRUE) expect_equal(names(wb), c("Sheet 1", "Sheet 2")) addWorksheet(wb, sheetName = "Sheet 3", tabColour = "green") writeDataTable(wb, sheet = 3, x = df3, startCol = 1, startRow = 1) expect_equal(names(wb), c("Sheet 1", "Sheet 2", "Sheet 3")) addWorksheet(wb, sheetName = "Sheet 4", tabColour = "orange") writeDataTable(wb, sheet = 4, x = df4) expect_equal(names(wb), c("Sheet 1", "Sheet 2", "Sheet 3", "Sheet 4")) addWorksheet(wb, sheetName = "Sheet 5", tabColour = "yellow") writeData(wb, sheet = "Sheet 5", x = df5, rowNames = TRUE) expect_equal(names(wb), c("Sheet 1", "Sheet 2", "Sheet 3", "Sheet 4", "Sheet 5")) worksheetOrder(wb) <- c(1, 3, 5, 4, 2) expect_equal(names(wb), c("Sheet 1", "Sheet 2", "Sheet 3", "Sheet 4", "Sheet 5")) ## save and load 1 saveWorkbook(wb, file = tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) expect_equal(names(wb), c("Sheet 1", "Sheet 3", "Sheet 5", "Sheet 4", "Sheet 2")) expect_equal(df1, read.xlsx(wb, sheet = 1)) expect_equal(df1, read.xlsx(wb, sheet = "Sheet 1")) expect_equal(df1, read.xlsx(tempFile, sheet = 1)) expect_equal(df1, read.xlsx(tempFile, sheet = "Sheet 1")) expect_equal(df3U, read.xlsx(wb, sheet = 2, detectDates = TRUE)) expect_equal(df3U, read.xlsx(wb, sheet = "Sheet 3", detectDates = TRUE)) expect_equal(df3U, read.xlsx(tempFile, sheet = 2, detectDates = TRUE)) expect_equal(df3U, read.xlsx(tempFile, sheet = "Sheet 3", detectDates = TRUE)) expect_equal(df5, read.xlsx(wb, sheet = 3, rowNames = TRUE)) expect_equal(df5, read.xlsx(wb, sheet = "Sheet 5", rowNames = TRUE)) expect_equal(df5, read.xlsx(tempFile, sheet = 3, rowNames = TRUE)) expect_equal(df5, read.xlsx(tempFile, sheet = "Sheet 5", rowNames = TRUE)) expect_equal(df4, read.xlsx(wb, sheet = 4)) expect_equal(df4, read.xlsx(wb, sheet = "Sheet 4")) expect_equal(df4, read.xlsx(tempFile, sheet = 4)) expect_equal(df4, read.xlsx(tempFile, sheet = "Sheet 4")) expect_equal(df2, read.xlsx(wb, sheet = 5, rowNames = TRUE)) expect_equal(df2, read.xlsx(wb, sheet = "Sheet 2", rowNames = TRUE)) expect_equal(df2, read.xlsx(tempFile, sheet = 5, rowNames = TRUE)) expect_equal(df2, read.xlsx(tempFile, sheet = "Sheet 2", rowNames = TRUE)) ## remove "Sheet 5" by index (3) removeWorksheet(wb, sheet = 3) expect_equal(names(wb), c("Sheet 1", "Sheet 3", "Sheet 4", "Sheet 2")) ## remove sheet "Sheet 4" removeWorksheet(wb, sheet = "Sheet 4") expect_equal(names(wb), c("Sheet 1", "Sheet 3", "Sheet 2")) ## Introduce some images getPlot(1) insertPlot(wb = wb, sheet = "Sheet 1", startCol = 14, startRow = 3) getPlot(2) insertPlot(wb = wb, sheet = "Sheet 2", startCol = 14, startRow = 3) getPlot(3) insertPlot(wb = wb, sheet = "Sheet 3", startCol = 14, startRow = 3) expect_true(any(grepl("image1", wb$drawings_rels[[1]]))) expect_true(any(grepl("image3", wb$drawings_rels[[2]]))) expect_true(any(grepl("image2", wb$drawings_rels[[3]]))) ## put back to original order worksheetOrder(wb) <- c(1, 3, 2) saveWorkbook(wb, file = tempFile, overwrite = TRUE) wb <- loadWorkbook(file = tempFile) ## drawings added in order expect_true(any(grepl("image1", wb$drawings_rels[[1]]))) expect_true(any(grepl("image2", wb$drawings_rels[[2]]))) expect_true(any(grepl("image3", wb$drawings_rels[[3]]))) ## Introduce some more images getPlot("1_2") insertPlot(wb = wb, sheet = "Sheet 1", startCol = 14, startRow = 25) getPlot("2_2") insertPlot(wb = wb, sheet = "Sheet 2", startCol = 14, startRow = 25) getPlot("3_2") insertPlot(wb = wb, sheet = "Sheet 3", startCol = 14, startRow = 25) saveWorkbook(wb, file = tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) worksheetOrder(wb) <- c(3, 2, 1) saveWorkbook(wb, file = tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) hl <- rep("https://google.com.au", 5) names(hl) <- sprintf("Link to google %s", 1:5) class(hl) <- "hyperlink" writeData(wb, "Sheet 1", hl) ## Add in some column widths setColWidths(wb, sheet = 1, cols = 1:50, widths = "auto") worksheetOrder(wb) <- c(3, 2, 1) removeWorksheet(wb, sheet = "Sheet 2") saveWorkbook(wb, file = tempFile, overwrite = TRUE) wb <- loadWorkbook(tempFile) expect_equal(names(wb), c("Sheet 1", "Sheet 3")) expect_equal(df1, read.xlsx(tempFile, sheet = 1, startRow = 10)) expect_equal(df3U, read.xlsx(tempFile, sheet = 2, detectDates = TRUE)) expect_equal(df1, read.xlsx(wb, sheet = 1, startRow = 10)) expect_equal(df3U, read.xlsx(wb, sheet = 2, detectDates = TRUE)) unlink(tempFile, recursive = TRUE, force = TRUE) rm(wb) } }) test_that("setColWidths() should support zero-length cols", { file <- temp_xlsx() on.exit(unlink(file), add = TRUE) wb <- createWorkbook() ws <- addWorksheet(wb, "empty") tbl <- data.frame(A = 1:3) writeData(wb, ws, tbl) setColWidths(wb, ws, integer(0L), widths = 12) saveWorkbook(wb, file) x <- readWorkbook(file) expect_equal(x, tbl) }) openxlsx/tests/testthat/test-read_xlsx_correct_sheet.R0000644000176200001440000000250714155600364023146 0ustar liggesusers context("Read xlsx") test_that("read.xlsx correct sheet", { fl <- system.file("extdata", "readTest.xlsx", package = "openxlsx") sheet_names <- getSheetNames(file = fl) expected_sheet_names <- c( "Sheet1", "Sheet2", "Sheet 3", "Sheet 4", "Sheet 5", "Sheet 6", "1", "11", "111", "1111", "11111", "111111" ) expect_equal(object = sheet_names, expected = expected_sheet_names) expect_equal(read.xlsx(xlsxFile = fl, sheet = 7), data.frame(x = 1)) expect_equal(read.xlsx(xlsxFile = fl, sheet = 8), data.frame(x = 11)) expect_equal(read.xlsx(xlsxFile = fl, sheet = 9), data.frame(x = 111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = 10), data.frame(x = 1111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = 11), data.frame(x = 11111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = 12), data.frame(x = 111111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = "1"), data.frame(x = 1)) expect_equal(read.xlsx(xlsxFile = fl, sheet = "11"), data.frame(x = 11)) expect_equal(read.xlsx(xlsxFile = fl, sheet = "111"), data.frame(x = 111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = "1111"), data.frame(x = 1111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = "11111"), data.frame(x = 11111)) expect_equal(read.xlsx(xlsxFile = fl, sheet = "111111"), data.frame(x = 111111)) }) openxlsx/tests/testthat/test-activeSheet.R0000644000176200001440000000210214155600364020477 0ustar liggesusers context("active Sheet ") test_that("get and set active sheet of a workbook", { tempFile1 <- temp_xlsx("temp1") tempFile2 <- temp_xlsx("temp2") tempFile3 <- temp_xlsx("temp3") wbook <- createWorkbook() addWorksheet(wbook, sheetName = "S1") addWorksheet(wbook, sheetName = "S2") addWorksheet(wbook, sheetName = "S3") saveWorkbook(wbook,tempFile1) # default value is the first sheet active expect_equal(activeSheet(wbook),1) expect_equal(activeSheet(wbook),loadWorkbook(tempFile1)$ActiveSheet) activeSheet(wbook) <- 1 ## active sheet S1 saveWorkbook(wbook,tempFile2) expect_equal(activeSheet(wbook),1) expect_equal(activeSheet(wbook),loadWorkbook(tempFile2)$ActiveSheet) activeSheet(wbook) <- "S2" ## active sheet S2 saveWorkbook(wbook,tempFile3) expect(activeSheet(wbook),2) expect_equal(activeSheet(wbook),loadWorkbook(tempFile3)$ActiveSheet) unlink(tempFile1, recursive = TRUE, force = TRUE) unlink(tempFile2, recursive = TRUE, force = TRUE) unlink(tempFile3, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-outlines.R0000644000176200001440000001045414155600364020106 0ustar liggesuserscontext("Workbook groupings") # groupRows() requires assigning to wb to global environment # For reference, see here: https://github.com/r-lib/testthat/issues/720 test_that("group columns", { # Grouping then setting widths updates hidden wb <- createWorkbook() addWorksheet(wb, "Sheet 1") groupColumns(wb, "Sheet 1", 2:3, hidden = T) setColWidths(wb, "Sheet 1", 2, widths = "18", hidden = F) expect_equal(attr(wb$colOutlineLevels[[1]], "hidden")[attr(wb$colOutlineLevels[[1]], "names") == 2], "0") # Setting column widths then grouping wb <- createWorkbook() addWorksheet(wb, "Sheet 1") setColWidths(wb, "Sheet 1", 2:3, widths = "18", hidden = F) groupColumns(wb, "Sheet 1", 1:2, hidden = T) expect_equal(attr(wb$colWidths[[1]], "hidden")[attr(wb$colWidths[[1]], "names") == 2], "1") }) test_that("group rows", { wb <- createWorkbook() assign("wb", wb, envir = .GlobalEnv) addWorksheet(wb, "Sheet 1") groupRows(wb, "Sheet 1", 1:4, hidden = T) expect_equal(names(wb$outlineLevels[[1]]), c("1", "2", "3", "4")) expect_equal(unique(attr(wb$outlineLevels[[1]], "hidden")), "1") rm(wb) }) test_that("ungroup columns", { # OutlineLevelCol is removed from SheetFormatPr when no # column groupings left wb <- createWorkbook() addWorksheet(wb, "Sheet 1") setColWidths(wb, "Sheet 1", 2:3, widths = "18", hidden = F) groupColumns(wb, "Sheet 1", 1:3, hidden = T) ungroupColumns(wb, "Sheet 1", 1:3) expect_equal(unique(attr(wb$colWidths[[1]], "hidden")[attr(wb$colWidths[[1]], "names") %in% c(2, 3)]), "0") }) test_that("ungroup rows", { wb <- createWorkbook() assign("wb", wb, envir = .GlobalEnv) addWorksheet(wb, "Sheet 1") groupRows(wb, "Sheet 1", 1:3, hidden = T) ungroupRows(wb, "Sheet 1", 1:3) expect_equal(length(wb$outlineLevels[[1]]), 0L) rm(wb) }) test_that("loading workbook preserves outlines", { fl <- system.file("extdata", "groupTest.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) expect_equal(names(wb$colOutlineLevels[[1]]), c("2", "3", "4")) expect_equal(names(wb$outlineLevels[[1]]), c("3", "4")) expect_equal(unique(attr(wb$outlineLevels[[1]], "hidden")[names(wb$outlineLevels[[1]]) %in% c("3", "4")]), "true") wbb <- createWorkbook() addWorksheet(wbb, sheetName = "Test", gridLines = FALSE, tabColour = "deepskyblue") writeData(wbb, sheet = "Test", x = c(colA = "testcol1", colB = "testcol2")) groupColumns(wbb, "Test", cols = 2:3, hidden = FALSE) setColWidths(wbb, sheet = "Test", cols=c(1:5), widths = c(9,9,9,9,9)) groupColumns(wbb, "Test", cols = 5:10, hidden = FALSE) setColWidths(wbb, "Test", cols = 15:20, widths = 9) tf <- temp_xlsx("test") tf2 <- temp_xlsx("test2") saveWorkbook(wbb, tf, overwrite = T) test <- wbb$worksheets[[1]]$copy() wb <- loadWorkbook(tf) saveWorkbook(wb, tf2, overwrite = T) testthat::expect_equal(wb$worksheets[[1]], test) unlink(c("tf", "tf2"), recursive = TRUE, force = TRUE) }) test_that("Grouping after setting colwidths has correct length of hidden attributes", { # Issue #100 - https://github.com/ycphs/openxlsx/issues/100 wb <- createWorkbook(title = "column width and grouping error") addWorksheet(wb, sheetName = 1) setColWidths( wb, sheet = 1, cols = 1:100, widths = 8 ) groupColumns(wb, sheet = 1, cols = 20:100, hidden = TRUE) expect_equal(length(wb$colOutlineLevels[[1]]), length(attr(wb$colOutlineLevels[[1]], "hidden"))) }) test_that("Consecutive calls to saveWorkbook doesn't corrupt attributes", { wbb <- createWorkbook() addWorksheet(wbb, sheetName = "Test", gridLines = FALSE, tabColour = "deepskyblue") writeData(wbb, sheet = "Test", x = c(colA = "testcol1", colB = "testcol2")) groupColumns(wbb, "Test", cols = 2:3, hidden = FALSE) setColWidths(wbb, sheet = "Test", cols=c(1:5), widths = c(9,9,9,9,9)) groupColumns(wbb, "Test", cols = 5:10, hidden = FALSE) setColWidths(wbb, "Test", cols = 15:20, widths = 9) tf <- temp_xlsx("test") tf2 <- temp_xlsx("test2") saveWorkbook(wbb, tf, overwrite = T) test <- wbb$worksheets[[1]]$copy() saveWorkbook(wbb, tf2, overwrite = T) testthat::expect_equal(wbb$worksheets[[1]], test) unlink(c("tf", "tf2"), recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat/test-read_xlsx_random_seed.R0000644000176200001440000000050314155600364022567 0ustar liggesuserstest_that("read_xlsx() does not change random seed", { rs <- .Random.seed expect_identical(rs, .Random.seed) tf <- temp_xlsx() expect_identical(rs, .Random.seed) write.xlsx(data.frame(a = 1), tf) expect_identical(rs, .Random.seed) read.xlsx(tf) expect_identical(rs, .Random.seed) unlink(tf) }) openxlsx/tests/testthat/test-getBaseFont.R0000644000176200001440000000100514155600364020435 0ustar liggesuserstest_that("getBaseFont works", { wb <- createWorkbook() expect_equal( getBaseFont(wb), list( size = list(val = "11"), # should this be "#000000"? colour = list(rgb = "FF000000"), name = list(val = "Calibri") ) ) modifyBaseFont(wb, fontSize = 9, fontName = "Arial", fontColour = "red") expect_equal( getBaseFont(wb), list( size = list(val = "9"), colour = list(rgb = "FFFF0000"), name = list(val = "Arial") ) ) }) openxlsx/tests/testthat/test-styles.R0000644000176200001440000000261114155600364017563 0ustar liggesusers context("Styles") test_that("setStyle", { tmp_file <- temp_xlsx() # lorem ipsum txt <- paste0( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, ", "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ", "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris", "nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in ", "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla", "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in ", "culpa qui officia deserunt mollit anim id est laborum." ) ## create workbook wb <- createWorkbook() addWorksheet(wb, "Test") writeData(wb, "Test", txt) ## create a style s <- createStyle( fontSize = 12, fontColour = "black", valign="center", wrapText = TRUE, halign = "justify" ) addStyle(wb, "Test", s, 1, 1) setColWidths(wb, "Test", 1, 50) setRowHeights(wb, "Test", 1, 150) ## save workbook saveWorkbook(wb, tmp_file) ## load it again wb2 <- loadWorkbook(tmp_file) s2 <- getStyles(wb2)[[1]] ## test that the style survived the round trip expect_equal(s2$fontSize, c(val="12")) expect_equal(s2$fontColour, c(rgb="FF000000")) expect_equal(s2$valign, "center") expect_equal(s2$wrapText, TRUE) expect_equal(s2$halign, "justify") }) openxlsx/tests/testthat/test-remove_worksheets.R0000644000176200001440000000311214155600364022010 0ustar liggesusers context("Removing worksheets.") test_that("Deleting worksheets", { tempFile <- temp_xlsx() genWS <- function(wb, sheetName) { addWorksheet(wb, sheetName) writeDataTable(wb, sheetName, data.frame("X" = sprintf("This is sheet: %s", sheetName)), colNames = FALSE) } wb <- createWorkbook() genWS(wb, "Sheet 1") genWS(wb, "Sheet 2") genWS(wb, "Sheet 3") expect_equal(names(wb), c("Sheet 1", "Sheet 2", "Sheet 3")) removeWorksheet(wb, sheet = 1) expect_equal(names(wb), c("Sheet 2", "Sheet 3")) removeWorksheet(wb, sheet = 1) expect_equal(names(wb), c("Sheet 3")) ## add to end genWS(wb, "Sheet 1") genWS(wb, "Sheet 2") expect_equal(names(wb), c("Sheet 3", "Sheet 1", "Sheet 2")) saveWorkbook(wb, tempFile, overwrite = TRUE) ## re-load & re-order worksheets wb <- loadWorkbook(tempFile) expect_equal(names(wb), c("Sheet 3", "Sheet 1", "Sheet 2")) writeData(wb, sheet = "Sheet 2", x = iris[1:10, 1:4], startRow = 5) expect_equal(iris[1:10, 1:4], read.xlsx(wb, "Sheet 2", startRow = 5)) writeData(wb, sheet = 1, x = iris[1:20, 1:4], startRow = 5) expect_equal(iris[1:20, 1:4], read.xlsx(wb, "Sheet 3", startRow = 5)) removeWorksheet(wb, sheet = 1) expect_equal("This is sheet: Sheet 1", read.xlsx(wb, 1, startRow = 1)[[1]]) removeWorksheet(wb, sheet = 2) expect_equal("This is sheet: Sheet 1", read.xlsx(wb, 1, startRow = 1)[[1]]) removeWorksheet(wb, sheet = 1) expect_equal(names(wb), character(0)) unlink(tempFile, recursive = TRUE, force = TRUE) rm(wb) }) openxlsx/tests/testthat/test-options.R0000644000176200001440000000333614155600364017740 0ustar liggesusers test_that("option names are appropriate", { bad <- grep("^openxlsx[.].*", names(op.openxlsx), value = TRUE, invert = TRUE) expect_equal(bad, character(0)) }) test_that("changing options", { op <- options() # Set via options() options(openxlsx.borders = "whatever") expect_equal(openxlsx_getOp("borders"), getOption("openxlsx.borders")) expect_equal(openxlsx_getOp("borders"), "whatever") # Set via openxlsx_setOp() openxlsx_setOp("borders", "new_whatever") expect_equal(openxlsx_getOp("borders"), getOption("openxlsx.borders")) expect_equal(openxlsx_getOp("borders"), "new_whatever") # with openxlsx. prefix openxlsx_setOp("openxlsx.borders", "new_new_whatever") expect_equal(openxlsx_getOp("openxlsx.borders"), getOption("openxlsx.borders")) expect_equal(openxlsx_getOp("openxlsx.borders"), "new_new_whatever") options(openxlsx.tableStyle = "Cool format") expect_equal(openxlsx_getOp("tableStyle"), openxlsx_getOp("openxlsx.tableStyle")) # Setting to NULL will return default options(openxlsx.borders = NULL) expect_equal(openxlsx_getOp("borders"), op.openxlsx[["openxlsx.borders"]]) # Bad options names will trigger warning but still be produced options(openxlsx.likelyNotARealOption = TRUE) expect_warning( expect_true(openxlsx_getOp("likelyNotARealOption")), "not a standard openxlsx option" ) # Multiple Ops returns error expect_error(openxlsx_getOp(c("withFilter", "borders")), "length 1") openxlsx_resetOp() options(op) }) test_that("openxlsx_setOp() works with list [#215]", { op <- options() expect_error(openxlsx_setOp(list(withFilter = TRUE, keepNA = TRUE)), NA) openxlsx_resetOp() options(op) }) openxlsx/tests/testthat/test-protect-worksheet.R0000644000176200001440000000151314155600364021731 0ustar liggesusers context("Protection") test_that("Protection", { wb <- createWorkbook() addWorksheet(wb, "s1") addWorksheet(wb, "s2") protectWorksheet(wb, sheet = "s1", protect = TRUE, password = "abcdefghij", lockSelectingLockedCells = FALSE, lockSelectingUnlockedCells = FALSE, lockFormattingCells = TRUE, lockFormattingColumns = TRUE, lockPivotTables = TRUE) expect_true(wb$worksheets[[1]]$sheetProtection == "") protectWorksheet(wb, sheet = "s2", protect = TRUE) expect_true(wb$worksheets[[2]]$sheetProtection == "") protectWorksheet(wb, sheet = "s2", protect = FALSE) expect_true(wb$worksheets[[2]]$sheetProtection == "") }) openxlsx/tests/testthat/test-table_overlaps.R0000644000176200001440000001076214155600364021250 0ustar liggesusers context("Writing over tables") test_that("writeDataTable over tables", { overwrite_table_error <- "Cannot overwrite existing table with another table" df1 <- data.frame("X" = 1:10) wb <- createWorkbook() addWorksheet(wb, "Sheet1") ## table covers rows 4->10 and cols 4->8 writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 4) ## should all run without error writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 3, startRow = 2) writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 9, startRow = 2) writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 4, startRow = 11) writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 5, startRow = 11) writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 6, startRow = 11) writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 7, startRow = 11) writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 8, startRow = 11) writeDataTable(wb = wb, sheet = 1, x = head(iris, 2), startCol = 4, startRow = 1) ## Now error expect_error(writeDataTable(wb = wb, sheet = 1, x = df1, startCol = "H", startRow = 21), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 3, startRow = 12), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = df1, startCol = 9, startRow = 12), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = df1, startCol = "i", startRow = 12), regexp = overwrite_table_error) ## more errors expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris)), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 21), regexp = overwrite_table_error) ## should work writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 22) writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 40) ## more errors expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris, 2), startCol = 4, startRow = 38), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris, 2), startCol = 4, startRow = 38, colNames = FALSE), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = "H", startRow = 40), regexp = overwrite_table_error) writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = "I", startRow = 40) writeDataTable(wb = wb, sheet = 1, x = head(iris)[, 1:3], startCol = "A", startRow = 40) expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris, 2), startCol = 4, startRow = 38, colNames = FALSE), regexp = overwrite_table_error) expect_error(writeDataTable(wb = wb, sheet = 1, x = head(iris, 2), startCol = 1, startRow = 46, colNames = FALSE), regexp = overwrite_table_error) }) test_that("writeData over tables", { overwrite_table_error <- "Cannot overwrite table headers. Avoid writing over the header row" df1 <- data.frame("X" = 1:10) wb <- createWorkbook() addWorksheet(wb, "Sheet1") ## table covers rows 4->10 and cols 4->8 writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 4) ## Anywhere on row 5 is fine for (i in 1:10) { writeData(wb = wb, sheet = 1, x = head(iris), startRow = 5, startCol = i) } ## Anywhere on col i is fine for (i in 1:10) { writeData(wb = wb, sheet = 1, x = head(iris), startRow = i, startCol = "i") } ## Now errors on headers expect_error(writeData(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 4), regexp = overwrite_table_error) writeData(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 5) writeData(wb = wb, sheet = 1, x = head(iris)[1:3]) writeData(wb = wb, sheet = 1, x = head(iris, 2), startCol = 4) writeData(wb = wb, sheet = 1, x = head(iris, 2), startCol = 4, colNames = FALSE) ## Example of how this should be used writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 30) writeData(wb = wb, sheet = 1, x = head(iris), startCol = 4, startRow = 31, colNames = FALSE) writeDataTable(wb = wb, sheet = 1, x = head(iris), startCol = 10, startRow = 30) writeData(wb = wb, sheet = 1, x = tail(iris), startCol = 10, startRow = 31, colNames = FALSE) writeDataTable(wb = wb, sheet = 1, x = head(iris)[, 1:3], startCol = 1, startRow = 30) writeData(wb = wb, sheet = 1, x = tail(iris), startCol = 1, startRow = 31, colNames = FALSE) }) openxlsx/tests/testthat/test-build_workbook.R0000644000176200001440000000262714155600364021263 0ustar liggesuserstest_that("buildWorkbook() accepts tableName [187]", { x <- data.frame(a = 1, b = 2) # default name wb <- buildWorkbook(x, asTable = TRUE) expect_equal(attr(wb$tables, "tableName"), "Table3") # define 1/2 table name wb <- buildWorkbook(x, asTable = TRUE, tableName = "table_x") expect_equal(attr(wb$tables, "tableName"), "table_x") # define 2/2 table names wb <- buildWorkbook(list(x, x), asTable = TRUE, tableName = c("table_x", "table_y")) expect_equal(attr(wb$tables, "tableName"), c("table_x", "table_y")) # try to define 1/2 table names expect_error(buildWorkbook(list(x, x), asTable = TRUE, tableName = "table_x")) }) test_that("row.name and col.name are deprecated", { x <- data.frame(a = 1) expect_warning( buildWorkbook(x, file = temp_xlsx(), row.names = TRUE, overwrite = TRUE), "Please use 'rowNames' instead of 'row.names'" ) expect_warning( buildWorkbook(x, file = temp_xlsx(), row.names = TRUE, overwrite = TRUE, asTable = TRUE), "Please use 'rowNames' instead of 'row.names'" ) expect_warning( buildWorkbook(x, file = temp_xlsx(), col.names = TRUE, overwrite = TRUE), "Please use 'colNames' instead of 'col.names'" ) expect_warning( buildWorkbook(x, file = temp_xlsx(), col.names = TRUE, overwrite = TRUE, asTable = TRUE), "Please use 'colNames' instead of 'col.names'" ) }) openxlsx/tests/testthat/test-wrappers.R0000644000176200001440000000051614155600364020105 0ustar liggesusers context("Test wrappers") test_that("int2col and col2int", { nums <- 2:27 chrs <- c("B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "AA") expect_equal(chrs, int2col(nums)) expect_equal(nums, col2int(chrs)) }) openxlsx/tests/testthat/test-v3_0_0_bugs.R0000644000176200001440000000062614155600364020252 0ustar liggesusers context("v3.0.0 Bug Fixes") test_that("read.xlsx bug fixes", { file <- system.file("extdata", "readTest.xlsx", package = "openxlsx") df <- read.xlsx(file, sheet = 1, rows = 1:2, cols = 1) expect_equal(df, data.frame("Var1" = TRUE)) df <- read.xlsx(file, sheet = 1, rows = 1, cols = 1, colNames = FALSE) expect_equal(df, data.frame("X1" = "Var1", stringsAsFactors = FALSE)) }) openxlsx/tests/testthat/test-fill_merged_cells.R0000644000176200001440000000327614155600364021703 0ustar liggesusers context("Fill Merged Cells") test_that("fill merged cells", { wb <- createWorkbook() addWorksheet(wb, sheetName = "sheet1") writeData(wb = wb, sheet = 1, x = data.frame("A" = 1, "B" = 2)) writeData(wb = wb, sheet = 1, x = 2, startRow = 2, startCol = 2) writeData(wb = wb, sheet = 1, x = 3, startRow = 2, startCol = 3) writeData(wb = wb, sheet = 1, x = 4, startRow = 2, startCol = 4) writeData(wb = wb, sheet = 1, x = t(matrix(1:4, 4, 4)), startRow = 3, startCol = 1, colNames = FALSE) mergeCells(wb = wb, sheet = 1, cols = 2:4, rows = 1) mergeCells(wb = wb, sheet = 1, cols = 2:4, rows = 3) mergeCells(wb = wb, sheet = 1, cols = 2:4, rows = 4) mergeCells(wb = wb, sheet = 1, cols = 2:4, rows = 5) tmp_file <- temp_xlsx() saveWorkbook(wb = wb, file = tmp_file, overwrite = TRUE) expect_equal(names(read.xlsx(tmp_file, fillMergedCells = FALSE)), c("A", "B", "X3", "X4")) expect_equal(names(read.xlsx(tmp_file, fillMergedCells = TRUE)), c("A", "B", "B", "B")) r1 <- data.frame("A" = rep(1, 5), "B" = rep(2, 5), "X3" = rep(3,5), "X4" = rep(4, 5)) r2 <- data.frame("A" = rep(1, 5), "B" = rep(2, 5), "B1" = c(3,2,2,2,3), "B2" = c(4,2,2,2,4)) names(r2) <- c("A", "B", "B", "B") r2_1 <- r2[1:5, 1:3] names(r2_1) <- c("A", "B", "B") expect_equal(read.xlsx(tmp_file, fillMergedCells = FALSE), r1) expect_equal(read.xlsx(tmp_file, fillMergedCells = TRUE), r2) expect_equal( read.xlsx(tmp_file, cols = 1:3, fillMergedCells = TRUE), r2_1) expect_equal( read.xlsx(tmp_file, rows = 1:3, fillMergedCells = TRUE), r2[1:2, ]) expect_equal( read.xlsx(tmp_file, cols = 1:3, rows = 1:4, fillMergedCells = TRUE), r2_1[1:3,]) }) openxlsx/tests/testthat/test-loading_workbook_unzipped.R0000644000176200001440000000112214155600364023504 0ustar liggesusers context("Load Unzipped Workbook Object") test_that("Loading unzipped readTest.xlsx", { fl <- system.file("extdata", "readTest.xlsx", package = "openxlsx") wb <- loadWorkbook(fl) ## make unzipped file & load tmp_dir <- file.path(tempdir(), paste(sample(LETTERS, 6), collapse = "")) if (dir.exists(tmp_dir)) unlink(tmp_dir, recursive = TRUE) dir.create(tmp_dir) unzip(zipfile = fl, exdir = tmp_dir) wb2 <- loadWorkbook(file = tmp_dir, isUnzipped = TRUE) expect_true(all.equal(wb, wb2)) unlink(tmp_dir, recursive = TRUE) }) "" openxlsx/tests/testthat/test-writing_posixct.R0000644000176200001440000000566014155600364021503 0ustar liggesusers context("Writing Posixct") test_that("Writing Posixct with writeData & writeDataTable", { options("openxlsx.datetimeFormat" = "dd/mm/yy hh:mm") tstart <- strptime("30/05/2017 08:30", "%d/%m/%Y %H:%M", tz = "CET") TimeDT <- c(0, 5, 10, 15, 30, 60, 120, 180, 240, 480, 720, 1440) * 60 + tstart df <- data.frame(TimeDT, TimeTxt = format(TimeDT, "%Y-%m-%d %H:%M")) wb <- createWorkbook() addWorksheet(wb, "writeData") addWorksheet(wb, "writeDataTable") writeData(wb, "writeData", df, startCol = 2, startRow = 3, rowNames = FALSE) writeDataTable(wb, "writeDataTable", df, startCol = 2, startRow = 3) wd <- as.numeric(wb$worksheets[[1]]$sheet_data$v) wdt <- as.numeric(wb$worksheets[[2]]$sheet_data$v) expected <- c( 0, 1, 42885.3541666667, 2, 42885.3576388889, 3, 42885.3611111111, 4, 42885.3645833333, 5, 42885.375, 6, 42885.3958333333, 7, 42885.4375, 8, 42885.4791666667, 9, 42885.5208333333, 10, 42885.6875, 11, 42885.8541666667, 12, 42886.3541666667, 13 ) expect_equal(object = round(wd, 12), expected = expected) expect_equal(object = round(wdt, 12), expected = expected) expect_equal(object = wd, expected = wdt) options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") }) test_that("Writing mixed EDT/EST Posixct with writeData & writeDataTable", { options("openxlsx.datetimeFormat" = "dd/mm/yy hh:mm") tstart1 <- as.POSIXct("12/03/2018 08:30", format = "%d/%m/%Y %H:%M") tstart2 <- as.POSIXct("10/03/2018 08:30", format = "%d/%m/%Y %H:%M") TimeDT1 <- c(NA, 0, 10, 30, 60, 120, 240, 720, 1440) * 60 + tstart1 TimeDT2 <- c(0, 10, 30, 60, 120, 240, 720, NA, 1440) * 60 + tstart2 df <- data.frame( timeval = c(TimeDT1, TimeDT2), timetxt = format(c(TimeDT1, TimeDT2), "%Y-%m-%d %H:%M") ) wb <- createWorkbook() addWorksheet(wb, "writeData") addWorksheet(wb, "writeDataTable") writeData(wb, "writeData", df, startCol = 2, startRow = 3, rowNames = FALSE) writeDataTable(wb, "writeDataTable", df, startCol = 2, startRow = 3) wd <- as.numeric(wb$worksheets[[1]]$sheet_data$v) wdt <- as.numeric(wb$worksheets[[2]]$sheet_data$v) wd <- wd[wb$worksheets[[1]]$sheet_data$cols == 2] wdt <- wdt[wb$worksheets[[2]]$sheet_data$cols == 2] # drop any integer indexes introduced in write wd <- wd[wd != 0 | is.na(wd)] wdt <- wdt[wdt != 0 | is.na(wdt)] # sort everything wd <- convertToDateTime(wd[order(wd)]) wdt <- convertToDateTime(wdt[order(wdt)]) expected <- df$timeval[order(df$timeval)] # compare expect_equal( object = wd, expected = expected, tolerance = 10 ^ -10, check.tzone = FALSE ) expect_equal( object = wdt, expected = expected, tolerance = 10 ^ -10, check.tzone = TRUE ) expect_equal( object = wd, expected = wdt, check.tzone = TRUE ) options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") }) openxlsx/tests/testthat/test-write-permissions.R0000644000176200001440000000067414155600364021752 0ustar liggesusers context("error without write permissions") test_that("test failed write errors for saveWorkbook", { temp_file <- tempfile() file.create(temp_file) Sys.chmod(path = temp_file, mode = "444") wb <- createWorkbook() addWorksheet(wb, "name") expect_warning(write.xlsx( x = cars, file = temp_file, overwrite = TRUE ), regexp = "Permission denied" ) unlink(temp_file, recursive = TRUE, force = TRUE) }) openxlsx/tests/testthat.R0000644000176200001440000000010014155600364015252 0ustar liggesuserslibrary(testthat) library(openxlsx) test_check("openxlsx") openxlsx/src/0000755000176200001440000000000014155742000012717 5ustar liggesusersopenxlsx/src/read_workbook.cpp0000644000176200001440000004527714155600364016300 0ustar liggesusers #include "openxlsx.h" IntegerVector which_cpp(Rcpp::LogicalVector x) { IntegerVector v = seq(0, x.size() - 1); return v[x]; } // [[Rcpp::export]] CharacterVector get_shared_strings(std::string xmlFile, bool isFile){ CharacterVector x; size_t pos = 0; std::string line; std::vector lines; if(isFile){ // READ IN FILE ifstream file; file.open(xmlFile.c_str()); while ( std::getline(file, line) ) { // skip empty lines: if (line.empty()) continue; lines.push_back(line); } line = ""; int n = lines.size(); for(int i = 0;i < n; ++i) line += lines[i] + "\n"; }else{ line = xmlFile; } x = getNodes(line, ""); // define variables for sharedString part int n = x.size(); CharacterVector strs(n); std::fill(strs.begin(), strs.end(), NA_STRING); std::string xml; size_t endPos = 0; std::string ttag = "", 0); if(pos == std::string::npos){ // NO INLINE FORMATTING for(int i = 0; i < n; i++){ // find opening tag xml = x[i]; pos = xml.find(ttag, 0); // find ttag if(pos != std::string::npos){ if(xml[pos+2] != '/'){ pos = xml.find(tag, pos+1); // find where opening ttag ends endPos = xml.find(tagEnd, pos+1); // find where the node ends (closing tag) strs[i] = xml.substr(pos+1, endPos-pos - 1).c_str(); } } } }else{ // we have inline formatting for(int i = 0; i < n; i++){ // find opening tag xml = x[i]; pos = xml.find(ttag, 0); // find ttag if(xml[pos+2] != '/'){ strs[i] = ""; while(1){ if(xml[pos+2] == '/'){ break; }else{ pos = xml.find(tag, pos+1); // find where opening ttag ends endPos = xml.find(tagEnd, pos+1); // find where the node ends (closing tag) strs[i] += xml.substr(pos+1, endPos-pos - 1).c_str(); pos = xml.find(ttag, endPos); // find ttag if(pos == std::string::npos) break; } } } } // end of for loop } // end of else inline formatting return wrap(strs); } // [[Rcpp::export]] List getCellInfo(std::string xmlFile, CharacterVector sharedStrings, bool skipEmptyRows, int startRow, IntegerVector rows, bool getDates){ //read in file std::string buf; std::string xml = read_file_newline(xmlFile); std::string xml2 = ""; std::string rtag = "r="; std::string ttag = " t="; std::string stag = " s="; std::string tagEnd = "\""; std::string vtag = ""; std::string vtag2 = ""); // find sheetData size_t endPos = 0; // If no data if(pos == std::string::npos){ res = List::create(Rcpp::Named("nRows") = 0, Rcpp::Named("r") = 0); return res; } xml = xml.substr(pos + 11); // get from "sheedData" to the end xml2 = xml; // startRow cut off int row_i = 0; if(startRow > 1){ //find r and check the row number pos = xml.find("= startRow){ xml = xml.substr(pos); break; }else{ pos = pos + 8; } pos = xml.find("= row_i]; int nr_sub = rows.size(); if(nr_sub > 0){ for(int i = 0; i < nr; i++){ row_xml_i = xml_rows[i]; pos = row_xml_i.find(" tag and end tag endPos = cell.find("", 0); if(endPos != std::string::npos){ pos = cell.find("", pos); v[j] = cell.substr(pos + 1, endPos - pos - 1); has_v = true; } // find tag and end tag endPos = cell.find("", 0); if(endPos != std::string::npos){ pos = cell.find("", pos); v[j] = cell.substr(pos + 4, endPos - pos - 4); // skip and ", pos_f + 3); // if(endPos == std::string::npos){ // endPos = cell.find("/>", pos_f + 3); // f[j] = cell.substr(pos_f, endPos - pos_f + 2); // }else{ // f[j] = cell.substr(pos_f, endPos - pos_f + 4); // } has_f = true; // do we really have t if(pos_t < pos_f){ endPos = cell.find(tagEnd, pos_t + 4); // find next " t[j] = cell.substr(pos_t + 4, endPos - pos_t - 4); } }else if(pos_t != std::string::npos){ // only have t endPos = cell.find(tagEnd, pos_t + 4); // find next " t[j] = cell.substr(pos_t + 4, endPos - pos_t - 4); }else if(pos_f != std::string::npos){ // only have f // endPos = cell.find("", pos_f + 3); // if(endPos == std::string::npos){ // endPos = cell.find("/>", pos_f + 3); // f[j] = cell.substr(pos_f, endPos - pos_f + 2); // }else{ // f[j] = cell.substr(pos_f, endPos - pos_f + 4); // } has_f = true; } /* since we return only a data frame, we do the preparation here */ if(t[j] == "s"){ auto ss_ind = atoi(v[j]); v[j] = sharedStrings[ss_ind]; if(v[j] == "openxlsx_na_vlu"){ v[j] = NA_STRING; } string_refs[j] = r[j]; }else if(t[j] == "e") { v[j] = NA_STRING; // exception from loadWorkbook }else if(t[j] == "b"){ if(v[j] == "1"){ v[j] = "TRUE"; }else{ v[j] = "FALSE"; } string_refs[j] = r[j]; }else if((t[j] == "str") || (t[j] == "inlineStr")){ string_refs[j] = r[j]; } /* preparation is finished */ if(has_f & (!has_v) & (t[j] != "n")){ v[j] = NA_STRING; }else if(has_f & !has_v){ t[j] = NA_STRING; v[j] = NA_STRING; }else if(has_f | has_v){ }else{ //only have s and r t[j] = NA_STRING; v[j] = NA_STRING; } j++; // INCREMENT OVER OCCURENCES pos = nextPos; pos_t = nextPos; pos_f = nextPos; } // end of while loop over occurences } // END OF CELL AND ATTRIBUTION GATHERING string_refs = string_refs[!is_na(string_refs)]; int nRows = calc_number_rows(r, skipEmptyRows); res = List::create(Rcpp::Named("r") = r, Rcpp::Named("string_refs") = string_refs, Rcpp::Named("v") = v, Rcpp::Named("s") = s, Rcpp::Named("nRows") = nRows, Rcpp::Named("cellMerge") = merge_cell_xml ); return wrap(res); } // [[Rcpp::export]] SEXP read_workbook(IntegerVector cols_in, IntegerVector rows_in, CharacterVector v, IntegerVector string_inds, LogicalVector is_date, bool hasColNames, char hasSepNames, bool skipEmptyRows, bool skipEmptyCols, int nRows, Function clean_names ){ IntegerVector cols = clone(cols_in); IntegerVector rows = clone(rows_in); int nCells = rows.size(); int nDates = is_date.size(); /* do we have any dates */ bool has_date; if(nDates == 1){ if(is_true(any(is_na(is_date)))){ has_date = false; }else{ has_date = true; } }else if(nDates == nCells){ has_date = true; }else{ has_date = false; } bool has_strings = true; IntegerVector st_inds0 (1); st_inds0[0] = string_inds[0]; if(is_true(all(is_na(st_inds0)))) has_strings = false; IntegerVector uni_cols = sort_unique(cols); if(!skipEmptyCols){ // want to keep all columns - just create a sequence from 1:max(cols) uni_cols = seq(1, max(uni_cols)); cols = cols - 1; }else{ cols = match(cols, uni_cols) - 1; } // scale columns from i:j to 1:(j-i+1) int nCols = *std::max_element(cols.begin(), cols.end()) + 1; // scale rows from i:j to 1:(j-i+1) IntegerVector uni_rows = sort_unique(rows); if(skipEmptyRows){ rows = match(rows, uni_rows) - 1; //int nRows = *std::max_element(rows.begin(), rows.end()) + 1; }else{ rows = rows - rows[0]; } // Check if first row are all strings //get first row number CharacterVector col_names(nCols); IntegerVector removeFlag; int pos = 0; // If we are told col_names exist take the first row and fill any gaps with X.i if(hasColNames){ int row_1 = rows[0]; char name[6]; IntegerVector row1_inds = which_cpp(rows == row_1); IntegerVector header_cols = cols[row1_inds]; IntegerVector header_inds = match(seq(0, nCols), na_omit(header_cols)); LogicalVector missing_header = is_na(header_inds); // looping over each column for(unsigned short i=0; i < nCols; i++){ if(missing_header[i]){ // a missing header element sprintf(&(name[0]), "X%hu", i+1); // sprintf(&(name[0]), "X%u", i+1); // snprintf(&(name[0]), sizeof(&(name[0])), "X%d", i+1); // snprintf(&(name[0]), 10, "X%d", i+1); col_names[i] = name; }else{ // this is a header elements col_names[i] = v[pos]; if(col_names[i] == "NA"){ sprintf(&(name[0]), "X%hu", i+1); // sprintf(&(name[0]), "X%du", i+1); // snprintf(&(name[0]), sizeof(&(name[0])), "X%d", i+1); // snprintf(&(name[0]), 10, "X%d", i+1); col_names[i] = name; } pos++; } } // tidy up column names col_names = clean_names(col_names,hasSepNames); //-------------------------------------------------------------------------------- // Remove elements from rows, cols, v that have been used as headers // I've used the first pos elements as headers // stringInds contains the indexes of v which are strings // string_inds <- string_inds[string_inds > pos] if(has_strings){ string_inds = string_inds[string_inds > pos]; string_inds = string_inds - pos; } rows.erase (rows.begin(), rows.begin() + pos); rows = rows - 1; v.erase (v.begin(), v.begin() + pos); //If nothing left return a data.frame with 0 rows if(rows.size() == 0){ List dfList(nCols); IntegerVector rowNames(0); for(int i = 0; i < nCols; i++){ dfList[i] = LogicalVector(0); // this is what read.table does (bool type) } dfList.attr("names") = col_names; dfList.attr("row.names") = rowNames; dfList.attr("class") = "data.frame"; return wrap(dfList); } cols.erase(cols.begin(), cols.begin() + pos); nRows--; // decrement number of rows as first row is now being used as col_names nCells = nCells - pos; // End Remove elements from rows, cols, v that have been used as headers //-------------------------------------------------------------------------------- }else{ // else col_names is FALSE char name[6]; for(int i =0; i < nCols; i++){ sprintf(&(name[0]), "X%hu", i+1); // snprintf(&(name[0]), sizeof(&(name[0])), "X%d", i+1); // sprintf(&(name[0]), "X%u", i+1); col_names[i] = name; } } // ------------------ column names complete // Possible there are no string_inds to begin with and value of string_inds is 0 // Possible we have string_inds but they have now all been used up by headers bool allNumeric = false; if((string_inds.size() == 0) | all(is_na(string_inds))) allNumeric = true; if(has_date){ if(is_true(any(is_date))) allNumeric = false; } // If we have colnames some elements where used to create these -so we remove the corresponding number of elements if(hasColNames & has_date) is_date.erase(is_date.begin(), is_date.begin() + pos); //Intialise return data.frame SEXP m; // for(int i = 0; i < rows.size(); i++) // Rcout << "rows[i]: " << rows[i] << endl; // // Rcout << "nRows " << nRows << endl; // Rcout << "nCols: " << nCols << endl; // Rcout << "cols.size(): " << cols.size() << endl; // Rcout << "rows.size(): " << rows.size() << endl; // Rcout << "is_date.size(): " << is_date.size() << endl; // Rcout << "v.size(): " << v.size() << endl; // Rcout << "has_date: " << has_date << endl; if(allNumeric){ m = buildMatrixNumeric(v, rows, cols, col_names, nRows, nCols); }else{ // If it contains any strings it will be a character column IntegerVector char_cols_unique; if(all(is_na(string_inds))){ char_cols_unique = -1; }else{ IntegerVector columns_which_are_characters = cols[string_inds - 1]; char_cols_unique = unique(columns_which_are_characters); } //date columns IntegerVector date_columns(1); if(has_date){ date_columns = cols[is_date]; date_columns = sort_unique(date_columns); }else{ date_columns[0] = -1; } // List d(10); // d[0] = v; // d[2] = rows; // d[3] = cols; // d[4] = col_names; // d[5] = nRows; // d[6] = nCols; // d[7] = char_cols_unique; // d[8] = date_columns; // return(wrap(d)); // Rcout << "Running buildMatrixMixed" << endl; m = buildMatrixMixed(v, rows, cols, col_names, nRows, nCols, char_cols_unique, date_columns); } return wrap(m) ; } // [[Rcpp::export]] int calc_number_rows(CharacterVector x, bool skipEmptyRows){ int n = x.size(); if(n == 0) return(0); int nRows; if(skipEmptyRows){ CharacterVector res(n); std::string r; for(int i = 0; i < n; i++){ r = x[i]; r.erase(std::remove_if(r.begin(), r.end(), ::isalpha), r.end()); res[i] = r; } CharacterVector uRes = unique(res); nRows = uRes.size(); }else{ std::string fRef = as(x[0]); std::string lRef = as(x[n-1]); fRef.erase(std::remove_if(fRef.begin(), fRef.end(), ::isalpha), fRef.end()); lRef.erase(std::remove_if(lRef.begin(), lRef.end(), ::isalpha), lRef.end()); int firstRow = atoi(fRef.c_str()); int lastRow = atoi(lRef.c_str()); nRows = lastRow - firstRow + 1; } return(nRows); } openxlsx/src/write_data.cpp0000644000176200001440000001313114155600364015553 0ustar liggesusers #include "openxlsx.h" // [[Rcpp::export]] IntegerVector map_cell_types_to_integer(CharacterVector t){ // 0: "n" // 1: "s" // 2: "b" // 3: "str" // 4: "e" // 9: "h" size_t n = t.size(); IntegerVector t_res(n); for(size_t i = 0; i < n; i++){ if(CharacterVector::is_na(t[i])){ t_res[i] = NA_INTEGER; }else if(t[i] == "n"){ t_res[i] = 0; }else if(t[i] == "s"){ t_res[i] = 1; }else if(t[i] == "b"){ t_res[i] = 2; }else if(t[i] == "str"){ t_res[i] = 3; }else if(t[i] == "e"){ t_res[i] = 4; }else if(t[i] == "inlineStr"){ t_res[i] = 5; } } return t_res; } // [[Rcpp::export]] CharacterVector map_cell_types_to_char(IntegerVector t){ // 0: "n" // 1: "s" // 2: "b" // 3: "str" // 4: "e" // 9: "h" size_t n = t.size(); CharacterVector t_res(n); for(size_t i = 0; i < n; i++){ if(IntegerVector::is_na(t[i])){ t_res[i] = NA_STRING; }else if(t[i] == 0){ t_res[i] = "n"; }else if(t[i] == 1){ t_res[i] = "s"; }else if(t[i] == 2){ t_res[i] = "b"; }else if(t[i] == 3){ t_res[i] = "str"; }else if(t[i] == 4){ t_res[i] = "e"; }else if(t[i] == 5){ t_res[i] = "inlineStr"; }else{ t_res[i] = "s"; } } return t_res; } // [[Rcpp::export]] IntegerVector build_cell_types_integer(CharacterVector classes, int n_rows){ // 0: "n" // 1: "s" // 2: "b" // 9: "h" // 4: TBC // 5: TBC size_t n_cols = classes.size(); IntegerVector col_t(n_cols); for(size_t i = 0; i < n_cols; i++){ if((classes[i] == "numeric") | (classes[i] == "integer") | (classes[i] == "raw") ){ col_t[i] = 0; }else if(classes[i] == "character"){ col_t[i] = 1; }else if(classes[i] == "logical"){ col_t[i] = 2; }else if(classes[i] == "hyperlink"){ col_t[i] = 9; }else if(classes[i] == "openxlsx_formula"){ col_t[i] = NA_INTEGER; }else{ col_t[i] = 1; } } IntegerVector cell_types = rep(col_t, n_rows); return cell_types; } // [[Rcpp::export]] CharacterVector buildCellTypes(CharacterVector classes, int nRows){ int nCols = classes.size(); CharacterVector colLabels(nCols); for(int i=0; i < nCols; i++){ if((classes[i] == "numeric") | (classes[i] == "integer") | (classes[i] == "raw") ){ colLabels[i] = "n"; }else if(classes[i] == "character"){ colLabels[i] = "s"; }else if(classes[i] == "logical"){ colLabels[i] = "b"; }else if(classes[i] == "hyperlink"){ colLabels[i] = "h"; }else if(classes[i] == "openxlsx_formula"){ colLabels[i] = NA_STRING; }else{ colLabels[i] = "s"; } } CharacterVector cellTypes = rep(colLabels, nRows); return wrap(cellTypes); } // [[Rcpp::export]] List build_cell_merges(List comps){ size_t nMerges = comps.size(); List res(nMerges); for(size_t i =0; i < nMerges; i++){ IntegerVector col = convert_from_excel_ref(comps[i]); CharacterVector comp = comps[i]; IntegerVector row(2); for(size_t j = 0; j < 2; j++){ std::string rt(comp[j]); rt.erase(std::remove_if(rt.begin(), rt.end(), ::isalpha), rt.end()); row[j] = atoi(rt.c_str()); } size_t ca(col[0]); size_t ck = size_t(col[1]) - ca + 1; std::vector v(ck) ; for(size_t j = 0; j < ck; j++) v[j] = j + ca; size_t ra(row[0]); size_t rk = int(row[1]) - ra + 1; std::vector r(rk) ; for(size_t j = 0; j < rk; j++) r[j] = j + ra; CharacterVector M(ck*rk); int ind = 0; for(size_t j = 0; j < ck; j++){ for(size_t k = 0; k < rk; k++){ char name[30]; sprintf(&(name[0]), "%d-%d", r[k], v[j]); M(ind) = name; ind++; } } res[i] = M; } return wrap(res) ; } // [[Rcpp::export]] List buildCellList( CharacterVector r, CharacterVector t, CharacterVector v) { //Valid combinations // r t v // T F F // T T T // F F F // T F T (must be a formula) int n = r.size(); List cells(n); LogicalVector hasV = !is_na(v); LogicalVector hasR = !is_na(r); LogicalVector hasT = !is_na(t); for(int i=0; i < n; i++){ if(hasR[i]){ if(hasV[i]){ if(hasT[i]){ // r t v // T T T (2) cells[i] = CharacterVector::create( Named("r") = r[i], Named("t") = t[i], Named("v") = v[i], Named("f") = NA_STRING); }else{ // r t f // T T T (4 - formula) cells[i] = CharacterVector::create( Named("r") = r[i], Named("t") = "str", Named("v") = NA_STRING, Named("f") = "" + v[i] + ""); } }else{ // r t v // T F F (1) cells[i] = CharacterVector::create( Named("r") = r[i], Named("t") = NA_STRING, Named("v") = NA_STRING, Named("f") = NA_STRING); } }else{ // r t v // F F F (3) cells[i] = CharacterVector::create( Named("r") = NA_STRING, Named("t") = NA_STRING, Named("v") = NA_STRING, Named("f") = NA_STRING); } } // end of for loop return wrap(cells) ; } openxlsx/src/openxlsx.h0000644000176200001440000000563014155600364014762 0ustar liggesusers #include #include #include #include using namespace Rcpp; using namespace std; // load workbook int cell_ref_to_col(string ); CharacterVector int_2_cell_ref(IntegerVector); // write file SEXP write_worksheet_xml(string, string, List, Reference, IntegerVector, CharacterVector, string ); // write_data.cpp CharacterVector map_cell_types_to_char(IntegerVector); IntegerVector map_cell_types_to_integer(CharacterVector); std::vector get_letters(); IntegerVector convert_from_excel_ref( CharacterVector x ); SEXP calc_column_widths(Reference sheet_data, std::vector sharedStrings, IntegerVector autoColumns, NumericVector widths, float baseFontCharWidth, float minW, float maxW); SEXP getOpenClosedNode(std::string xml, std::string open_tag, std::string close_tag); std::string cppReadFile(std::string xmlFile); std::string read_file_newline(std::string xmlFile); SEXP getNodes(std::string xml, std::string tagIn); std::vector getChildlessNode_ss(std::string xml, std::string tag); CharacterVector get_extLst_Major(std::string xml); CharacterVector getChildlessNode(std::string xml, std::string tag); SEXP getAttr(CharacterVector x, std::string tag); CharacterVector get_shared_strings(std::string xmlFile, bool isFile); List buildCellList( CharacterVector r, CharacterVector t, CharacterVector v); SEXP openxlsx_convert_to_excel_ref(IntegerVector cols, std::vector LETTERS); SEXP buildMatrixNumeric(CharacterVector v, IntegerVector rowInd, IntegerVector colInd, CharacterVector colNames, int nRows, int nCols); SEXP buildMatrixMixed(CharacterVector v, IntegerVector rowInd, IntegerVector colInd, CharacterVector colNames, int nRows, int nCols, IntegerVector charCols, IntegerVector dateCols); List getCellInfo(std::string xmlFile, CharacterVector sharedStrings, bool skipEmptyRows, int startRow, IntegerVector rows, bool getDates); SEXP convert_to_excel_ref_expand(const std::vector& cols, const std::vector& LETTERS, const std::vector& rows); IntegerVector matrixRowInds(IntegerVector indices); CharacterVector build_table_xml(std::string table, std::string ref, std::vector colNames, bool showColNames, std::string tableStyle, bool withFilter); int calc_number_rows(CharacterVector x, bool skipEmptyRows); CharacterVector buildCellTypes(CharacterVector classes, int nRows); LogicalVector isInternalHyperlink(CharacterVector x); // helper functions string itos(int i); SEXP write_file(std::string parent, std::string xmlText, std::string parentEnd, std::string R_fileName); CharacterVector markUTF8(CharacterVector x, bool clone = false); openxlsx/src/helper_functions.cpp0000644000176200001440000001467414155600364017014 0ustar liggesusers #include "openxlsx.h" // [[Rcpp::export]] SEXP calc_column_widths(Reference sheet_data , std::vector sharedStrings , IntegerVector autoColumns , NumericVector widths , float baseFontCharWidth , float minW , float maxW){ int n = sheet_data.field("n_elements"); IntegerVector cell_types = sheet_data.field("t"); StringVector cell_values(sheet_data.field("v")); IntegerVector cell_cols = sheet_data.field("cols"); NumericVector cell_n_character(n); CharacterVector r(n); int nLen; std::string tmp; // get widths of all values for(int i = 0; i < n; i++){ if(cell_types[i] == 1){ // "s" cell_n_character[i] = sharedStrings[atoi(cell_values[i])].length() - 37; //-37 for shared string tags around text }else{ tmp = cell_values[i]; nLen = tmp.length(); cell_n_character[i] = min(nLen, 11); // For numerics - max width is 11 } } // get column for each value // reducing to only the columns that are auto LogicalVector notNA = !is_na(match(cell_cols, autoColumns)); cell_cols = cell_cols[notNA]; cell_n_character = cell_n_character[notNA]; widths = widths[notNA]; IntegerVector unique_cell_cols = sort_unique(cell_cols); size_t k = unique_cell_cols.size(); NumericVector column_widths(k); // for each unique column, get all widths for that column and take max for(size_t i = 0; i < k; i++){ NumericVector wTmp = cell_n_character[cell_cols == unique_cell_cols[i]]; NumericVector thisColWidths = widths[cell_cols == unique_cell_cols[i]]; column_widths[i] = max(wTmp * thisColWidths / baseFontCharWidth); } column_widths[column_widths < minW] = minW; column_widths[column_widths > maxW] = maxW; // assign column names column_widths.attr("names") = unique_cell_cols; return(wrap(column_widths)); } // [[Rcpp::export]] SEXP convert_to_excel_ref(IntegerVector cols, std::vector LETTERS){ int n = cols.size(); CharacterVector res(n); int x; int modulo; for(int i = 0; i < n; i++){ x = cols[i]; string columnName; while(x > 0){ modulo = (x - 1) % 26; columnName = LETTERS[modulo] + columnName; x = (x - modulo) / 26; } res[i] = columnName; } return res ; } // [[Rcpp::export]] IntegerVector convert_from_excel_ref( CharacterVector x ){ // This function converts the Excel column letter to an integer std::vector r = as >(x); int n = r.size(); int k; std::string a; IntegerVector colNums(n); char A = 'A'; int aVal = (int)A - 1; for(int i = 0; i < n; i++){ a = r[i]; // remove digits from string a.erase(std::remove_if(a.begin()+1, a.end(), ::isdigit), a.end()); int sum = 0; k = a.length(); for (int j = 0; j < k; j++){ sum *= 26; sum += (a[j] - aVal); } colNums[i] = sum; } return colNums; } // [[Rcpp::export]] SEXP convert_to_excel_ref_expand(const std::vector& cols, const std::vector& LETTERS, const std::vector& rows){ int n = cols.size(); int nRows = rows.size(); std::vector res(n); //Convert col number to excel col letters size_t x; size_t modulo; for(int i = 0; i < n; i++){ x = cols[i]; string columnName; while(x > 0){ modulo = (x - 1) % 26; columnName = LETTERS[modulo] + columnName; x = (x - modulo) / 26; } res[i] = columnName; } CharacterVector r(n*nRows); CharacterVector names(n*nRows); size_t c = 0; for(int i=0; i < nRows; i++) for(int j=0; j < n; j++){ r[c] = res[j] + rows[i]; names[c] = rows[i]; c++; } r.attr("names") = names; return wrap(r) ; } // [[Rcpp::export]] LogicalVector isInternalHyperlink(CharacterVector x){ int n = x.size(); std::string xml; std::string tag = "r:id="; size_t found; LogicalVector isInternal(n); for(int i = 0; i < n; i++){ // find location tag xml = x[i]; found = xml.find(tag, 0); if (found != std::string::npos){ isInternal[i] = false; }else{ isInternal[i] = true; } } return wrap(isInternal) ; } string itos(int i){ // convert int to string stringstream s; s << i; return s.str(); } // [[Rcpp::export]] SEXP write_file(std::string head = "", std::string body = "", std::string tail = "", std::string fl = "") { const char * s = fl.c_str(); std::ofstream xmlFile; xmlFile.open (s); xmlFile << ""; xmlFile << head; xmlFile << body; xmlFile << tail; xmlFile.close(); return R_NilValue; } // [[Rcpp::export]] std::string cppReadFile(std::string xmlFile){ std::string buf; std::string xml; ifstream file; file.open(xmlFile.c_str()); while (file >> buf) xml += buf + ' '; return xml; } // [[Rcpp::export]] std::string read_file_newline(std::string xmlFile){ ifstream file; file.open(xmlFile.c_str()); std::vector lines; std::string line; while ( std::getline(file, line) ) { // skip empty lines: if (line.empty()) continue; lines.push_back(line); } line = ""; int n = lines.size(); for(int i = 0;i < n; ++i) line += lines[i] + "\n"; return line; } // [[Rcpp::export]] std::vector get_letters(){ std::vector LETTERS(26); LETTERS[0] = "A"; LETTERS[1] = "B"; LETTERS[2] = "C"; LETTERS[3] = "D"; LETTERS[4] = "E"; LETTERS[5] = "F"; LETTERS[6] = "G"; LETTERS[7] = "H"; LETTERS[8] = "I"; LETTERS[9] = "J"; LETTERS[10] = "K"; LETTERS[11] = "L"; LETTERS[12] = "M"; LETTERS[13] = "N"; LETTERS[14] = "O"; LETTERS[15] = "P"; LETTERS[16] = "Q"; LETTERS[17] = "R"; LETTERS[18] = "S"; LETTERS[19] = "T"; LETTERS[20] = "U"; LETTERS[21] = "V"; LETTERS[22] = "W"; LETTERS[23] = "X"; LETTERS[24] = "Y"; LETTERS[25] = "Z"; return(LETTERS); } // [[Rcpp::export]] CharacterVector markUTF8(CharacterVector x, bool clone) { CharacterVector out; if (clone) { out = Rcpp::clone(x); } else { out = x; } const size_t n = x.size(); for (size_t i = 0; i < n; ++i) { out[i] = Rf_mkCharCE(x[i], CE_UTF8); } return out; } openxlsx/src/load_workbook.cpp0000644000176200001440000007554014155600364016300 0ustar liggesusers #include "openxlsx.h" // [[Rcpp::export]] SEXP loadworksheets(Reference wb, List styleObjects, std::vector xmlFiles, LogicalVector is_chart_sheet){ List worksheets = wb.field("worksheets"); int n_sheets = is_chart_sheet.size(); CharacterVector sheetNames = wb.field("sheet_names"); // variable set up std::string tagEnd = "\""; std::string cell; List colWidths(n_sheets); List rowHeights(n_sheets); List wbstyleObjects(0); List outlineLevels(n_sheets); List colOutlineLevels(n_sheets); // loop over each worksheet file for(int i = 0; i < n_sheets; i++){ if(is_chart_sheet[i]){ colWidths[i] = List(0); rowHeights[i] = List(0); outlineLevels[i] = List(0); colOutlineLevels[i] = List(0); }else{ colWidths[i] = List(0); rowHeights[i] = List(0); outlineLevels[i] = List(0); colOutlineLevels[i] = List(0); Reference this_worksheet(worksheets[i]); Reference sheet_data(this_worksheet.field("sheet_data")); //read in file std::string xmlFile = xmlFiles[i]; std::string buf; std::string xml = read_file_newline(xmlFile); // ifstream file; // file.open(xmlFile.c_str()); // while (file >> buf) // xml += buf + ' '; size_t pos = xml.find(""); // find size_t endPos = 0; size_t tmp_pos = 0; bool has_data = true; if(pos == string::npos){ has_data = false; pos = xml.find(""); if(pos == string::npos){ pos = xml.find(""); } } /* --- Everything before pos --- */ std::string xml_pre = xml.substr(0, pos); // sheetPR CharacterVector sheetPr = getNodes(xml_pre, ""); if(sheetPr.size() == 0){ sheetPr = getNodes(xml_pre, "(sheetPr[j]); char ch = *sp.rbegin(); if(ch != '>') sp += ">"; sheetPr[j] = sp; } } if(sheetPr.size() == 0) sheetPr = getChildlessNode(xml_pre, "sheetPr"); if(sheetPr.size() > 0) this_worksheet.field("sheetPr") = sheetPr; // Freeze Panes CharacterVector node_xml = getChildlessNode(xml_pre, "pane"); if(node_xml.size() > 0) this_worksheet.field("freezePane") = node_xml; // SheetViews node_xml = getNodes(xml_pre, ""); if(node_xml.size() > 0) this_worksheet.field("sheetViews") = node_xml; //colwidths std::vector cols = getChildlessNode_ss(xml_pre, " 0){ NumericVector widths; IntegerVector columns_with_widths; IntegerVector columns_with_groups; CharacterVector column_hidden; CharacterVector col_outline; CharacterVector col_hidden; for (size_t ci = 0; ci < cols.size(); ci++) { double tmp_width = 0; std::string tmp_hidden; int min_c = 0; int max_c = 0; std::string tmp_coloutline; buf = cols[ci]; // If either custom widths or groupings, get column index if ((buf.find("customWidth", 0) != string::npos) | (buf.find("outlineLevel", 0) != string::npos)) { tmp_pos = buf.find("min=\"", 0); endPos = buf.find(tagEnd, tmp_pos + 5); min_c = atoi(buf.substr(tmp_pos + 5, endPos - tmp_pos - 5).c_str()); tmp_pos = buf.find("max=\"", 0); endPos = buf.find(tagEnd, tmp_pos + 5); max_c = atoi(buf.substr(tmp_pos + 5, endPos - tmp_pos - 5).c_str()); tmp_pos = buf.find("hidden=\"", 0); if (tmp_pos != string::npos) { endPos = buf.find(tagEnd, tmp_pos + 8); tmp_hidden = buf.substr(tmp_pos + 8, endPos - tmp_pos - 8); } else { tmp_hidden = "0"; } // If column has both a custom width and is part of a group if ((buf.find("customWidth", 0) != string::npos) & (buf.find("outlineLevel", 0) != string::npos)) { tmp_pos = buf.find("width=\"", 0); endPos = buf.find(tagEnd, tmp_pos + 7); tmp_width = atof(buf.substr(tmp_pos + 7, endPos - tmp_pos - 7).c_str()) - 0.71; tmp_pos = buf.find("outlineLevel=\"", 0); endPos = buf.find(tagEnd, tmp_pos + 14); tmp_coloutline = buf.substr(tmp_pos + 14, endPos - tmp_pos - 14); while (min_c <= max_c) { widths.push_back(tmp_width); columns_with_widths.push_back(min_c); columns_with_groups.push_back(min_c); column_hidden.push_back(tmp_hidden); col_hidden.push_back(tmp_hidden); col_outline.push_back(tmp_coloutline); min_c++; } } else if (buf.find("customWidth", 0) != string::npos) { // Column only has a custom width tmp_pos = buf.find("width=\"", 0); endPos = buf.find(tagEnd, tmp_pos + 7); tmp_width = atof(buf.substr(tmp_pos + 7, endPos - tmp_pos - 7).c_str()) - 0.71; while (min_c <= max_c) { widths.push_back(tmp_width); columns_with_widths.push_back(min_c); column_hidden.push_back(tmp_hidden); min_c++; } } else { // Column is only part of a group tmp_pos = buf.find("outlineLevel=\"", 0); endPos = buf.find(tagEnd, tmp_pos + 14); tmp_coloutline = buf.substr(tmp_pos + 14, endPos - tmp_pos - 14); while (min_c <= max_c) { columns_with_groups.push_back(min_c); col_hidden.push_back(tmp_hidden); col_outline.push_back(tmp_coloutline); min_c++; } } } } if (widths.size() > 0) { CharacterVector tmp_widths(widths); tmp_widths.attr("names") = columns_with_widths; tmp_widths.attr("hidden") = column_hidden; colWidths[i] = tmp_widths; } if (col_outline.size() > 0) { CharacterVector columns_outline(col_outline); columns_outline.attr("names") = columns_with_groups; columns_outline.attr("hidden") = col_hidden; colOutlineLevels[i] = columns_outline; } } /* --- Everything after sheetData --- */ size_t pos_post = 0; if(has_data){ pos_post = xml.find(""); }else{ pos_post = pos; } std::string xml_post = xml.substr(pos_post); node_xml = getChildlessNode(xml_post, "sheetProtection"); if(node_xml.size() > 0) { this_worksheet.field("sheetProtection") = node_xml; } node_xml = getChildlessNode(xml_post, "autoFilter"); if(node_xml.size() > 0) this_worksheet.field("autoFilter") = node_xml; node_xml = getChildlessNode(xml_post, "hyperlink"); if(node_xml.size() > 0) this_worksheet.field("hyperlinks") = node_xml; node_xml = getChildlessNode(xml_post, "pageMargins"); if(node_xml.size() > 0) this_worksheet.field("pageMargins") = node_xml; node_xml = getChildlessNode(xml_post, "pageSetup"); if(node_xml.size() > 0){ for(int j = 0; j < node_xml.size(); j++){ std::string pageSetup_tmp = as(node_xml[j]); size_t ps_pos = pageSetup_tmp.find("r:id=\"rId", 0); if(ps_pos != std::string::npos){ std::string pageSetup_tmp2 = pageSetup_tmp.substr(0, ps_pos + 9) + "2"; ps_pos = pageSetup_tmp.find("\"", ps_pos + 9); pageSetup_tmp = pageSetup_tmp2 + pageSetup_tmp.substr(ps_pos); } node_xml[j] = pageSetup_tmp; } this_worksheet.field("pageSetup") = node_xml; } node_xml = getChildlessNode(xml_post, "mergeCell"); if(node_xml.size() > 0) this_worksheet.field("mergeCells") = node_xml; node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) this_worksheet.field("oleObjects") = node_xml; // headerfooter CharacterVector xml_hf = getNodes(xml_post, " 0){ List hf = List(0); node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) hf["oddHeader"] = node_xml; node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) hf["oddFooter"] = node_xml; node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) hf["evenHeader"] = node_xml; node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) hf["evenFooter"] = node_xml; node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) hf["firstHeader"] = node_xml; node_xml = getNodes(xml_post, ""); if(node_xml.size() > 0) hf["firstFooter"] = node_xml; this_worksheet.field("headerFooter") = hf; } node_xml = getChildlessNode(xml_post, "drawing"); if(node_xml.size() == 0) node_xml = getChildlessNode(xml_post, "legacyDrawing"); if(node_xml.size() > 0){ for(int j = 0; j < node_xml.size(); j++){ std::string drawingId_tmp = as(node_xml[j]); size_t ps_pos = drawingId_tmp.find("r:id=\"rId", 0); std::string drawingId_tmp2 = drawingId_tmp.substr(0, ps_pos + 9) + "1"; ps_pos = drawingId_tmp.find("\"", ps_pos + 9); drawingId_tmp = drawingId_tmp2 + drawingId_tmp.substr(ps_pos); node_xml[j] = drawingId_tmp; } } // conditionalFormatting CharacterVector conForm = getNodes(xml_post, " 0){ // get sqref attribute size_t tmp_pos = 0; int end_pos = 0; std::string sqref; CharacterVector cf; CharacterVector cf_names; for(int ci = 0; ci < conForm.size(); ci++){ buf = conForm[ci]; tmp_pos = buf.find("sqref=\"", 0); end_pos = buf.find("\"", tmp_pos + 7); sqref = buf.substr(tmp_pos + 7, end_pos - tmp_pos - 7); buf = buf.substr(0, buf.find(" 1){ tmp_pos = buf.find(" 0) //data validation node_xml = getOpenClosedNode(xml_post, ""); if(node_xml.size() > 0) this_worksheet.field("dataValidations") = node_xml; // extLst node_xml = get_extLst_Major(xml_post); if(node_xml.size() > 0) this_worksheet.field("extLst") = node_xml; // clean pre and post xml xml_post.clear(); xml_pre.clear(); /* --------------------------- sheet Data --------------------------- */ if(has_data){ xml = xml.substr(pos + 11, pos_post - pos - 11); // get from "" to the end // count cells with children int ocs = 0; string::size_type start = 0; while((start = xml.find(" tag and end tag endPos = cell.find("", 0); if(endPos != std::string::npos){ pos = cell.find("", pos); v[j] = cell.substr(pos + 1, endPos - pos - 1); has_v = true; } // find tag and end tag endPos = cell.find("", 0); if(endPos != std::string::npos){ pos = cell.find("", pos); v[j] = cell.substr(pos + 4, endPos - pos - 4); // skip and ", pos_f + 3); if(endPos == std::string::npos){ endPos = cell.find("/>", pos_f + 3); f[j] = cell.substr(pos_f, endPos - pos_f + 2); }else{ f[j] = cell.substr(pos_f, endPos - pos_f + 4); } has_f = true; // do we really have t if(pos_t < pos_f){ endPos = cell.find(tagEnd, pos_t + 4); // find next " t[j] = cell.substr(pos_t + 4, endPos - pos_t - 4); } }else if(pos_t != std::string::npos){ // only have t endPos = cell.find(tagEnd, pos_t + 4); // find next " t[j] = cell.substr(pos_t + 4, endPos - pos_t - 4); }else if(pos_f != std::string::npos){ // only have f endPos = cell.find("", pos_f + 3); if(endPos == std::string::npos){ endPos = cell.find("/>", pos_f + 3); f[j] = cell.substr(pos_f, endPos - pos_f + 2); }else{ f[j] = cell.substr(pos_f, endPos - pos_f + 4); } has_f = true; } if(has_f & (!has_v) & (t[j] != "n")){ v[j] = NA_STRING; }else if(has_f & !has_v){ t[j] = NA_STRING; v[j] = NA_STRING; }else if(has_f | has_v){ }else{ //only have s and r t[j] = NA_STRING; v[j] = NA_STRING; } j++; // INCREMENT OVER OCCURENCES pos = nextPos; pos_t = nextPos; pos_f = nextPos; } // end of while loop over occurences } // END OF CELL AND ATTRIBUTION GATHERING // get names of cells if(ocs > 0){ // may be a problem when we have a formula, no value and we now write t="n" in it's place sheet_data.field("rows") = rows_cell_ref; sheet_data.field("cols") = cols_cell_ref; sheet_data.field("t") = map_cell_types_to_integer(t); sheet_data.field("v") = v; sheet_data.field("f") = f; sheet_data.field("data_count") = 1; sheet_data.field("n_elements") = ocs; } // count number of rows int row_ocs = 0; start = 0; while((start = xml.find(" 0) { heights = heights[!is_na(heights)]; heights.attr("names") = heightsRows; rowHeights[i] = heights; } outlineRows = outlineRows[!is_na(outlines)]; if (outlineRows.size() > 0) { outline_hidden = outline_hidden[!is_na(outline_hidden)]; outlines = outlines[!is_na(outlines)]; outlines.attr("names") = outlineRows; outlines.attr("hidden") = outline_hidden; outlineLevels[i] = outlines; } // styleObjects std::string this_sheetname = as(sheetNames[i]); if(any(!is_na(s))){ CharacterVector s_refs = r[!is_na(s)]; s = s[!is_na(s)]; CharacterVector uStyleInds = sort_unique(s); int nsu = uStyleInds.size(); CharacterVector uStyleInds_j(1); std::string ref_j; CharacterVector styleElementNames = CharacterVector::create("style", "sheet", "rows", "cols"); for(int j = 0; j < nsu; j++){ List styleElement(4); int styleInd = atoi(as(uStyleInds[j]).c_str()); if(styleInd != 0){ uStyleInds_j[0] = uStyleInds[j]; LogicalVector ind = !is_na(match(s, uStyleInds_j)); CharacterVector s_refs_j = s_refs[ind]; int n_j = s_refs_j.size(); IntegerVector rows(n_j); IntegerVector cols = convert_from_excel_ref(s_refs_j); for(int k = 0; k < n_j; k++){ ref_j = s_refs_j[k]; ref_j.erase(std::remove_if(ref_j.begin(), ref_j.end(), ::isalpha), ref_j.end()); rows[k] = atoi(ref_j.c_str()); } styleElement[0] = styleObjects[styleInd - 1]; styleElement[1] = this_sheetname; styleElement[2] = rows; styleElement[3] = cols; styleElement.attr("names") = styleElementNames; wbstyleObjects.push_back(styleElement); } } } // end if(any(!is_na(s))) } // end of if(has_data) } // end if is_chart_sheet[i] else } // end of loop over sheets // assign back to workbook wb.field("worksheets") = worksheets; wb.field("rowHeights") = rowHeights; wb.field("colWidths") = colWidths; wb.field("styleObjects") = wbstyleObjects; wb.field("outlineLevels") = outlineLevels; wb.field("colOutlineLevels") = colOutlineLevels; return wrap(wb); } // [[Rcpp::export]] SEXP getNodes(std::string xml, std::string tagIn){ // This function loops over all characters in xml, looking for tag // tag should look liked // tagEnd is then generated to be if(xml.length() == 0) return wrap(NA_STRING); xml = " " + xml; std::vector r; size_t pos = 0; size_t endPos = 0; std::string tag = tagIn; std::string tagEnd = tagIn.insert(1,"/"); size_t k = tag.length(); size_t l = tagEnd.length(); while(1){ pos = xml.find(tag, pos+1); endPos = xml.find(tagEnd, pos+k); if((pos == std::string::npos) | (endPos == std::string::npos)) break; r.push_back(xml.substr(pos, endPos-pos+l).c_str()); } CharacterVector out = wrap(r); return markUTF8(out); } // [[Rcpp::export]] SEXP getOpenClosedNode(std::string xml, std::string open_tag, std::string close_tag){ if(xml.length() == 0) return wrap(NA_STRING); xml = " " + xml; size_t pos = 0; size_t endPos = 0; size_t k = open_tag.length(); size_t l = close_tag.length(); std::vector r; while(1){ pos = xml.find(open_tag, pos+1); endPos = xml.find(close_tag, pos+k); if((pos == std::string::npos) | (endPos == std::string::npos)) break; r.push_back(xml.substr(pos, endPos-pos+l).c_str()); } CharacterVector out = wrap(r); return markUTF8(out); } // [[Rcpp::export]] SEXP getAttr(CharacterVector x, std::string tag){ size_t n = x.size(); size_t k = tag.length(); if(n == 0) return wrap(-1); std::string xml; CharacterVector r(n); size_t pos = 0; size_t endPos = 0; std::string rtagEnd = "\""; for(size_t i = 0; i < n; i++){ // find opening tag xml = x[i]; pos = xml.find(tag, 0); if(pos == std::string::npos){ r[i] = NA_STRING; }else{ endPos = xml.find(rtagEnd, pos+k); r[i] = xml.substr(pos+k, endPos-pos-k).c_str(); } } return markUTF8(r); // no need to wrap as r is already a CharacterVector } // [[Rcpp::export]] std::vector getChildlessNode_ss(std::string xml, std::string tag){ size_t k = tag.length(); std::vector r; size_t pos = 0; size_t endPos = 0; std::string tagEnd = "/>"; while(1){ pos = xml.find(tag, pos+1); if(pos == std::string::npos) break; endPos = xml.find(tagEnd, pos+k); r.push_back(xml.substr(pos, endPos-pos+2).c_str()); } return r ; } // [[Rcpp::export]] CharacterVector getChildlessNode(std::string xml, std::string tag) { // size_t k = tag.length(); variable not used if(xml.length() == 0) return wrap(NA_STRING); size_t begPos = 0, endPos = 0; std::vector r; std::string res = ""; // check " while( begPos != std::string::npos ) { endPos = xml.find(endTag, begPos); if(begPos == std::string::npos || endPos == std::string::npos) break; res = xml.substr(begPos, (endPos - begPos) + endTag.length()); if (res.length() == 0) break; auto itr = 0; // check if we have either , , or . We have to avoid // while ( res.substr(begTag.length(),1).compare(" ") != 0 && // res.substr(begTag.length(),1).compare("/") != 0 && // res.substr(begTag.length(),1).compare(">") != 0 // ) { if (itr == 0) begPos = begPos + begTag.length(); if(begPos == std::string::npos || endPos == std::string::npos) break; Rcpp::checkUserInterrupt(); begPos = xml.find(begTag, begPos); endPos = xml.find(endTag, begPos); if(begPos == std::string::npos || endPos == std::string::npos) break; res = xml.substr(begPos, (endPos - begPos) + endTag.length()); ++itr; } // if we have we need to find the matching closing tag bool closingtag = false; if (res.substr( res.length() - 2 ).compare("/>") != 0) { // this node has temp_endTag = ""; closingtag = true; } else { temp_endTag = endTag; } // if we have a closing tag, we need to reposition the endPos. Previously // it was at the end of . Now we search for if (closingtag) { endPos = xml.find(temp_endTag, begPos); if(begPos == std::string::npos || endPos == std::string::npos) break; // read from initial "<" to final ">" res = xml.substr(begPos, (endPos - begPos) + temp_endTag.length()); if (res.length() == 0) break; } if(begPos == std::string::npos || endPos == std::string::npos) break; begPos = endPos + temp_endTag.length(); begPos = xml.find(begTag, begPos); r.push_back(res); if(begPos == std::string::npos || endPos == std::string::npos) break; } CharacterVector out = wrap(r); return markUTF8(out); } // [[Rcpp::export]] CharacterVector get_extLst_Major(std::string xml){ // find page margin or pagesetup then take the extLst after that if(xml.length() == 0) return wrap(NA_STRING); std::vector r; std::string tagEnd = ""; size_t endPos = 0; std::string node; size_t pos = xml.find("", 0); if(pos == std::string::npos) return wrap(NA_STRING); while(1){ pos = xml.find("", pos + 1); if(pos == std::string::npos) break; endPos = xml.find(tagEnd, pos + 8); node = xml.substr(pos + 8, endPos - pos - 8); //pos = xml.find("conditionalFormattings", pos + 1); //if(pos == std::string::npos) // break; r.push_back(node.c_str()); } CharacterVector out = wrap(r); return markUTF8(out); } // [[Rcpp::export]] int cell_ref_to_col( std::string x ){ // This function converts the Excel column letter to an integer char A = 'A'; int a_value = (int)A - 1; int sum = 0; // remove digits from string x.erase(std::remove_if(x.begin()+1, x.end(), ::isdigit),x.end()); int k = x.length(); for (int j = 0; j < k; j++){ sum *= 26; sum += (x[j] - a_value); } return sum; } // [[Rcpp::export]] CharacterVector int_2_cell_ref(IntegerVector cols){ std::vector LETTERS = get_letters(); int n = cols.size(); CharacterVector res(n); std::fill(res.begin(), res.end(), NA_STRING); int x; int modulo; for(int i = 0; i < n; i++){ if(!IntegerVector::is_na(cols[i])){ string columnName; x = cols[i]; while(x > 0){ modulo = (x - 1) % 26; columnName = LETTERS[modulo] + columnName; x = (x - modulo) / 26; } res[i] = columnName; } } return res ; } openxlsx/src/write_file.cpp0000644000176200001440000002074114155600364015566 0ustar liggesusers #include "openxlsx.h" //' @import Rcpp // [[Rcpp::export]] SEXP write_worksheet_xml(std::string prior , std::string post , Reference sheet_data , std::string R_fileName){ // open file and write header XML const char * s = R_fileName.c_str(); std::ofstream xmlFile; xmlFile.open (s); xmlFile << ""; xmlFile << prior; //NOTES ON WHY THIS WORKS // - If no row heights every row has children // - If data only added once, sheet_data will be in order // - Thus all rows have children (all starting row tags are open) and no need to sort/find // DEV START IntegerVector cell_row = sheet_data.field("rows"); // If no data write childless node and return if(cell_row.size() == 0){ xmlFile << ""; xmlFile << post; xmlFile.close(); return Rcpp::wrap(0); } CharacterVector cell_col = int_2_cell_ref(sheet_data.field("cols")); CharacterVector cell_types = map_cell_types_to_char(sheet_data.field("t")); CharacterVector cell_value = sheet_data.field("v"); CharacterVector cell_fn = sheet_data.field("f"); CharacterVector style_id = sheet_data.field("style_id"); CharacterVector unique_rows(sort_unique(cell_row)); size_t n = cell_row.size(); size_t k = unique_rows.size(); std::string xml; std::string cell_xml; // write sheet_data // write xml prior to sheetData and opening tag xmlFile << ""; size_t j = 0; String currentRow = unique_rows[0]; for(size_t i = 0; i < k; i++){ cell_xml = ""; while(currentRow == unique_rows[i]){ //cell XML strings cell_xml += "(cell_types[j]).compare("inlineStr") == 0){ cell_xml += "\" t=\"" + cell_types[j] + "\">" + "" + cell_value[j] + ""; }else{ cell_xml += "\" t=\"" + cell_types[j] + "\">" + cell_value[j] + ""; } }else{ if(CharacterVector::is_na(cell_value[j])){ // If v is NA cell_xml += "\" t=\"" + cell_types[j] + "\">" + cell_fn[j] + ""; }else{ cell_xml += "\" t=\"" + cell_types[j] + "\">" + cell_fn[j] + "" + cell_value[j] + ""; } } }else{ cell_xml += "\"/>"; } j += 1; if(j == n) break; currentRow = cell_row[j]; } xmlFile << "" + cell_xml + ""; } // write closing tag, post xml and close xmlFile << ""; xmlFile << post; xmlFile.close(); return Rcpp::wrap(0); } // [[Rcpp::export]] SEXP buildMatrixNumeric(CharacterVector v, IntegerVector rowInd, IntegerVector colInd, CharacterVector colNames, int nRows, int nCols){ LogicalVector isNA_element = is_na(v); if(is_true(any(isNA_element))){ v = v[!isNA_element]; rowInd = rowInd[!isNA_element]; colInd = colInd[!isNA_element]; } int k = v.size(); NumericMatrix m(nRows, nCols); std::fill(m.begin(), m.end(), NA_REAL); for(int i = 0; i < k; i++) m(rowInd[i], colInd[i]) = atof(v[i]); List dfList(nCols); for(int i=0; i < nCols; ++i) dfList[i] = m(_,i); std::vector rowNames(nRows); for(int i = 0;i < nRows; ++i) rowNames[i] = i+1; dfList.attr("names") = colNames; dfList.attr("row.names") = rowNames; dfList.attr("class") = "data.frame"; return Rcpp::wrap(dfList); } // [[Rcpp::export]] SEXP buildMatrixMixed(CharacterVector v, IntegerVector rowInd, IntegerVector colInd, CharacterVector colNames, int nRows, int nCols, IntegerVector charCols, IntegerVector dateCols){ /* List d(10); d[0] = v; d[1] = vn; d[2] = rowInd; d[3] = colInd; d[4] = colNames; d[5] = nRows; d[6] = nCols; d[7] = charCols; d[8] = dateCols; d[9] = originAdj; return(wrap(d)); */ int k = v.size(); std::string dt_str; // create and fill matrix CharacterMatrix m(nRows, nCols); std::fill(m.begin(), m.end(), NA_STRING); for(int i = 0;i < k; i++) m(rowInd[i], colInd[i]) = v[i]; // this will be the return data.frame List dfList(nCols); // loop over each column and check type for(int i = 0; i < nCols; i++){ CharacterVector tmp(nRows); for(int ri = 0; ri < nRows; ri++) tmp[ri] = m(ri,i); LogicalVector notNAElements = !is_na(tmp); // If column is date class and no strings exist in column if( (std::find(dateCols.begin(), dateCols.end(), i) != dateCols.end()) && (std::find(charCols.begin(), charCols.end(), i) == charCols.end()) ){ // these are all dates and no characters --> safe to convert numerics DateVector datetmp(nRows); for(int ri=0; ri < nRows; ri++){ if(!notNAElements[ri]){ datetmp[ri] = NA_REAL; //IF TRUE, TRUE else FALSE }else{ // dt_str = as(m(ri,i)); dt_str = m(ri,i); try{ datetmp[ri] = Rcpp::Date(atoi(dt_str.substr(5,2).c_str()), atoi(dt_str.substr(8,2).c_str()), atoi(dt_str.substr(0,4).c_str()) ); }catch(...) { Rcpp::Rcerr << "Error reading date:\n" << dt_str << "\nrow: " << ri+1 << "\ncol: " << i+1 << "\n"; throw; } //datetmp[ri] = Date(atoi(m(ri,i)) - originAdj); //datetmp[ri] = Date(as(m(ri,i))); } } dfList[i] = datetmp; // character columns }else if(std::find(charCols.begin(), charCols.end(), i) != charCols.end()){ // determine if column is logical or date bool logCol = true; for(int ri = 0; ri < nRows; ri++){ if(notNAElements[ri]){ if((m(ri, i) != "TRUE") & (m(ri, i) != "FALSE")){ logCol = false; break; } } } if(logCol){ LogicalVector logtmp(nRows); for(int ri=0; ri < nRows; ri++){ if(!notNAElements[ri]){ logtmp[ri] = NA_LOGICAL; //IF TRUE, TRUE else FALSE }else{ logtmp[ri] = (tmp[ri] == "TRUE"); } } dfList[i] = logtmp; }else{ dfList[i] = tmp; } }else{ // else if column NOT character class (thus numeric) NumericVector ntmp(nRows); for(int ri = 0; ri < nRows; ri++){ if(notNAElements[ri]){ ntmp[ri] = atof(m(ri, i)); }else{ ntmp[ri] = NA_REAL; } } dfList[i] = ntmp; } } std::vector rowNames(nRows); for(int i = 0;i < nRows; ++i) rowNames[i] = i+1; dfList.attr("names") = colNames; dfList.attr("row.names") = rowNames; dfList.attr("class") = "data.frame"; return wrap(dfList); } // [[Rcpp::export]] IntegerVector matrixRowInds(IntegerVector indices) { int n = indices.size(); LogicalVector notDup = !duplicated(indices); IntegerVector res(n); int j = -1; for(int i =0; i < n; i ++){ if(notDup[i]) j++; res[i] = j; } return wrap(res); } // [[Rcpp::export]] CharacterVector build_table_xml(std::string table, std::string tableStyleXML, std::string ref, std::vector colNames, bool showColNames, bool withFilter){ int n = colNames.size(); std::string tableCols; table += " totalsRowShown=\"0\">"; if(withFilter) table += ""; for(int i = 0; i < n; i ++){ tableCols += ""; } tableCols = "" + tableCols + ""; table = table + tableCols + tableStyleXML + ""; CharacterVector out = wrap(table); return markUTF8(out); } openxlsx/src/write_file_2.cpp0000644000176200001440000001522214155600364016005 0ustar liggesusers #include "openxlsx.h" // [[Rcpp::export]] SEXP write_worksheet_xml_2( std::string prior , std::string post , Reference sheet_data , Nullable row_heights_ = R_NilValue , Nullable outline_levels_ = R_NilValue , std::string R_fileName = "output"){ // open file and write header XML const char * s = R_fileName.c_str(); std::ofstream xmlFile; xmlFile.open (s); xmlFile << ""; xmlFile << prior; IntegerVector cell_row = sheet_data.field("rows"); // If no data write childless node and return if(cell_row.size() == 0){ xmlFile << ""; xmlFile << post; xmlFile.close(); return Rcpp::wrap(0); } // sheet_data will be in order, just need to check for row_heights CharacterVector cell_col = int_2_cell_ref(sheet_data.field("cols")); CharacterVector cell_types = map_cell_types_to_char(sheet_data.field("t")); CharacterVector cell_value = sheet_data.field("v"); CharacterVector cell_fn = sheet_data.field("f"); CharacterVector style_id = sheet_data.field("style_id"); CharacterVector unique_rows(sort_unique(cell_row)); CharacterVector row_heights; CharacterVector row_heights_rows; size_t n_row_heights = 0; CharacterVector outline_levels; CharacterVector outline_levels_rows; CharacterVector outline_levels_hidden; size_t n_outline_levels = 0; if (row_heights_.isNotNull()) { row_heights = row_heights_; row_heights_rows = row_heights.attr("names"); n_row_heights = row_heights.size(); } if (outline_levels_.isNotNull()) { outline_levels = outline_levels_; outline_levels_rows = outline_levels.attr("names"); outline_levels_hidden = outline_levels.attr("hidden"); n_outline_levels = outline_levels.size(); } size_t n = cell_row.size(); size_t k = unique_rows.size(); std::string xml; std::string cell_xml; size_t j = 0; size_t h = 0; size_t l = 0; String current_row = unique_rows[0]; bool row_has_data = true; xmlFile << ""; for(size_t i = 0; i < k; i++){ cell_xml = ""; row_has_data = true; while(current_row == unique_rows[i]){ row_has_data = true; j += 1; if(CharacterVector::is_na(cell_col[j-1])){ //If r IS NA we have no row data we only have a rowHeight row_has_data = false; if(j == n) break; current_row = cell_row[j]; break; } //cell XML strings cell_xml += "(cell_types[j-1]).compare("inlineStr") == 0){ cell_xml += "\" t=\"" + cell_types[j-1] + "\">" + "" + cell_value[j-1] + ""; }else{ cell_xml += "\" t=\"" + cell_types[j-1] + "\">" + cell_value[j-1] + ""; } }else{ if(CharacterVector::is_na(cell_value[j-1])){ // If v is NA cell_xml += "\" t=\"" + cell_types[j-1] + "\">" + cell_fn[j-1] + ""; }else{ cell_xml += "\" t=\"" + cell_types[j-1] + "\">" + cell_fn[j-1] + "" + cell_value[j-1] + ""; } } }else if(!CharacterVector::is_na(cell_fn[j-1])){ cell_xml += "\">" + cell_fn[j-1] + ""; }else{ cell_xml += "\"/>"; } if(j == n) break; current_row = cell_row[j]; } if ((h < n_row_heights) && (!Rf_isNull(row_heights_))) { // If there are custom row heights if ((l < n_outline_levels) && (!Rf_isNull(outline_levels_))) { // If there are grouped rows if ((unique_rows[i] == row_heights_rows[h]) && (unique_rows[i] == outline_levels_rows[l]) && row_has_data) { // Row is grouped and has a custom height xmlFile << ""; h++; l++; } else if ((unique_rows[i] == outline_levels_rows[l]) && row_has_data) { xmlFile << ""; l++; } else if ((unique_rows[i] == row_heights_rows[h]) && row_has_data) { // Row has custom height xmlFile << "" + cell_xml + ""; h++; } else if (row_has_data) { // Row has data xmlFile << "" + cell_xml + ""; } else { xmlFile << ""; xmlFile << post; //close file xmlFile.close(); return wrap(0); } openxlsx/src/RcppExports.cpp0000644000176200001440000006121114155603163015723 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; #ifdef RCPP_USE_GLOBAL_ROSTREAM Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif // calc_column_widths SEXP calc_column_widths(Reference sheet_data, std::vector sharedStrings, IntegerVector autoColumns, NumericVector widths, float baseFontCharWidth, float minW, float maxW); RcppExport SEXP _openxlsx_calc_column_widths(SEXP sheet_dataSEXP, SEXP sharedStringsSEXP, SEXP autoColumnsSEXP, SEXP widthsSEXP, SEXP baseFontCharWidthSEXP, SEXP minWSEXP, SEXP maxWSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< Reference >::type sheet_data(sheet_dataSEXP); Rcpp::traits::input_parameter< std::vector >::type sharedStrings(sharedStringsSEXP); Rcpp::traits::input_parameter< IntegerVector >::type autoColumns(autoColumnsSEXP); Rcpp::traits::input_parameter< NumericVector >::type widths(widthsSEXP); Rcpp::traits::input_parameter< float >::type baseFontCharWidth(baseFontCharWidthSEXP); Rcpp::traits::input_parameter< float >::type minW(minWSEXP); Rcpp::traits::input_parameter< float >::type maxW(maxWSEXP); rcpp_result_gen = Rcpp::wrap(calc_column_widths(sheet_data, sharedStrings, autoColumns, widths, baseFontCharWidth, minW, maxW)); return rcpp_result_gen; END_RCPP } // convert_to_excel_ref SEXP convert_to_excel_ref(IntegerVector cols, std::vector LETTERS); RcppExport SEXP _openxlsx_convert_to_excel_ref(SEXP colsSEXP, SEXP LETTERSSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type cols(colsSEXP); Rcpp::traits::input_parameter< std::vector >::type LETTERS(LETTERSSEXP); rcpp_result_gen = Rcpp::wrap(convert_to_excel_ref(cols, LETTERS)); return rcpp_result_gen; END_RCPP } // convert_from_excel_ref IntegerVector convert_from_excel_ref(CharacterVector x); RcppExport SEXP _openxlsx_convert_from_excel_ref(SEXP xSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type x(xSEXP); rcpp_result_gen = Rcpp::wrap(convert_from_excel_ref(x)); return rcpp_result_gen; END_RCPP } // convert_to_excel_ref_expand SEXP convert_to_excel_ref_expand(const std::vector& cols, const std::vector& LETTERS, const std::vector& rows); RcppExport SEXP _openxlsx_convert_to_excel_ref_expand(SEXP colsSEXP, SEXP LETTERSSEXP, SEXP rowsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< const std::vector& >::type cols(colsSEXP); Rcpp::traits::input_parameter< const std::vector& >::type LETTERS(LETTERSSEXP); Rcpp::traits::input_parameter< const std::vector& >::type rows(rowsSEXP); rcpp_result_gen = Rcpp::wrap(convert_to_excel_ref_expand(cols, LETTERS, rows)); return rcpp_result_gen; END_RCPP } // isInternalHyperlink LogicalVector isInternalHyperlink(CharacterVector x); RcppExport SEXP _openxlsx_isInternalHyperlink(SEXP xSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type x(xSEXP); rcpp_result_gen = Rcpp::wrap(isInternalHyperlink(x)); return rcpp_result_gen; END_RCPP } // write_file SEXP write_file(std::string head, std::string body, std::string tail, std::string fl); RcppExport SEXP _openxlsx_write_file(SEXP headSEXP, SEXP bodySEXP, SEXP tailSEXP, SEXP flSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type head(headSEXP); Rcpp::traits::input_parameter< std::string >::type body(bodySEXP); Rcpp::traits::input_parameter< std::string >::type tail(tailSEXP); Rcpp::traits::input_parameter< std::string >::type fl(flSEXP); rcpp_result_gen = Rcpp::wrap(write_file(head, body, tail, fl)); return rcpp_result_gen; END_RCPP } // cppReadFile std::string cppReadFile(std::string xmlFile); RcppExport SEXP _openxlsx_cppReadFile(SEXP xmlFileSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xmlFile(xmlFileSEXP); rcpp_result_gen = Rcpp::wrap(cppReadFile(xmlFile)); return rcpp_result_gen; END_RCPP } // read_file_newline std::string read_file_newline(std::string xmlFile); RcppExport SEXP _openxlsx_read_file_newline(SEXP xmlFileSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xmlFile(xmlFileSEXP); rcpp_result_gen = Rcpp::wrap(read_file_newline(xmlFile)); return rcpp_result_gen; END_RCPP } // get_letters std::vector get_letters(); RcppExport SEXP _openxlsx_get_letters() { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; rcpp_result_gen = Rcpp::wrap(get_letters()); return rcpp_result_gen; END_RCPP } // markUTF8 CharacterVector markUTF8(CharacterVector x, bool clone); RcppExport SEXP _openxlsx_markUTF8(SEXP xSEXP, SEXP cloneSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type x(xSEXP); Rcpp::traits::input_parameter< bool >::type clone(cloneSEXP); rcpp_result_gen = Rcpp::wrap(markUTF8(x, clone)); return rcpp_result_gen; END_RCPP } // loadworksheets SEXP loadworksheets(Reference wb, List styleObjects, std::vector xmlFiles, LogicalVector is_chart_sheet); RcppExport SEXP _openxlsx_loadworksheets(SEXP wbSEXP, SEXP styleObjectsSEXP, SEXP xmlFilesSEXP, SEXP is_chart_sheetSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< Reference >::type wb(wbSEXP); Rcpp::traits::input_parameter< List >::type styleObjects(styleObjectsSEXP); Rcpp::traits::input_parameter< std::vector >::type xmlFiles(xmlFilesSEXP); Rcpp::traits::input_parameter< LogicalVector >::type is_chart_sheet(is_chart_sheetSEXP); rcpp_result_gen = Rcpp::wrap(loadworksheets(wb, styleObjects, xmlFiles, is_chart_sheet)); return rcpp_result_gen; END_RCPP } // getNodes SEXP getNodes(std::string xml, std::string tagIn); RcppExport SEXP _openxlsx_getNodes(SEXP xmlSEXP, SEXP tagInSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xml(xmlSEXP); Rcpp::traits::input_parameter< std::string >::type tagIn(tagInSEXP); rcpp_result_gen = Rcpp::wrap(getNodes(xml, tagIn)); return rcpp_result_gen; END_RCPP } // getOpenClosedNode SEXP getOpenClosedNode(std::string xml, std::string open_tag, std::string close_tag); RcppExport SEXP _openxlsx_getOpenClosedNode(SEXP xmlSEXP, SEXP open_tagSEXP, SEXP close_tagSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xml(xmlSEXP); Rcpp::traits::input_parameter< std::string >::type open_tag(open_tagSEXP); Rcpp::traits::input_parameter< std::string >::type close_tag(close_tagSEXP); rcpp_result_gen = Rcpp::wrap(getOpenClosedNode(xml, open_tag, close_tag)); return rcpp_result_gen; END_RCPP } // getAttr SEXP getAttr(CharacterVector x, std::string tag); RcppExport SEXP _openxlsx_getAttr(SEXP xSEXP, SEXP tagSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type x(xSEXP); Rcpp::traits::input_parameter< std::string >::type tag(tagSEXP); rcpp_result_gen = Rcpp::wrap(getAttr(x, tag)); return rcpp_result_gen; END_RCPP } // getChildlessNode_ss std::vector getChildlessNode_ss(std::string xml, std::string tag); RcppExport SEXP _openxlsx_getChildlessNode_ss(SEXP xmlSEXP, SEXP tagSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xml(xmlSEXP); Rcpp::traits::input_parameter< std::string >::type tag(tagSEXP); rcpp_result_gen = Rcpp::wrap(getChildlessNode_ss(xml, tag)); return rcpp_result_gen; END_RCPP } // getChildlessNode CharacterVector getChildlessNode(std::string xml, std::string tag); RcppExport SEXP _openxlsx_getChildlessNode(SEXP xmlSEXP, SEXP tagSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xml(xmlSEXP); Rcpp::traits::input_parameter< std::string >::type tag(tagSEXP); rcpp_result_gen = Rcpp::wrap(getChildlessNode(xml, tag)); return rcpp_result_gen; END_RCPP } // get_extLst_Major CharacterVector get_extLst_Major(std::string xml); RcppExport SEXP _openxlsx_get_extLst_Major(SEXP xmlSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xml(xmlSEXP); rcpp_result_gen = Rcpp::wrap(get_extLst_Major(xml)); return rcpp_result_gen; END_RCPP } // cell_ref_to_col int cell_ref_to_col(std::string x); RcppExport SEXP _openxlsx_cell_ref_to_col(SEXP xSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type x(xSEXP); rcpp_result_gen = Rcpp::wrap(cell_ref_to_col(x)); return rcpp_result_gen; END_RCPP } // int_2_cell_ref CharacterVector int_2_cell_ref(IntegerVector cols); RcppExport SEXP _openxlsx_int_2_cell_ref(SEXP colsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type cols(colsSEXP); rcpp_result_gen = Rcpp::wrap(int_2_cell_ref(cols)); return rcpp_result_gen; END_RCPP } // get_shared_strings CharacterVector get_shared_strings(std::string xmlFile, bool isFile); RcppExport SEXP _openxlsx_get_shared_strings(SEXP xmlFileSEXP, SEXP isFileSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xmlFile(xmlFileSEXP); Rcpp::traits::input_parameter< bool >::type isFile(isFileSEXP); rcpp_result_gen = Rcpp::wrap(get_shared_strings(xmlFile, isFile)); return rcpp_result_gen; END_RCPP } // getCellInfo List getCellInfo(std::string xmlFile, CharacterVector sharedStrings, bool skipEmptyRows, int startRow, IntegerVector rows, bool getDates); RcppExport SEXP _openxlsx_getCellInfo(SEXP xmlFileSEXP, SEXP sharedStringsSEXP, SEXP skipEmptyRowsSEXP, SEXP startRowSEXP, SEXP rowsSEXP, SEXP getDatesSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type xmlFile(xmlFileSEXP); Rcpp::traits::input_parameter< CharacterVector >::type sharedStrings(sharedStringsSEXP); Rcpp::traits::input_parameter< bool >::type skipEmptyRows(skipEmptyRowsSEXP); Rcpp::traits::input_parameter< int >::type startRow(startRowSEXP); Rcpp::traits::input_parameter< IntegerVector >::type rows(rowsSEXP); Rcpp::traits::input_parameter< bool >::type getDates(getDatesSEXP); rcpp_result_gen = Rcpp::wrap(getCellInfo(xmlFile, sharedStrings, skipEmptyRows, startRow, rows, getDates)); return rcpp_result_gen; END_RCPP } // read_workbook SEXP read_workbook(IntegerVector cols_in, IntegerVector rows_in, CharacterVector v, IntegerVector string_inds, LogicalVector is_date, bool hasColNames, char hasSepNames, bool skipEmptyRows, bool skipEmptyCols, int nRows, Function clean_names); RcppExport SEXP _openxlsx_read_workbook(SEXP cols_inSEXP, SEXP rows_inSEXP, SEXP vSEXP, SEXP string_indsSEXP, SEXP is_dateSEXP, SEXP hasColNamesSEXP, SEXP hasSepNamesSEXP, SEXP skipEmptyRowsSEXP, SEXP skipEmptyColsSEXP, SEXP nRowsSEXP, SEXP clean_namesSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type cols_in(cols_inSEXP); Rcpp::traits::input_parameter< IntegerVector >::type rows_in(rows_inSEXP); Rcpp::traits::input_parameter< CharacterVector >::type v(vSEXP); Rcpp::traits::input_parameter< IntegerVector >::type string_inds(string_indsSEXP); Rcpp::traits::input_parameter< LogicalVector >::type is_date(is_dateSEXP); Rcpp::traits::input_parameter< bool >::type hasColNames(hasColNamesSEXP); Rcpp::traits::input_parameter< char >::type hasSepNames(hasSepNamesSEXP); Rcpp::traits::input_parameter< bool >::type skipEmptyRows(skipEmptyRowsSEXP); Rcpp::traits::input_parameter< bool >::type skipEmptyCols(skipEmptyColsSEXP); Rcpp::traits::input_parameter< int >::type nRows(nRowsSEXP); Rcpp::traits::input_parameter< Function >::type clean_names(clean_namesSEXP); rcpp_result_gen = Rcpp::wrap(read_workbook(cols_in, rows_in, v, string_inds, is_date, hasColNames, hasSepNames, skipEmptyRows, skipEmptyCols, nRows, clean_names)); return rcpp_result_gen; END_RCPP } // calc_number_rows int calc_number_rows(CharacterVector x, bool skipEmptyRows); RcppExport SEXP _openxlsx_calc_number_rows(SEXP xSEXP, SEXP skipEmptyRowsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type x(xSEXP); Rcpp::traits::input_parameter< bool >::type skipEmptyRows(skipEmptyRowsSEXP); rcpp_result_gen = Rcpp::wrap(calc_number_rows(x, skipEmptyRows)); return rcpp_result_gen; END_RCPP } // map_cell_types_to_integer IntegerVector map_cell_types_to_integer(CharacterVector t); RcppExport SEXP _openxlsx_map_cell_types_to_integer(SEXP tSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type t(tSEXP); rcpp_result_gen = Rcpp::wrap(map_cell_types_to_integer(t)); return rcpp_result_gen; END_RCPP } // map_cell_types_to_char CharacterVector map_cell_types_to_char(IntegerVector t); RcppExport SEXP _openxlsx_map_cell_types_to_char(SEXP tSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type t(tSEXP); rcpp_result_gen = Rcpp::wrap(map_cell_types_to_char(t)); return rcpp_result_gen; END_RCPP } // build_cell_types_integer IntegerVector build_cell_types_integer(CharacterVector classes, int n_rows); RcppExport SEXP _openxlsx_build_cell_types_integer(SEXP classesSEXP, SEXP n_rowsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type classes(classesSEXP); Rcpp::traits::input_parameter< int >::type n_rows(n_rowsSEXP); rcpp_result_gen = Rcpp::wrap(build_cell_types_integer(classes, n_rows)); return rcpp_result_gen; END_RCPP } // buildCellTypes CharacterVector buildCellTypes(CharacterVector classes, int nRows); RcppExport SEXP _openxlsx_buildCellTypes(SEXP classesSEXP, SEXP nRowsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type classes(classesSEXP); Rcpp::traits::input_parameter< int >::type nRows(nRowsSEXP); rcpp_result_gen = Rcpp::wrap(buildCellTypes(classes, nRows)); return rcpp_result_gen; END_RCPP } // build_cell_merges List build_cell_merges(List comps); RcppExport SEXP _openxlsx_build_cell_merges(SEXP compsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type comps(compsSEXP); rcpp_result_gen = Rcpp::wrap(build_cell_merges(comps)); return rcpp_result_gen; END_RCPP } // buildCellList List buildCellList(CharacterVector r, CharacterVector t, CharacterVector v); RcppExport SEXP _openxlsx_buildCellList(SEXP rSEXP, SEXP tSEXP, SEXP vSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type r(rSEXP); Rcpp::traits::input_parameter< CharacterVector >::type t(tSEXP); Rcpp::traits::input_parameter< CharacterVector >::type v(vSEXP); rcpp_result_gen = Rcpp::wrap(buildCellList(r, t, v)); return rcpp_result_gen; END_RCPP } // write_worksheet_xml SEXP write_worksheet_xml(std::string prior, std::string post, Reference sheet_data, std::string R_fileName); RcppExport SEXP _openxlsx_write_worksheet_xml(SEXP priorSEXP, SEXP postSEXP, SEXP sheet_dataSEXP, SEXP R_fileNameSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type prior(priorSEXP); Rcpp::traits::input_parameter< std::string >::type post(postSEXP); Rcpp::traits::input_parameter< Reference >::type sheet_data(sheet_dataSEXP); Rcpp::traits::input_parameter< std::string >::type R_fileName(R_fileNameSEXP); rcpp_result_gen = Rcpp::wrap(write_worksheet_xml(prior, post, sheet_data, R_fileName)); return rcpp_result_gen; END_RCPP } // buildMatrixNumeric SEXP buildMatrixNumeric(CharacterVector v, IntegerVector rowInd, IntegerVector colInd, CharacterVector colNames, int nRows, int nCols); RcppExport SEXP _openxlsx_buildMatrixNumeric(SEXP vSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP colNamesSEXP, SEXP nRowsSEXP, SEXP nColsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type v(vSEXP); Rcpp::traits::input_parameter< IntegerVector >::type rowInd(rowIndSEXP); Rcpp::traits::input_parameter< IntegerVector >::type colInd(colIndSEXP); Rcpp::traits::input_parameter< CharacterVector >::type colNames(colNamesSEXP); Rcpp::traits::input_parameter< int >::type nRows(nRowsSEXP); Rcpp::traits::input_parameter< int >::type nCols(nColsSEXP); rcpp_result_gen = Rcpp::wrap(buildMatrixNumeric(v, rowInd, colInd, colNames, nRows, nCols)); return rcpp_result_gen; END_RCPP } // buildMatrixMixed SEXP buildMatrixMixed(CharacterVector v, IntegerVector rowInd, IntegerVector colInd, CharacterVector colNames, int nRows, int nCols, IntegerVector charCols, IntegerVector dateCols); RcppExport SEXP _openxlsx_buildMatrixMixed(SEXP vSEXP, SEXP rowIndSEXP, SEXP colIndSEXP, SEXP colNamesSEXP, SEXP nRowsSEXP, SEXP nColsSEXP, SEXP charColsSEXP, SEXP dateColsSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type v(vSEXP); Rcpp::traits::input_parameter< IntegerVector >::type rowInd(rowIndSEXP); Rcpp::traits::input_parameter< IntegerVector >::type colInd(colIndSEXP); Rcpp::traits::input_parameter< CharacterVector >::type colNames(colNamesSEXP); Rcpp::traits::input_parameter< int >::type nRows(nRowsSEXP); Rcpp::traits::input_parameter< int >::type nCols(nColsSEXP); Rcpp::traits::input_parameter< IntegerVector >::type charCols(charColsSEXP); Rcpp::traits::input_parameter< IntegerVector >::type dateCols(dateColsSEXP); rcpp_result_gen = Rcpp::wrap(buildMatrixMixed(v, rowInd, colInd, colNames, nRows, nCols, charCols, dateCols)); return rcpp_result_gen; END_RCPP } // matrixRowInds IntegerVector matrixRowInds(IntegerVector indices); RcppExport SEXP _openxlsx_matrixRowInds(SEXP indicesSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< IntegerVector >::type indices(indicesSEXP); rcpp_result_gen = Rcpp::wrap(matrixRowInds(indices)); return rcpp_result_gen; END_RCPP } // build_table_xml CharacterVector build_table_xml(std::string table, std::string tableStyleXML, std::string ref, std::vector colNames, bool showColNames, bool withFilter); RcppExport SEXP _openxlsx_build_table_xml(SEXP tableSEXP, SEXP tableStyleXMLSEXP, SEXP refSEXP, SEXP colNamesSEXP, SEXP showColNamesSEXP, SEXP withFilterSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type table(tableSEXP); Rcpp::traits::input_parameter< std::string >::type tableStyleXML(tableStyleXMLSEXP); Rcpp::traits::input_parameter< std::string >::type ref(refSEXP); Rcpp::traits::input_parameter< std::vector >::type colNames(colNamesSEXP); Rcpp::traits::input_parameter< bool >::type showColNames(showColNamesSEXP); Rcpp::traits::input_parameter< bool >::type withFilter(withFilterSEXP); rcpp_result_gen = Rcpp::wrap(build_table_xml(table, tableStyleXML, ref, colNames, showColNames, withFilter)); return rcpp_result_gen; END_RCPP } // write_worksheet_xml_2 SEXP write_worksheet_xml_2(std::string prior, std::string post, Reference sheet_data, Nullable row_heights_, Nullable outline_levels_, std::string R_fileName); RcppExport SEXP _openxlsx_write_worksheet_xml_2(SEXP priorSEXP, SEXP postSEXP, SEXP sheet_dataSEXP, SEXP row_heights_SEXP, SEXP outline_levels_SEXP, SEXP R_fileNameSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< std::string >::type prior(priorSEXP); Rcpp::traits::input_parameter< std::string >::type post(postSEXP); Rcpp::traits::input_parameter< Reference >::type sheet_data(sheet_dataSEXP); Rcpp::traits::input_parameter< Nullable >::type row_heights_(row_heights_SEXP); Rcpp::traits::input_parameter< Nullable >::type outline_levels_(outline_levels_SEXP); Rcpp::traits::input_parameter< std::string >::type R_fileName(R_fileNameSEXP); rcpp_result_gen = Rcpp::wrap(write_worksheet_xml_2(prior, post, sheet_data, row_heights_, outline_levels_, R_fileName)); return rcpp_result_gen; END_RCPP } static const R_CallMethodDef CallEntries[] = { {"_openxlsx_calc_column_widths", (DL_FUNC) &_openxlsx_calc_column_widths, 7}, {"_openxlsx_convert_to_excel_ref", (DL_FUNC) &_openxlsx_convert_to_excel_ref, 2}, {"_openxlsx_convert_from_excel_ref", (DL_FUNC) &_openxlsx_convert_from_excel_ref, 1}, {"_openxlsx_convert_to_excel_ref_expand", (DL_FUNC) &_openxlsx_convert_to_excel_ref_expand, 3}, {"_openxlsx_isInternalHyperlink", (DL_FUNC) &_openxlsx_isInternalHyperlink, 1}, {"_openxlsx_write_file", (DL_FUNC) &_openxlsx_write_file, 4}, {"_openxlsx_cppReadFile", (DL_FUNC) &_openxlsx_cppReadFile, 1}, {"_openxlsx_read_file_newline", (DL_FUNC) &_openxlsx_read_file_newline, 1}, {"_openxlsx_get_letters", (DL_FUNC) &_openxlsx_get_letters, 0}, {"_openxlsx_markUTF8", (DL_FUNC) &_openxlsx_markUTF8, 2}, {"_openxlsx_loadworksheets", (DL_FUNC) &_openxlsx_loadworksheets, 4}, {"_openxlsx_getNodes", (DL_FUNC) &_openxlsx_getNodes, 2}, {"_openxlsx_getOpenClosedNode", (DL_FUNC) &_openxlsx_getOpenClosedNode, 3}, {"_openxlsx_getAttr", (DL_FUNC) &_openxlsx_getAttr, 2}, {"_openxlsx_getChildlessNode_ss", (DL_FUNC) &_openxlsx_getChildlessNode_ss, 2}, {"_openxlsx_getChildlessNode", (DL_FUNC) &_openxlsx_getChildlessNode, 2}, {"_openxlsx_get_extLst_Major", (DL_FUNC) &_openxlsx_get_extLst_Major, 1}, {"_openxlsx_cell_ref_to_col", (DL_FUNC) &_openxlsx_cell_ref_to_col, 1}, {"_openxlsx_int_2_cell_ref", (DL_FUNC) &_openxlsx_int_2_cell_ref, 1}, {"_openxlsx_get_shared_strings", (DL_FUNC) &_openxlsx_get_shared_strings, 2}, {"_openxlsx_getCellInfo", (DL_FUNC) &_openxlsx_getCellInfo, 6}, {"_openxlsx_read_workbook", (DL_FUNC) &_openxlsx_read_workbook, 11}, {"_openxlsx_calc_number_rows", (DL_FUNC) &_openxlsx_calc_number_rows, 2}, {"_openxlsx_map_cell_types_to_integer", (DL_FUNC) &_openxlsx_map_cell_types_to_integer, 1}, {"_openxlsx_map_cell_types_to_char", (DL_FUNC) &_openxlsx_map_cell_types_to_char, 1}, {"_openxlsx_build_cell_types_integer", (DL_FUNC) &_openxlsx_build_cell_types_integer, 2}, {"_openxlsx_buildCellTypes", (DL_FUNC) &_openxlsx_buildCellTypes, 2}, {"_openxlsx_build_cell_merges", (DL_FUNC) &_openxlsx_build_cell_merges, 1}, {"_openxlsx_buildCellList", (DL_FUNC) &_openxlsx_buildCellList, 3}, {"_openxlsx_write_worksheet_xml", (DL_FUNC) &_openxlsx_write_worksheet_xml, 4}, {"_openxlsx_buildMatrixNumeric", (DL_FUNC) &_openxlsx_buildMatrixNumeric, 6}, {"_openxlsx_buildMatrixMixed", (DL_FUNC) &_openxlsx_buildMatrixMixed, 8}, {"_openxlsx_matrixRowInds", (DL_FUNC) &_openxlsx_matrixRowInds, 1}, {"_openxlsx_build_table_xml", (DL_FUNC) &_openxlsx_build_table_xml, 6}, {"_openxlsx_write_worksheet_xml_2", (DL_FUNC) &_openxlsx_write_worksheet_xml_2, 6}, {NULL, NULL, 0} }; RcppExport void R_init_openxlsx(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } openxlsx/vignettes/0000755000176200001440000000000014155741777014164 5ustar liggesusersopenxlsx/vignettes/Introduction.Rmd0000644000176200001440000004067114155600364017303 0ustar liggesusers--- title: "Introduction" author: "Alexander Walker, Philipp Schauberger" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Basic Examples ### write.xlsx The simplest way to write to a workbook is write.xlsx(). By default, write.xlsx calls writeData. If asTable is TRUE write.xlsx will write x as an Excel table. ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} ## write to working directory library(openxlsx) write.xlsx(iris, file = "writeXLSX1.xlsx") write.xlsx(iris, file = "writeXLSXTable1.xlsx", asTable = TRUE) ``` ### write list of data.frames to xlsx-file ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} ## write a list of data.frames to individual worksheets using list names as worksheet names l <- list("IRIS" = iris, "MTCARS" = mtcars) write.xlsx(l, file = "writeXLSX2.xlsx") write.xlsx(l, file = "writeXLSXTable2.xlsx", asTable = TRUE) ``` ### write.xlsx also accepts styling parameters #### The simplest way is to set default options and set column class ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} options("openxlsx.borderColour" = "#4F80BD") options("openxlsx.borderStyle" = "thin") options("openxlsx.dateFormat" = "mm/dd/yyyy") options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") options("openxlsx.numFmt" = NULL) ## For default style rounding of numeric columns df <- data.frame("Date" = Sys.Date()-0:19, "LogicalT" = TRUE, "Time" = Sys.time()-0:19*60*60, "Cash" = paste("$",1:20), "Cash2" = 31:50, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(0, 1, length.out=20), "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE) class(df$Cash) <- "currency" class(df$Cash2) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumbers) <- "scientific" write.xlsx(df, "writeXLSX3.xlsx") write.xlsx(df, file = "writeXLSXTable3.xlsx", asTable = TRUE) ``` ## Workbook styles ### define a style for column headers ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", halign = "center", valign = "center", textDecoration = "Bold", border = "TopBottomLeftRight", textRotation = 45) write.xlsx(iris, file = "writeXLSX4.xlsx", borders = "rows", headerStyle = hs) write.xlsx(iris, file = "writeXLSX5.xlsx", borders = "columns", headerStyle = hs) write.xlsx(iris, "writeXLSXTable4.xlsx", asTable = TRUE, headerStyle = createStyle(textRotation = 45)) ``` ### When writing a list, the stylings will apply to all list elements ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} l <- list("IRIS" = iris, "colClasses" = df) write.xlsx(l, file = "writeXLSX6.xlsx", borders = "columns", headerStyle = hs) write.xlsx(l, file = "writeXLSXTable5.xlsx", asTable = TRUE, tableStyle = "TableStyleLight2") openXL("writeXLSX6.xlsx") openXL("writeXLSXTable5.xlsx") ``` ### write.xlsx returns the workbook object for further editing ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} wb <- write.xlsx(iris, "writeXLSX6.xlsx") setColWidths(wb, sheet = 1, cols = 1:5, widths = 20) saveWorkbook(wb, "writeXLSX6.xlsx", overwrite = TRUE) ``` ## Workbook creation walk-through ### create workbook and set default border Colour and style ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} require(ggplot2) wb <- createWorkbook() options("openxlsx.borderColour" = "#4F80BD") options("openxlsx.borderStyle" = "thin") modifyBaseFont(wb, fontSize = 10, fontName = "Arial Narrow") ``` ### Add Sheets ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} addWorksheet(wb, sheetName = "Motor Trend Car Road Tests", gridLines = FALSE) addWorksheet(wb, sheetName = "Iris", gridLines = FALSE) ``` ### write data to sheet 1 ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} freezePane(wb, sheet = 1, firstRow = TRUE, firstCol = TRUE) ## freeze first row and column writeDataTable(wb, sheet = 1, x = mtcars, colNames = TRUE, rowNames = TRUE, tableStyle = "TableStyleLight9") setColWidths(wb, sheet = 1, cols = "A", widths = 18) ``` ### write data to sheet 2 iris data.frame is added as excel table on sheet 2. ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} writeDataTable(wb, sheet = 2, iris, startCol = "K", startRow = 2) qplot(data=iris, x = Sepal.Length, y= Sepal.Width, colour = Species) insertPlot(wb, 2, xy=c("B", 16)) ## insert plot at cell B16 means <- aggregate(x = iris[,-5], by = list(iris$Species), FUN = mean) vars <- aggregate(x = iris[,-5], by = list(iris$Species), FUN = var) ``` ### add write group means ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} headSty <- createStyle(fgFill="#DCE6F1", halign="center", border = "TopBottomLeftRight") writeData(wb, 2, x = "Iris dataset group means", startCol = 2, startRow = 2) writeData(wb, 2, x = means, startCol = "B", startRow=3, borders="rows", headerStyle = headSty) ``` ### add write group variances ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} writeData(wb, 2, x = "Iris dataset group variances", startCol = 2, startRow = 9) writeData(wb, 2, x= vars, startCol = "B", startRow=10, borders="columns", headerStyle = headSty) setColWidths(wb, 2, cols=2:6, widths = 12) ## width is recycled for each col setColWidths(wb, 2, cols=11:15, widths = 15) ``` ### add style mean & variance table headers ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} s1 <- createStyle(fontSize=14, textDecoration=c("bold", "italic")) addStyle(wb, 2, style = s1, rows=c(2,9), cols=c(2,2)) ``` ### save workbook ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} saveWorkbook(wb, "basics.xlsx", overwrite = TRUE) ## save to working directory ``` ## Gallery ```{r eval=FALSE, include=TRUE} ## inspired by xtable gallery #https://CRAN.R-project.org/package=xtable/vignettes/xtableGallery.pdf ## Create a new workbook wb <- createWorkbook() data(tli, package = "xtable") ## data.frame test.n <- "data.frame" my.df <- tli[1:10, ] addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.df, borders = "n") ## matrix test.n <- "matrix" design.matrix <- model.matrix(~ sex * grade, data = my.df) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = design.matrix) ## aov test.n <- "aov" fm1 <- aov(tlimth ~ sex + ethnicty + grade + disadvg, data = tli) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = fm1) ## lm test.n <- "lm" fm2 <- lm(tlimth ~ sex*ethnicty, data = tli) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = fm2) ## anova 1 test.n <- "anova" my.anova <- anova(fm2) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.anova) ## anova 2 test.n <- "anova2" fm2b <- lm(tlimth ~ ethnicty, data = tli) my.anova2 <- anova(fm2b, fm2) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.anova2) ## glm test.n <- "glm" fm3 <- glm(disadvg ~ ethnicty*grade, data = tli, family = binomial()) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = fm3) ## prcomp test.n <- "prcomp" pr1 <- prcomp(USArrests) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = pr1) ## summary.prcomp test.n <- "summary.prcomp" addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = summary(pr1)) ## simple table test.n <- "table" data(airquality) airquality$OzoneG80 <- factor(airquality$Ozone > 80, levels = c(FALSE, TRUE), labels = c("Oz <= 80", "Oz > 80")) airquality$Month <- factor(airquality$Month, levels = 5:9, labels = month.abb[5:9]) my.table <- with(airquality, table(OzoneG80,Month) ) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.table) ## survdiff 1 library(survival) test.n <- "survdiff1" addWorksheet(wb = wb, sheetName = test.n) x <- survdiff(Surv(futime, fustat) ~ rx, data = ovarian) writeData(wb = wb, sheet = test.n, x = x) ## survdiff 2 test.n <- "survdiff2" addWorksheet(wb = wb, sheetName = test.n) expect <- survexp(futime ~ ratetable(age=(accept.dt - birth.dt), sex=1,year=accept.dt,race="white"), jasa, cohort=FALSE, ratetable=survexp.usr) x <- survdiff(Surv(jasa$futime, jasa$fustat) ~ offset(expect)) writeData(wb = wb, sheet = test.n, x = x) ## coxph 1 test.n <- "coxph1" addWorksheet(wb = wb, sheetName = test.n) bladder$rx <- factor(bladder$rx, labels = c("Pla","Thi")) x <- coxph(Surv(stop,event) ~ rx, data = bladder) writeData(wb = wb, sheet = test.n, x = x) ## coxph 2 test.n <- "coxph2" addWorksheet(wb = wb, sheetName = test.n) x <- coxph(Surv(stop,event) ~ rx + cluster(id), data = bladder) writeData(wb = wb, sheet = test.n, x = x) ## cox.zph test.n <- "cox.zph" addWorksheet(wb = wb, sheetName = test.n) x <- cox.zph(coxph(Surv(futime, fustat) ~ age + ecog.ps, data=ovarian)) writeData(wb = wb, sheet = test.n, x = x) ## summary.coxph 1 test.n <- "summary.coxph1" addWorksheet(wb = wb, sheetName = test.n) x <- summary(coxph(Surv(stop,event) ~ rx, data = bladder)) writeData(wb = wb, sheet = test.n, x = x) ## summary.coxph 2 test.n <- "summary.coxph2" addWorksheet(wb = wb, sheetName = test.n) x <- summary(coxph(Surv(stop,event) ~ rx + cluster(id), data = bladder)) writeData(wb = wb, sheet = test.n, x = x) ## view without saving openXL(wb) ``` ## Further Examples ### Stock Price ```{r eval=FALSE, include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} require(ggplot2) wb <- createWorkbook() ## read historical prices from yahoo finance ticker <- "CBA.AX" csv.url <- paste0("https://query1.finance.yahoo.com/v7/finance/download/", ticker, "?period1=1597597610&period2=1629133610&interval=1d&events=history&includeAdjustedClose=true") prices <- read.csv(url(csv.url), as.is = TRUE) prices$Date <- as.Date(prices$Date) close <- prices$Close prices$logReturns = c(0, log(close[2:length(close)]/close[1:(length(close)-1)])) ## Create plot of price series and add to worksheet ggplot(data = prices, aes(as.Date(Date), as.numeric(Close))) + geom_line(colour="royalblue2") + labs(x = "Date", y = "Price", title = ticker) + geom_area(fill = "royalblue1",alpha = 0.3) + coord_cartesian(ylim=c(min(prices$Close)-1.5, max(prices$Close)+1.5)) ## Add worksheet and write plot to sheet addWorksheet(wb, sheetName = "CBA") insertPlot(wb, sheet = 1, xy = c("J", 3)) ## Histogram of log returns ggplot(data = prices, aes(x = logReturns)) + geom_bar(binwidth=0.0025) + labs(title = "Histogram of log returns") ## currency class(prices$Close) <- "currency" ## styles as currency in workbook ## write historical data and histogram of returns writeDataTable(wb, sheet = "CBA", x = prices) insertPlot(wb, sheet = 1, startRow=25, startCol = "J") ## Add conditional formatting to show where logReturn > 0.01 using default style conditionalFormat(wb, sheet = 1, cols = 1:ncol(prices), rows = 2:(nrow(prices)+1), rule = "$H2 > 0.01") ## style log return col as a percentage logRetStyle <- createStyle(numFmt = "percentage") addStyle(wb, 1, style = logRetStyle, rows = 2:(nrow(prices) + 1), cols = "H", gridExpand = TRUE) setColWidths(wb, sheet=1, cols = c("A", "F", "G", "H"), widths = 15) ## save workbook to working directory saveWorkbook(wb, "stockPrice.xlsx", overwrite = TRUE) openXL("stockPrice.xlsx") ``` ### Image Compression using PCA ```{r eval=FALSE, include=TRUE} require(openxlsx) require(jpeg) require(ggplot2) plotFn <- function(x, ...){ colvec <- grey(x) colmat <- array(match(colvec, unique(colvec)), dim = dim(x)[1:2]) image(x = 0:(dim(colmat)[2]), y = 0:(dim(colmat)[1]), z = t(colmat[nrow(colmat):1, ]), col = unique(colvec), xlab = "", ylab = "", axes = FALSE, asp = 1, bty ="n", frame.plot=F, ann=FALSE) } ## Create workbook and add a worksheet, hide gridlines wb <- createWorkbook("Einstein") addWorksheet(wb, "Original Image", gridLines = FALSE) A <- readJPEG(file.path(path.package("openxlsx"), "einstein.jpg")) height <- nrow(A); width <- ncol(A) ## write "Original Image" to cell B2 writeData(wb, 1, "Original Image", xy = c(2,2)) ## write Object size to cell B3 writeData(wb, 1, sprintf("Image object size: %s bytes", format(object.size(A+0)[[1]], big.mark=',')), xy = c(2,3)) ## equivalent to startCol = 2, startRow = 3 ## Plot image par(mar=rep(0, 4), xpd = NA); plotFn(A) ## insert plot currently showing in plot window insertPlot(wb, 1, width, height, units="px", startRow= 5, startCol = 2) ## SVD of covariance matrix rMeans <- rowMeans(A) rowMeans <- do.call("cbind", lapply(1:ncol(A), function(X) rMeans)) A <- A - rowMeans E <- svd(A %*% t(A) / (ncol(A) - 1)) # SVD on covariance matrix of A pve <- data.frame("Eigenvalues" = E$d, "PVE" = E$d/sum(E$d), "Cumulative PVE" = cumsum(E$d/sum(E$d))) ## write eigenvalues to worksheet addWorksheet(wb, "Principal Component Analysis") hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", halign = "CENTER", textDecoration = "Bold", border = "TopBottomLeftRight", borderColour = "#4F81BD") writeData(wb, 2, x="Proportions of variance explained by Eigenvector" ,startRow = 2) mergeCells(wb, sheet=2, cols=1:4, rows=2) setColWidths(wb, 2, cols = 1:3, widths = c(14, 12, 15)) writeData(wb, 2, x=pve, startRow = 3, startCol = 1, borders="rows", headerStyle=hs) ## Plots pve <- cbind(pve, "Ind" = 1:nrow(pve)) ggplot(data = pve[1:20,], aes(x = Ind, y = 100*PVE)) + geom_bar(stat="identity", position = "dodge") + xlab("Principal Component Index") + ylab("Proportion of Variance Explained") + geom_line(size = 1, col = "blue") + geom_point(size = 3, col = "blue") ## Write plot to worksheet 2 insertPlot(wb, 2, width = 5, height = 4, startCol = "E", startRow = 2) ## Plot of cumulative explained variance ggplot(data = pve[1:50,], aes(x = Ind, y = 100*Cumulative.PVE)) + geom_point(size=2.5) + geom_line(size=1) + xlab("Number of PCs") + ylab("Cumulative Proportion of Variance Explained") insertPlot(wb, 2, width = 5, height = 4, xy= c("M", 2)) ## Reconstruct image using increasing number of PCs nPCs <- c(5, 7, 12, 20, 50, 200) startRow <- rep(c(2, 24), each = 3) startCol <- rep(c("B", "H", "N"), 2) ## create a worksheet to save reconstructed images to addWorksheet(wb, "Reconstructed Images", zoom = 90) for(i in 1:length(nPCs)){ V <- E$v[, 1:nPCs[i]] imgHat <- t(V) %*% A ## project img data on to PCs imgSize <- object.size(V) + object.size(imgHat) + object.size(rMeans) imgHat <- V %*% imgHat + rowMeans ## reconstruct from PCs and add back row means imgHat <- round((imgHat - min(imgHat)) / (max(imgHat) - min(imgHat))*255) # scale plotFn(imgHat/255) ## write strings to worksheet 3 writeData(wb, "Reconstructed Images", sprintf("Number of principal components used: %s", nPCs[[i]]), startCol[i], startRow[i]) writeData(wb, "Reconstructed Images", sprintf("Sum of component object sizes: %s bytes", format(as.numeric(imgSize), big.mark=',')), startCol[i], startRow[i]+1) ## write reconstruced image insertPlot(wb, "Reconstructed Images", width, height, units="px", xy = c(startCol[i], startRow[i]+3)) } # hide grid lines showGridLines(wb, sheet = 3, showGridLines = FALSE) ## Make text above images BOLD boldStyle <- createStyle(textDecoration="BOLD") ## only want to apply style to specified cells (not all combinations of rows & cols) addStyle(wb, "Reconstructed Images", style=boldStyle, rows = c(startRow, startRow+1), cols = rep(startCol, 2), gridExpand = FALSE) ## save workbook to working directory saveWorkbook(wb, "Image dimensionality reduction.xlsx", overwrite = TRUE) ## remove example files for cran test if (identical(Sys.getenv("NOT_CRAN", unset = "true"), "false")) { file_list<-list.files(pattern="\\.xlsx",recursive = T) file_list<-fl[!grepl("inst/extdata",file_list)&!grepl("man/",file_list)] if(length(file_list)>0){ rm(file_list) } ``` openxlsx/vignettes/tableStyles.PNG0000644000176200001440000007757114155600364017030 0ustar liggesusersPNG  IHDR/ӶsRGBgAMA a pHYsodIDATx^k\ՙy&g"OLDOD􏙈 t5Uvwu.e1m/L1m,0`c.%c!c6B ! [2BX !q1WI!f;srwo<>OoԱ2rB=,B/[W߄\!R,;ߝmBy.!{Ӧ,!B |tnLY>֭RJi$.s)T OB!B7;$j'Co_kw.?{JqC}:қyw?k]M?_pGf> Os*҇&B`ܖIQ)} w U7׏ÇCqquu{py}K_)ܵ۵Y+ot/=>>:T ڇ&B9y = )EҷB: %z=~ւB?k޻w: %z=~ւB?k\!E۫/?W.}y}]ybyWt_,}Js?_5rW^}C$/΂B?k@DPWԽ~{]><-e Xzr__>wʃ;tAwM.[ᾳ※űnq7NwWԽ1C$/΂B?k@DP:CPM&("(&8ZHBP/ /J8(RaA_w7~?uOy7Jw3cBCSԩw!,(gWmj (G7ϸzd-(:+ܵW o5lp/r{>B6 vB?@XzH?[fw@;0(]}(_~/v^ޱí{n/Oq8|LȑB>?@SS%O xb YBfnÿܵSUe!hg (J7} <07.YJkTGܭ7y+78B`pznqR@&݁a(&#gv ,׬8P=྿/_t.mٲ۷ϭ]`ٷk>n\Bp EO¸e!kJ( g>rړ̴5P*ùR.w'}?s㔅J_Gm LP!w%`-#޽ad Vn(w2 ൲,ʀEPR@Q|Š wJ@>HU~+VM6/]4 ;ݒFZLyq;udɩm2_{Bn ׄۇXNrݳY0?< w_nY,J)3Pyv!. #;μ@RYn>m84lGavjpk{F?wn.M̆o!(wC==+ l f}`#\P-ÿA ?ϖSznQݕ7u߻.7yr-K.qgV-j7 "0 B0 0̖aI',)ns^oc!@;s׿ ~,Y),P rw@ 7/=Û/XvBP{B4(E@A\x\0.o/_7`~$̥E{[|#JϾ}\r{7 w>(5 e@v ZCpيy7cvw巾v^ٵq+t.;=b*>G AY°A!A;eG!>>Y^Ͼa<7~ [JQ!yU~>. +oBi / , /Ϳ-w0 |u7zT"|2 ʯ".ʀ| H|pXon@X}|[mk/}ܳ{=nҥNs_Xe']kOqt#SP v>0O!(o/w RB*T}ˠ #aX9cJ(UV|nc, RX|Hj!W/}; /[{̅Hx82U@Y20,gBp ẃ֟,^I~-[̝}/~}S};vت Czɤwыc(íqs`P !JP Bnǎ {~C0p=sq7ٱܲb &(./_1R vM  ?.R#͛˝ŋSN=ݼyw]yTض |>cA ?BPAT ZCtk@&)#k>#01 , R ѹQG(ą Rs A?X/ _,#"2phPdw@&(Jut?AW/%^=hYo.}Sݍw-[u}s_;ByП>o;BP^; KI!QH%gF& ۋr P(8+a>rhuoBZ }oP gm %Ͻa!~ <\b)2x =]\>R[^69߸)[vnuWw>ڭ\}~ݝvWX.sgr[/8 BP(g (JYC]!X(|Ã_,.E׮v?]m7u΅./pW.Oiw׮s7?Tg^&$wB?k@DP:W~xwQo2JQNJw o\s]w#wW~-k?4wW#]7/nw_q/ ĕw.5Sc(yavDq#PP(g-(+{Jص(`Q-Z!w^uCsЭG;}G}}?u7lC1}?.W!O̮$/w"BP(g (JYC U =)o|wAwCwSeo;]C }?s_KO%⊟V~:BϜV϶c$#wRnQ %z=~ւB?k^!+W v|QxwvR 7>s;C_};g3.4w'Z\ϻx}[K20sOj ɋgwB?k@DP:XֹK7~jw^ßȝ_rǫܵw?wg>z_p{>wחs.9x̅>3s˚><0=~րB?kAD5H!\x &wp|Wmr9gW>{G/g|]u+>趯<KͿc aم8g9v}޹Wܞ=.ywg.;߅N |Bd8Bpd2{{r ~ys =w'qE_|]*<ܟ/z^{cP)ۏl:o'x̅,BH=)tc<”B_B`|d!>/GuV <7d! A>t O<ѝ[s )Y1w}۳x{܃ͱlwꗇH*,x| S2};|89)wwU@ A] O ؟c63)OV!M[nc>M"sԝ-0eEqoԥ S_D/WE2A^er(OĽ~^-CW N)w’b﹩S/9$_B35~O܉ Tx}ð3up)n.Dx~3saBp !|p؞(.BZ* ܛ\&A0xVv#Xq\,ry'#/ 'ךr!8"̧ )rԴB07 YB}6oz0xp}_Ew5Lry>;G 0kr<HpƇg{vCʵQ!Րt A}pW^uo?˫ݕWvV_Yfi8+_psqv5 pp8kח3<^<6r>\sQ&<8F 7㾼RKvt ~ɽ <ߜ"e`6(ܝ+0TB{p+NuG/֭+o.B2䄩@3S^_ s SG% r;zmќaq saYQ} Հ_+\ \OC@BR0`2P4 h`Z><`ZFfsׯ/T;^3\I2!q A+|=܎ A0"v y\|~.k^K*eqDD}Uo@}!(O~-ybp| C?. !8Rdl6PwP 9s ڎ5tW>/ rl!3YP! 0Px5" EBvpoIErFˀ>E|c2`;H_XR-ɴ+#s#WK`AGC!_AY6 v_/S'|S, o)8w;@k\ 5u\N0$ }΁>g h)ow xH-(_@]?x#zfE9 ӑ+<ϦW#%E]*#ǪY2;SsA>x!(_[WBP8 %!xQB0L*'||A>Y6b^.? 8g㫻+&nAhag敵axGks,c#'<W!| y;E>@z_=ޅ_-A_B0,.2P!?$85!˚#% l!([J!g߫>r=.AξS?'6ݿB0 Q,Cm4dgY-EsGg *ڼ^(2ۃ]7r E[#; y 7̖B-T=.v@BOeV—5@s)e;0 F?XS7zV P!h'( t^+$GP!XHYX+W6o='X򑅠W:B y_E-6a!G3Bp=B̀Yÿ]6E⹱ YhqB}&^!;0\!T,X74'kx!gq~h!#Pwƹ-㞺lx;ݚg:WA?-{p>6:w;gos99yxlh)=|?95ze!>,3Bק:<`G` |qOܶEw5?\2n/nr]4w6w5-­rEν8W]<45/E<B!Ĺr Nw=xq:gSz['5e>)ܲGz;wOUnjo"1xlRb01o'sB@!T:p}`9[B [Oʹ}}sν,}8A~܉_dD۞(+Tr_ss1xnQ Ra)x(-3$ύB2u@Q"J@+0ܢIpyNun2S?^k6sf w(KE)vYy{Cǧ$ύB2 ~*[|W;}勅efbPevީ F))`}ۻw۳g-^yk+n]nǎw_z=‹ne)d;|Ԟ?bY(<@W{kU{r]XvvvWՑ/ RJ)qg}ɽb\AO[\d!RJpϺO>^xnGfHO.#RJ)q_r/].=شBp;O9>}?>68}`fwsSJ)}S+W/(<^§^z(AOg=Blc!8sSJ)Fl*R)`gJo{޽⮑!r༻Ahy/'c'^O)ȺB1c+~!OMvvZJ)TDoS!\qۓ3F({  v|ȏewX(Ҹ[_.sΑ^P`$Y(RUC0߿eo.}rs;G꥝{\ [s H.÷FO)qMOny9vd }sB0w*Rg'V~.V[꧔RJg!ظq[§_-[!2@)R{U!xbL 7q}߸o\=S]bjRJ)@M?,S20ɑB➇J5Sy|e!RJP]v^}UkCÇ_9rְVy3#?], !,߹}dT9B@!8 s(w`򸇅Bɘ, TxVNU'wj\}Sc)7NP8pzg w!B@!B!d_^"М4/o^ty4E9/yh;_z֊@s^"МoZh΋@s^"Мw,w4E9/y\tM4E9/yh;_>׊@s^"МMpB!,Ba! BH !B,x@_c] =@_c] =@_c] =֍ɲl޼wjlynOػީ'{ƳvN]z7L@i] @i] @i] ֍a!D @i] @i] @iݘ zu}^# J>paP/s]}7F }\5 D_ >%Bo8dF}Q# ʾe_/jA5 1,Ӻ(0Ӻ(0Ӻ(0B(0Ӻ(0Ӻ(0Ӻ1,Ӻ(0Ӻ(0Ӻ(03B], (0Ӻ(0Ӻ(0Ӻ1,߹}dB!Ll{fe! B`G*}߹[^~B!$c&V."Rxh΋@s^"Мw/"М4E9|A_E9/yh΋@sNj@s^"М}4E9/y B/yh΋@s^>^"М4mbk_Vկ\r%#?f0*w!<=ۼّg! B2B!,Bɴ/j/j/j/n A&j Lj Lj Lưd Lj Lj Ljn8 =,Wu5P`ZWu5P`ZWu5P`Z7&Bu}^# J>paP/s]}7F }\5 D_ >%Bo8dF}Q# ʾe_/jA5 1,Ӻ(0Ӻ(0Ӻ(0B(0Ӻ(0Ӻ(0Ӻ1,Ӻ(0Ӻ(0Ӻ(0B(0Ӻ(0Ӻ(0Ӻ1,RR!2YX!B@!>^"М4/ }4E9/yh;_xh΋@s^"Мw/"М4E9|A_E9/yh΋@sNj@s^"М}4E9/y!B&Wn=.BɃqo[r?#Y!<g!x;_?TB!e!@ƻwj:靽SOwjzƁ;cC@!B!B!B!,Ba! BH !BX!i!@_c] =@_c] =@_c] =@_cL@i] @i] @i] ֍a!D @i] @i] @iL@i] @i] @i] ֍ɾ4u}^#5(ùke8yԠ >\5R2FSPs]A&jA5 aPE0(F}Q# ʾB(0Ӻ(0Ӻ(0Ӻ1,Ӻ(0Ӻ(0Ӻ(03B0ee!Su5P`ZWu5P`ZWu5P`Z7f`ڍOM3#E @i] @i] @iݘ TDj!O>ZDj!Od A*,UXTa!B),UXTa!BPJ' \4E9/y=:€@s^"М漞ˋ@s^"М漞ˋ@s^"М漞ˋ@s^"М漞ˋ@s^"М漞ˋ@s^"МdJATATAd A*,UXTa!B),UXTa!BP ATa!BP A**}SCojH@M Ⱦ!75$ dߌA>ZDj!O>Z ATa!BP A*,BP A*,UXt:h΋@s^"М^# <4E9/yhi[й4E9/yhi[й4E9/yhi[й4E9/yhi[й4E9/yhi[й4E9/yhA<_TATAT ATa!BP A*,BP A*,UXd A*,UXTa!ҩB75$ dԐ}SCojH@͘-[],w>ZDj!O>ZBuk7nX?7͌d>BP A*,UXXTa!BP A*,rN"М42>O(yh΋@s^zt./yh΋@s^zt./yh΋@s^zt./yh΋@s^zt./yh΋@s^zt./yh΋@s^zC))ϗ;UCP;UCP;UCB,UXTa!BP ATa!BP A*,BP A*,UXtM Ⱦ!75$ dԐ}3;ZDj!O>ZDj!,BP A*,UXd A*,UXTa!B D9/yh΋@s^Ox0 "М4E9m!@"М4E9m!@"М4E9m!@"М4E9m!@"М4E9m!@"М4E9;|CP;UCP;UCP;,YBP A*,UXd A*,UXTa!B),UXTa!BPJ AԐ}SCojH@M Ⱦ!7c,B,,B\!ٲŲB!y0Bn !1!BB!B!B!Y=@_c] =@_c] =@_c] =֍ɶ)҇ |J@a>% C0!}GلyrC!CR!)}>|Hʼŀoͳ$|>$e2߇IC!CR-|h>B ėėėė2>O|a@sy=4_G\^:€Dz,$:cirO Ȑ>̧dHS2)҇ < &,γ$|>$e2߇IC!CR-|h !)}>|H|>$e2o1@, 0߇IC!CR!)}y϶M Ⱦ!75$ dԐ}3&Byd-kH@nynO5$ ny"Y /y2Y nۑy ș]0!yZa!HpS5,<:^kXxtNװx2a!:e^Bu| Aװx2a!:e^BuʼGy )Sc/rpa asy@F\0P>*:7 Ԯk asy@| ̖.;e^# -ky0PS0j5@M¼F)Zg!XvcnzH-e^BuʼGy )S5,<:^kXxtN,e^BuʼGy )S5,<:^kXxtNa!HpS5,<:^kXxtNװx2a!:e^Bu|LojH@M Ⱦ!75$ dԐ16a>% C0!}O Ȑ>̧dHS2( 8B@}>|H|>$e2߇IMyrC!CR!)}>|Hʼŀog_rrrrrra@ X# Ht.|a@sy=4_G\^:€Dz,{C@)҇ |J@a>% C0!}GلyrC!CR!)}>|Hʼŀoͳ$|>$e2߇IC!CR-|h !)}>|H|>$e2o1@!75$ dԐ}SCod[Wia^C}o5$ Wia^C}o5$ Wia^C}o5$ Wia> IkXxtNװx2a!:e^BuʼGy )1, Nb^BuʼGy )S5,<:^kXxtNɾ:Ùk'4 Ԯk asy@F\0P>*:7 Ԯk 1!Hpaha^# -ky0PS0j5@M| Aװx2a!:e^BuʼGy )Sc&Vflu,e^BuʼGy )S5,<:^kXxtNg!XvcnzHsAװx2a!:e^BuʼGy )Sc&C09BS!}SCojH@M Ⱦ!75$ fL }O Ȑ>̧dHS2)҇ < &,γ$|>$e2߇IC!CR-|h !)}>|H|>$e2o1@\'\'\'\'\'\GyB=4_G\^:€Dz,$:ci0 ѹK$|J@a>% C0!}O Ȑ>̧dHQ@6aq !)}>|H|>$e2o1@, 0߇IC!CR!)}yg!H >|H|>$e2߇IC[ &|ojH@M Ⱦ!75$ dԐ1YB!LB!B!B!Y{_RJqRJ),RJ)e!RJi ̖.RJ)quk7nX?7͌d?w(Ҍi^wމ'œwmם<8~`s|pRJ)*!@SJ)};ᅠ x(&x`85s,?~)ڝBpZ w?|?~]>2ypyA݉rǁRJiP!71 \,JQ`O!KŰ(Ү٩B{ B0<ޑ`PJ)+sa.J7e pH`!Rj؎z#Їׇ`n`0Z9J)@!RJBB@)RJ)PJ)0B@0]{o?B! A2p BLtGnடេyBY8X2,vGw=xş !,,YWmK+]Ny/#f+zƃwjܾS[z;cX21f}Zwkʀ|VƲϻ|şyH@:靽1\}1,>ޘZ~C1m-[], 1{K!RUWϻ|şyİTa!BP x K7vӛfF;1Ok3E(^>x/# A*,UX:A\cmEnvGuwٯ&P|Y]>Asq?w]>A|e?ATAT2*,UXTa!BP SXTa!BP ALa!BP A*,U:Uf *}3M Ⱦ!75$ dߌɶjj!@_kBת}ůUS _VM-+~B,UYTe!BP* ATe!BP* AU,r0TwY~ϻ|şyp wp wp``fVB0߼ݿ}ۅW/X!+ߺs9vX A*,-n,ݰ~Mo~4 mKsG_^ @{^#]G~;,UXT9;ß# `SQB )ݻWw*Gzt(Bi Spۿ$wk bptw; _>&W+IjG_~uQ dBR`!Ș_<Sש!}!wWM AؿpwýcOO32_w{m5ߔ󴽯mO,对p\}] X!i ^;\S'C6ͥc7O󴼯r^O6<,|/,R AxCAX6wsNϸq܍s/YUw><%k;U#ǃ5gܸ|3nrO%|"x.θsǿ Kx&aܶU~>P}睓2_Xz޽kEX-{f/vm}sw;'ݲ"й_6w<˞狟Ex|x%]\/-\?zms<.ehx?egX_CY!%Bɶkr5LY#gj"XzWs_;+53~;{=~ߝQ޶}}_..kbo59'w5+WיۼZvMnoym6/jml&y1 5P7Fcal^_yIݦm\mER.kE1HyL`fSql'՝+>^f>sЦTAd5͋(0l&yQfm6/ư$ &SHxڵ]T sgowoxmOwMW9E\t%W< wEmwtg2~<. &ۮm^@d5͋(0l&y1 5P7Fc׀tx}m^m./a7&>ܝK܊B߶ӿ؊A|N=Wxm5ϊ_0:^gyQ4;[vMniR>3 ou- 3V@m^2Ŷkr5 LE0(Sl&y1 5P7vMnswm6/jlE M]ۼɶkr5P`6vMnb A =/jlE M]ۼɶkr5P`6vMn &ۮm^@d5͋1, .ɶkr5P`6vMn &ۮm^@d5͋(0l&yQfm6/dY!2Y,7one5͋[ʶkr5nym6/jV]ۼl&yQcf׾V]ۼqͭl&y1 5P`6vMn &ۮm^@d5͋(0l&yQfm6/jlz^@d5͋(0l&yQfm6/jlE M]ۼɶkrc/avyޯi" Jt.o]ki& Day0(ѹavyޯi" Jt.o]ki$F)]ۼem6/jAb5͋aPvMnF)]ۼem6/ư$ &ۮm^@d5͋(0l&yQfm6/jlE M]ۼBBϋ(0l&yQfm6/jlE M]ۼɶkr5P`6vMnb A =/jlE M]ۼɶkr5P`6vMn &ۮm^@d5͋1YB!L-[]l]!@_d5͋}l&yQ}om6/jmE վM]ۼڷɶkr5W6vMnb8 K7vӛfF &ۮm^@d5͋(0l&yQfm6/jlE M]ۼɶkrc&C09 (0l&yQfm6/jlE M]ۼɶkr5P`6vMnbL}7 .5MA ô~MaPsy0_D\0L<4%:7 .5MA ô~MaPsy0_z^2Ŷkr5 LE0(Sl&yQ# ۮm^2Ŷkr5 Lz^@d5͋(0l&yQfm6/jlE M]ۼɶkrcX\yQfm6/jlE M]ۼɶkr5P`6vMn &ۮm^a!HpE M]ۼɶkr5P`6vMn &ۮm^@d5͋(0l&y1&B@!ɒe!@_d5͋}l&yQ}om6/jmE վM]ۼڷɶkr5W6vMnb A =/jlE M]ۼɶkr5P`6vMn &ۮm^@d5͋1, .ɶkr5P`6vMn &ۮm^@d5͋(0l&yQfm6/d_+x0_D\0L<4%:7 .5MA ô~MaPsy0_D\0L<4%:7 .5!!HpE0(Sl&yQ# ۮm^2Ŷkr5 LE0(Sl&yQ# ۮm^a!HpE M]ۼɶkr5P`6vMn &ۮm^@d5͋(0l&y1 5P`6vMn &ۮm^@d5͋(0l&yQfm6/jlz^@d5͋(0l&yQfm6/jlE M]ۼɶkrcX\yQfm6/jlE M]ۼɶkr5P`6vMn &ۮm^ɲB!dB!dr`fVB@!8 K7vӛfF;BHLl`sB!$X!B@!B!dYпd] ƺhu5j5@kX7 5ScI׬x_X| S<;5\B(0Ӻ(0Ӻ(0Ӻ1,Ӻ(0Ӻ(0Ӻ(0}!h"FjPs]H pAu}^#5(ùke8y >L/jA5 aPE0(F}1 5P`ZWu5P`ZWu5P`ZWucX2Qu5P`ZWu5P`ZWu5P`Z7 5P`ZWu5P`ZWu5P`ZWucX2QZc] ƺhu5j5֍ɶl޼9Y hu5j5@kXWn A@묫XWZc] ƺhucXļZg] ƺhu5j5@k}!u3]0΢syùk\p,:7F8 >΢syùLlbC0P5:j5@kXWZcݘquk7nX?7͌d?2hP5:j5@kXWZcݘ B0P5:j5@kXWZc'1YWZc] ƺhu5d[eZWZc] ƺhu5d[зyB!dܰ$wϓ0!&ka^CU3Zא/$ka^CZa!HpS5,<:^kXxtNװx2a!:e^Bu| Aװx2a!:e^BuʼGy )Sc/rpa asy@F\0P>*:7 Ԯk asy@| wļF)Z5E aha^# -ky0PS0B$5,<:^kXxtNװx2a!:e^BuʼG'1a!:e^BuʼGy )S5,<:^ǰ$8y )S5,<:^kXxtNװx2a!:e>&B75$ dԐ}SCojH@͘l :-kH@¼$:-kH@¼$:-kH@¼$:-LlbYʼGy )S5,<:^kXxtNװx23Bn A2a!:e^BuʼGy )S5,<:^Ll`sB g>j|0P>*:7 Ԯk asy@F\0P>*:7 Ԯd_R0j5@M¼F)Z5E aha^# -ǰ$8y )S5,<:^kXxtNװx2a!:e> IkXxtNװx2a!:e^BuʼGy )1, Nb^BuʼGy )S5,<:^kXxtNɶM Ⱦ!75$ dԐ}3&BN +~0!N +~0!N +~0!N 1, Nb^BuʼGy )S5,<:^kXxtNa!HpS5,<:^kXxtNװx2a!:e^Bu|L@.t}^# fb`fVRrL5@j5@Ey0Pѹav}^# Tt.o]j5@Equk7nX?7͌d?wy0PS0j5@M¼F)Z5E aha>fb;ß# y )S5,<:^kXxtNװx2a!:e> IkXxtNװx2a!:e^BuʼGy )1, Nb^BuʼGy )S5,<:^kXxtNɶM Ⱦ!75$ dԐ}3&B@!B@!B!B! B{kRJ)=PJ)RJ),RJ)-d!RJ) RJPw; RJA;Y1meIRJ)=vb`fV;BC~`y<,2 ?RsY֭Xa43 鹷T(Ҿ;Ug>_~A\λ{dY3(C)ʾp0ݿ\5R*_(RPJ)RJ),RJ)-̢B!B@!B!B! B055;5j5@kXWZcL@kXWZc] ƺhucX2QZc] ƺhu5j5֍a!D ƺhu5j5@kX7&BD8yp\5RgùkΆs]H >:u}>;XWZc] ƺhu5ưdZc] ƺhu5j5@kBhu5j5@kXWn ̖.`N ƺhu5j5@kX7f`ڍOM3#uB!eb;ß#S͋.W{9׽Scѿ|O{g A&j Lj Lj Lưd Lj Lj LjnLpAu}^#5(ùke8yԠ >\52$| w2Q# ʾe_/jA5 aPL@i] @i] @i] ֍a!D @i] @i] @iL@i] @i] @i] ֍a!D @i] @i] @iݘ, ?ީ/NzgԸfzSnoީqષL@i] @i] @i] ֍a!D @i] @i] @iݘ A\5R2FjPs]H pAu}^#5(ùk4eH8dF}Q# ʾe_/jA5 1,Ӻ(0Ӻ(0Ӻ(0B(0Ӻ(0Ӻ(0Ӻ1,Ӻ(0Ӻ(0Ӻ(0e!@_c] =@_c] =@_c] =@_cݘ-[], (0Ӻ(0Ӻ(0Ӻ1,n,ݰ~Mo~(j Lj Lj LLl`s95P`ZWu5P`ZWu5P`ZWuc/Ms]H pAu}^#5(ùke8yԠ >!\cCaPE0(F}Q# ʾe_/ưd Lj Lj Ljn A&j Lj Lj Lưd Lj Lj Ljn A&j Lj Lj LdYWZW}u5WZW}u5WZW}u5WZ7 5P`ZWu5P`ZWu5P`ZWucX2Qu5P`ZWu5P`ZWu5P`Z7&BD8yԠ >\5R2FjPs]H pMAu}>;e_/jA5 aPE0(b A&j Lj Lj Lưd Lj Lj Ljn A&j Lj Lj Lưd Lj Lj LjnLB!B!+3[XB!$Y֭Xa43! B2fb;ß#B!yB@!B!B! B055;5ЗXW}!u5ЗX2X2Qu5P`ZWu !ぅ 5P`ZWu5P`Z2X2Qu5P`ZWu !!BD8yԠ >\5R2%:7F\paPsyùBw2Q# ʾe_/B A&j Lj.!d<d Lj LB A&j Lj.!d%lbY@i] @i]Bx@_qgvw;vvv^~y{/n^[tďd?w5P`ZWu5P`Z2$}Z\C09 (0Ӻ(0KYվ@_k] վ@_k]Bx`!D @i] %L@i] @i]Bx Rh!@_J >%:7F\paPsyùB!@%R# ʾe_/B**|`@i] @i]Bx+ 3  Lj LBCS!J ' Lj LBƃV & Lj LBCB!B!,B{Vv{V2B1l* dyş-O-ͽ;F( THpѲ5].hy_BzX Z|۠ = H ]w9p1"$97s%ǟZh.e!$0x8r[c)kq> \!AP^Xvݗ2)w>| TO/ @ !Xg`භm?U,OX F¹|{o!7:WK[m /?Ws=6ȱِw}ɌvBu/;~W@`M,g-]hQn;B@<;BH: VE@ANZKuE M0B0ʹ~0x;`twBIgA |!2^ !2X!B@!B!`fVB@!B}ڍOM3#B!;ß#B!k!x/.womM]NB c-own}բx@GJ|,Bdo!y}YUn LS~apb w;- !2Z&}I2v?D۷f! B3!pE Dv%c(8W߱0Y g|>Mϱsi}BɎ {aP{;鬥n(GG!X- Ci!B*:g-8S{\2Χ}Bɒ=,{|΀ݏ/N vsnc)hRRr}wݢفh^~dA@B erǃ^}c e6[6~! By~-BHـg)2U^:BP FCjSVnv,xFY1 \k{_Bra p@>DXMrG@>Dxm B}CP"塘 /ևr<:1e evSoTПQ}L.B9,X!ʿZH 3+V񽃷 KgA #)]"3Go q "n]xF{ ?<2!ɂkw =:|@vV=ZX X $;B @v?q] ,K ;Rf_=GB!XB Vw!0B0NX!B@!lbY![H[ti7if$C@!u`s_>RJ)W8B@!B!~Qr,! ïSJ;j8F)1F)qvS K;vQJ) *%nRJ)#޺ܳr{lkB2 MRJdyX]/q&Bra{׾Q7||x_Gb_2]=$>{u~ k|x_G"~_\2]=$9r>>gY&?3]z,ϟϿ<uՕe)IENDB`openxlsx/vignettes/Formatting.Rmd0000644000176200001440000002767014155600364016740 0ustar liggesusers--- title: "Formating with xlsx" author: "Alexander Walker, Philipp Schauberger" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Formating with xlsx} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Formatting with writeData and writeDataTable ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} ## data.frame to write df <- data.frame("Date" = Sys.Date()-0:4, "Logical" = c(TRUE, FALSE, TRUE, TRUE, FALSE), "Currency" = paste("$",-2:2), "Accounting" = -2:2, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(-1, 1, length.out=5), "TinyNumber" = runif(5) / 1E9, stringsAsFactors = FALSE) class(df$Currency) <- "currency" class(df$Accounting) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumber) <- "scientific" ## Formatting can be applied simply through the write functions ## global options can be set to further simplify things options("openxlsx.borderStyle" = "thin") options("openxlsx.borderColour" = "#4F81BD") ## create a workbook and add a worksheet wb <- createWorkbook() addWorksheet(wb, "writeData auto-formatting") writeData(wb, 1, df, startRow = 2, startCol = 2) writeData(wb, 1, df, startRow = 9, startCol = 2, borders = "surrounding") writeData(wb, 1, df, startRow = 16, startCol = 2, borders = "rows") writeData(wb, 1, df, startRow = 23, startCol = 2, borders ="columns") writeData(wb, 1, df, startRow = 30, startCol = 2, borders ="all") ## headerStyles hs1 <- createStyle(fgFill = "#4F81BD", halign = "CENTER", textDecoration = "Bold", border = "Bottom", fontColour = "white") writeData(wb, 1, df, startRow = 16, startCol = 10, headerStyle = hs1, borders = "rows", borderStyle = "medium") ## to change the display text for a hyperlink column just write over those cells writeData(wb, sheet = 1, x = paste("Hyperlink", 1:5), startRow = 17, startCol = 14) ## writing as an Excel Table addWorksheet(wb, "writeDataTable") writeDataTable(wb, 2, df, startRow = 2, startCol = 2) writeDataTable(wb, 2, df, startRow = 9, startCol = 2, tableStyle = "TableStyleLight9") writeDataTable(wb, 2, df, startRow = 16, startCol = 2, tableStyle = "TableStyleLight2") writeDataTable(wb, 2, df, startRow = 23, startCol = 2, tableStyle = "TableStyleMedium21") openXL(wb) ## opens a temp version ``` ## Use of pre-defined table styles The 'tableStyle' argument in writeDataTable can be any of the predefined tableStyles in Excel. ![](tableStyles.PNG) ## Date Formatting ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} # data.frame of dates dates <- data.frame("d1" = Sys.Date() - 0:4) for(i in 1:3) dates <- cbind(dates, dates) names(dates) <- paste0("d", 1:8) ## Date Formatting wb <- createWorkbook() addWorksheet(wb, "Date Formatting", gridLines = FALSE) writeData(wb, 1, dates) ## write without styling ## openxlsx converts columns of class "Date" to Excel dates with the format given by getOption("openxlsx.dateFormat", "mm/dd/yyyy") ## this can be set via (for example) options("openxlsx.dateFormat" = "yyyy/mm/dd") ## custom date formats can be made up of any combination of: ## d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy ## numFmt == "DATE" will use the date format specified by the above addStyle(wb, 1, style = createStyle(numFmt = "DATE"), rows = 2:11, cols = 1, gridExpand = TRUE) ## some custom date format examples sty <- createStyle(numFmt = "yyyy/mm/dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) sty <- createStyle(numFmt = "yyyy/mmm/dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) sty <- createStyle(numFmt = "yy / mmmm / dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) sty <- createStyle(numFmt = "ddddd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 5, gridExpand = TRUE) sty <- createStyle(numFmt = "yyyy-mmm-dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 6, gridExpand = TRUE) sty <- createStyle(numFmt = "mm/ dd yyyy") addStyle(wb, 1, style = sty, rows = 2:11, cols = 7, gridExpand = TRUE) sty <- createStyle(numFmt = "mm/dd/yy") addStyle(wb, 1, style = sty, rows = 2:11, cols = 8, gridExpand = TRUE) setColWidths(wb, 1, cols = 1:10, widths = 23) ## The default date format used in writeData and writeDataTable can be set with: options("openxlsx.dateFormat" = "dd/mm/yyyy") writeData(wb, "Date Formatting", dates, startRow = 8, borders = "rows") options("openxlsx.dateFormat" = "yyyy-mm-dd") writeData(wb, "Date Formatting", dates, startRow = 15) saveWorkbook(wb, "Date Formatting.xlsx", overwrite = TRUE) ``` ## DateTime Formatting The conversion from POSIX to Excel datetimes is dependent on the timezone you are in. If POSIX values are being written incorrectly, try setting the timezone with (for example) ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} Sys.setenv(TZ = "Australia/Sydney") dateTimes <- data.frame("d1" = Sys.time() - 0:4*10000) for(i in 1:2) dateTimes <- cbind(dateTimes, dateTimes) names(dateTimes) <- paste0("d", 1:4) ## POSIX Formatting wb <- createWorkbook() addWorksheet(wb, "DateTime Formatting", gridLines = FALSE) writeData(wb, 1, dateTimes) ## write without styling ## openxlsx converts columns of class "POSIxt" to Excel datetimes with the format given by getOption("openxlsx.datetimeFormat", "yyyy/mm/dd hh:mm:ss") ## this can be set via (for example) options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") ## custom datetime formats can be made up of any combination of: ## d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy, h, hh, m, mm, s, ss, AM/PM ## numFmt == "LONGDATE" will use the date format specified by the above long_date_style <- createStyle(numFmt = "LONGDATE") addStyle(wb, 1, style = long_date_style, rows = 2:11, cols = 1, gridExpand = TRUE) ## some custom date format examples sty <- createStyle(numFmt = "yyyy/mm/dd hh:mm:ss AM/PM") addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) sty <- createStyle(numFmt = "hh:mm:ss AM/PM") addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) sty <- createStyle(numFmt = "hh:mm:ss") addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) setColWidths(wb, 1, cols = 1:4, widths = 30) ## The default date format used in writeData and writeDataTable can be set with: options("openxlsx.datetimeFormat" = "yyyy/mm/dd hh:mm:ss") writeData(wb, "DateTime Formatting", dateTimes, startRow = 8, borders = "rows") options("openxlsx.datetimeFormat" = "hh:mm:ss AM/PM") writeDataTable(wb, "DateTime Formatting", dateTimes, startRow = 15) saveWorkbook(wb, "DateTime Formatting.xlsx", overwrite = TRUE) openXL("DateTime Formatting.xlsx") ``` ## Conditional Formatting ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} wb <- createWorkbook() addWorksheet(wb, "cellIs") addWorksheet(wb, "Moving Row") addWorksheet(wb, "Moving Col") addWorksheet(wb, "Dependent on 1") addWorksheet(wb, "Duplicates") addWorksheet(wb, "containsText") addWorksheet(wb, "colourScale", zoom = 30) addWorksheet(wb, "databar") negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE") ## rule applies to all each cell in range writeData(wb, "cellIs", -5:5) writeData(wb, "cellIs", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "cellIs", cols=1, rows=1:11, rule="!=0", style = negStyle) conditionalFormatting(wb, "cellIs", cols=1, rows=1:11, rule="==0", style = posStyle) ## highlight row dependent on first cell in row writeData(wb, "Moving Row", -5:5) writeData(wb, "Moving Row", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Moving Row", cols=1:2, rows=1:11, rule="$A1<0", style = negStyle) conditionalFormatting(wb, "Moving Row", cols=1:2, rows=1:11, rule="$A1>0", style = posStyle) ## highlight column dependent on first cell in column writeData(wb, "Moving Col", -5:5) writeData(wb, "Moving Col", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Moving Col", cols=1:2, rows=1:11, rule="A$1<0", style = negStyle) conditionalFormatting(wb, "Moving Col", cols=1:2, rows=1:11, rule="A$1>0", style = posStyle) ## highlight entire range cols X rows dependent only on cell A1 writeData(wb, "Dependent on 1", -5:5) writeData(wb, "Dependent on 1", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=1:11, rule="$A$1<0", style = negStyle) conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=1:11, rule="$A$1>0", style = posStyle) ## highlight duplicates using default style writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE)) conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates") ## cells containing text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "containsText", sapply(1:10, fn)) conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains", rule = "A") ## colourscale colours cells based on cell value df <- read.xlsx(system.file("readTest.xlsx", package = "openxlsx"), sheet = 4) writeData(wb, "colourScale", df, colNames=FALSE) ## write data.frame ## rule is a vector or colours of length 2 or 3 (any hex colour or any of colours()) ## If rule is NULL, min and max of cells is used. Rule must be the same length as style or NULL. conditionalFormatting(wb, "colourScale", cols=1:ncol(df), rows=1:nrow(df), style = c("black", "white"), rule = c(0, 255), type = "colourScale") setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07) setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5) ## Databars writeData(wb, "databar", -5:5) conditionalFormatting(wb, "databar", cols = 1, rows = 1:12, type = "databar") ## Default colours saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE) openXL(wb) ``` ## Numeric Formatting numeric columns styling can be set using the numFmt parameter in createStyle or a default can be set with, for example, options("openxlsx.numFmt" = "#,#0.00") ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} options("openxlsx.numFmt" = NULL) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") df <- data.frame(matrix(12.987654321, ncol = 7, nrow = 5)) ## data.frame to write df[ ,6:7] <- df[ ,6:7]*1E6 ## Set column 1 class to "comma" to get comma separated thousands class(df$X1) <- "comma" writeData(wb, 1, df) s <- createStyle(numFmt = "0.0") addStyle(wb, 1, style = s, rows = 2:6, cols = 2, gridExpand = TRUE) s <- createStyle(numFmt = "0.00") addStyle(wb, 1, style = s, rows = 2:6, cols = 3, gridExpand = TRUE) s <- createStyle(numFmt = "0.000") addStyle(wb, 1, style = s, rows = 2:6, cols = 4, gridExpand = TRUE) s <- createStyle(numFmt = "#,##0") addStyle(wb, 1, style = s, rows = 2:6, cols = 5, gridExpand = TRUE) s <- createStyle(numFmt = "#,##0.00") addStyle(wb, 1, style = s, rows = 2:6, cols = 6, gridExpand = TRUE) s <- createStyle(numFmt = "$ #,##0.00") addStyle(wb, 1, style = s, rows = 2:6, cols = 7, gridExpand = TRUE) ## set a default number format for numeric columns of data.frames options("openxlsx.numFmt" = "$* #,#0.00") writeData(wb, 1, x = data.frame("Using Default Options" = rep(2345.1235, 5)), startCol = 9) setColWidths(wb, 1, cols = 1:10, widths = 15) ## Using default numFmt to round to 2 dp (Any numeric column will be affected) addWorksheet(wb, "Sheet 2") df <- iris; df[, 1:4] <- df[1:4] + runif(1) writeDataTable(wb, sheet = 2, x = df) writeData(wb, sheet = 2, x = df, startCol = 7) writeData(wb, sheet = 2, x = df, startCol = 13, borders = "rows") ## To stop auto-formatting numerics set options("openxlsx.numFmt" = NULL) addWorksheet(wb, "Sheet 3") writeDataTable(wb, sheet = 3, x = df) openXL(wb) ``` openxlsx/R/0000755000176200001440000000000014155742000012331 5ustar liggesusersopenxlsx/R/openxlsxCoerce.R0000644000176200001440000001305614155600363015467 0ustar liggesusers ## openxlsxCoerce <- function(x, rowNames) { UseMethod("openxlsxCoerce") } openxlsxCoerce.default <- function(x, rowNames) { x <- as.data.frame(x, stringsAsFactors = FALSE) return(x) } openxlsxCoerce.data.frame <- function(x, rowNames) { ## cbind rownames to x if (rowNames) { x <- cbind(data.frame("row names" = rownames(x), stringsAsFactors = FALSE), as.data.frame(x, stringsAsFactors = FALSE)) names(x)[[1]] <- "" } return(x) } openxlsxCoerce.data.table <- function(x, rowNames) { x <- as.data.frame(x, stringsAsFactors = FALSE) ## cbind rownames to x if (rowNames) { x <- cbind(data.frame("row names" = rownames(x), stringsAsFactors = FALSE), x) names(x)[[1]] <- "" } return(x) } openxlsxCoerce.matrix <- function(x, rowNames) { x <- as.data.frame(x, stringsAsFactors = FALSE) if (rowNames) { x <- cbind(data.frame("row names" = rownames(x), stringsAsFactors = FALSE), x) names(x)[[1]] <- "" } return(x) } openxlsxCoerce.array <- function(x, rowNames) { stop("array in writeData : currently not supported") } openxlsxCoerce.aov <- function(x, rowNames) { x <- summary(x) x <- cbind(x[[1]]) x <- cbind(data.frame("row name" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" return(x) } openxlsxCoerce.lm <- function(x, rowNames) { x <- as.data.frame(summary(x)[["coefficients"]]) x <- cbind(data.frame("Variable" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" return(x) } openxlsxCoerce.anova <- function(x, rowNames) { x <- as.data.frame(x) if (rowNames) { x <- cbind(data.frame("row name" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" } return(x) } openxlsxCoerce.glm <- function(x, rowNames) { x <- as.data.frame(summary(x)[["coefficients"]]) x <- cbind(data.frame("row name" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" return(x) } openxlsxCoerce.table <- function(x, rowNames) { x <- as.data.frame(unclass(x)) x <- cbind(data.frame("Variable" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" return(x) } openxlsxCoerce.prcomp <- function(x, rowNames) { x <- as.data.frame(x$rotation) x <- cbind(data.frame("Variable" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" return(x) } openxlsxCoerce.summary.prcomp <- function(x, rowNames) { x <- as.data.frame(x$importance) x <- cbind(data.frame("Variable" = rownames(x), stringsAsFactors = FALSE), x) names(x)[1] <- "" return(x) } #' @name openxlsxCoerce.survdiff #' @description like print.survdiff with some ideas from the ascii package #' @param x data.frame for input #' @param rowNames rownames #' @importFrom stats pchisq #' @keywords internal #' @noRd openxlsxCoerce.survdiff <- function(x, rowNames) { ## like print.survdiff with some ideas from the ascii package if (length(x$n) == 1) { z <- sign(x$exp - x$obs) * sqrt(x$chisq) temp <- c(x$obs, x$exp, z, 1 - pchisq(x$chisq, 1)) names(temp) <- c("Observed", "Expected", "Z", "p") x <- as.data.frame(t(temp)) } else { if (is.matrix(x$obs)) { otmp <- apply(x$obs, 1, sum) etmp <- apply(x$exp, 1, sum) } else { otmp <- x$obs etmp <- x$exp } chisq <- c(x$chisq, rep(NA, length(x$n) - 1)) df <- c((sum(1 * (etmp > 0))) - 1, rep(NA, length(x$n) - 1)) p <- c(1 - pchisq(x$chisq, df[!is.na(df)]), rep(NA, length(x$n) - 1)) temp <- cbind( x$n, otmp, etmp, ((otmp - etmp)^2) / etmp, ((otmp - etmp)^2) / diag(x$var), chisq, df, p ) colnames(temp) <- c( "N", "Observed", "Expected", "(O-E)^2/E", "(O-E)^2/V", "Chisq", "df", "p" ) temp <- as.data.frame(temp, checknames = FALSE) x <- cbind("Group" = names(x$n), temp) names(x)[1] <- "" } return(x) } openxlsxCoerce.coxph <- function(x, rowNames) { ## sligthly modified print.coxph coef <- x$coefficients se <- sqrt(diag(x$var)) if (is.null(coef) | is.null(se)) { stop("Input is not valid") } if (is.null(x$naive.var)) { tmp <- cbind(coef, exp(coef), se, coef / se, pchisq((coef / se)^2, 1)) colnames(tmp) <- c("coef", "exp(coef)", "se(coef)", "z", "p") } else { nse <- sqrt(diag(x$naive.var)) tmp <- cbind(coef, exp(coef), nse, se, coef / se, pchisq((coef / se)^2, 1)) colnames(tmp) <- c("coef", "exp(coef)", "se(coef)", "robust se", "z", "p") } x <- cbind("Variable" = names(coef), as.data.frame(tmp, checknames = FALSE)) names(x)[1] <- "" return(x) } openxlsxCoerce.summary.coxph <- function(x, rowNames) { coef <- x$coefficients ci <- x$conf.int # nvars <- nrow(coef) variable not used tmp <- cbind( coef[, -ncol(coef), drop = FALSE], # p later ci[, (ncol(ci) - 1):ncol(ci), drop = FALSE], # confint coef[, ncol(coef), drop = FALSE] ) # p.value x <- as.data.frame(tmp, checknames = FALSE) x <- cbind(data.frame("row names" = rownames(x)), x) names(x)[[1]] <- "" return(x) } openxlsxCoerce.cox.zph <- function(x, rowNames) { tmp <- as.data.frame(x$table) x <- cbind(data.frame("row names" = rownames(tmp)), tmp) names(x)[[1]] <- "" return(x) } openxlsxCoerce.hyperlink <- function(x, rowNames) { ## vector of hyperlinks class(x) <- c("character", "hyperlink") x <- as.data.frame(x, stringsAsFactors = FALSE) } openxlsx/R/HyperlinkClass.R0000644000176200001440000000506414155600363015421 0ustar liggesusers Hyperlink <- setRefClass("Hyperlink", fields = c( "ref", "target", "location", "display", "is_external" ), methods = list() ) Hyperlink$methods(initialize = function(ref, target, location, display = NULL, is_external = TRUE) { ref <<- ref target <<- target location <<- location display <<- display is_external <<- is_external }) Hyperlink$methods(to_xml = function(id) { loc <- sprintf('location="%s"', location) disp <- sprintf('display="%s"', display) rf <- sprintf('ref="%s"', ref) if (is_external) { rid <- sprintf('r:id="rId%s"', id) } else { rid <- NULL } paste("") }) Hyperlink$methods(to_target_xml = function(id) { if (is_external) { return(sprintf('', id, target)) } else { return(NULL) } }) xml_to_hyperlink <- function(xml) { # xml <- c('', # '', # '') if (length(xml) == 0) { return(xml) } targets <- names(xml) if (is.null(targets)) { targets <- rep(NA, length(xml)) } xml <- unname(xml) a <- unlist(lapply(xml, function(x) regmatches(x, gregexpr('[a-zA-Z]+=".*?"', x))), recursive = FALSE) names <- lapply(a, function(xml) regmatches(xml, regexpr('[a-zA-Z]+(?=\\=".*?")', xml, perl = TRUE))) vals <- lapply(a, function(xml) regmatches(xml, regexpr('(?<=").*?(?=")', xml, perl = TRUE))) vals <- lapply(vals, function(x) { Encoding(x) <- "UTF-8" x }) hyperlink_objects <- lapply(seq_along(xml), function(i) { tmp_vals <- vals[[i]] tmp_nms <- names[[i]] names(tmp_vals) <- tmp_nms ## ref ref <- tmp_vals[["ref"]] ## location if ("location" %in% tmp_nms) { location <- tmp_vals[["location"]] } else { location <- NULL } ## location if ("display" %in% tmp_nms) { display <- tmp_vals[["display"]] } else { display <- NULL } ## target/external if (is.na(targets[i])) { target <- NULL is_external <- FALSE } else { is_external <- TRUE target <- targets[i] } Hyperlink$new(ref = ref, target = target, location = location, display = display, is_external = is_external) }) return(hyperlink_objects) } openxlsx/R/openxlsx-package.R0000644000176200001440000000014114155600363015726 0ustar liggesusers## usethis namespace: start #' @importFrom lifecycle deprecate_soft ## usethis namespace: end openxlsx/R/helperFunctions.R0000644000176200001440000007040314155600363015635 0ustar liggesusers #' @name makeHyperlinkString #' @title create Excel hyperlink string #' @description Wrapper to create internal hyperlink string to pass to writeFormula(). Either link to external urls or local files or straight to cells of local Excel sheets. #' @param sheet Name of a worksheet #' @param row integer row number for hyperlink to link to #' @param col column number of letter for hyperlink to link to #' @param text display text #' @param file Excel file name to point to. If NULL hyperlink is internal. #' @seealso [writeFormula()] #' @export makeHyperlinkString #' @examples #' #' ## Writing internal hyperlinks #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet1") #' addWorksheet(wb, "Sheet2") #' addWorksheet(wb, "Sheet 3") #' writeData(wb, sheet = 3, x = iris) #' #' ## External Hyperlink #' x <- c("https://www.google.com", "https://www.google.com.au") #' names(x) <- c("google", "google Aus") #' class(x) <- "hyperlink" #' #' writeData(wb, sheet = 1, x = x, startCol = 10) #' #' #' ## Internal Hyperlink - create hyperlink formula manually #' writeFormula(wb, "Sheet1", #' x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")', #' startCol = 3 #' ) #' #' ## Internal - No text to display using makeHyperlinkString() function #' writeFormula(wb, "Sheet1", #' startRow = 1, #' x = makeHyperlinkString(sheet = "Sheet 3", row = 1, col = 2) #' ) #' #' ## Internal - Text to display #' writeFormula(wb, "Sheet1", #' startRow = 2, #' x = makeHyperlinkString( #' sheet = "Sheet 3", row = 1, col = 2, #' text = "Link to Sheet 3" #' ) #' ) #' #' ## Link to file - No text to display #' writeFormula(wb, "Sheet1", #' startRow = 4, #' x = makeHyperlinkString( #' sheet = "testing", row = 3, col = 10, #' file = system.file("extdata", "loadExample.xlsx", package = "openxlsx") #' ) #' ) #' #' ## Link to file - Text to display #' writeFormula(wb, "Sheet1", #' startRow = 3, #' x = makeHyperlinkString( #' sheet = "testing", row = 3, col = 10, #' file = system.file("extdata", "loadExample.xlsx", package = "openxlsx"), #' text = "Link to File." #' ) #' ) #' #' ## Link to external file - Text to display #' writeFormula(wb, "Sheet1", #' startRow = 10, startCol = 1, #' x = '=HYPERLINK(\\"[C:/Users]\\", \\"Link to an external file\\")' #' ) #' #' ## Link to internal file #' x = makeHyperlinkString(text = "test.png", file = "D:/somepath/somepicture.png") #' writeFormula(wb, "Sheet1", startRow = 11, startCol = 1, x = x) #' #' \dontrun{ #' saveWorkbook(wb, "internalHyperlinks.xlsx", overwrite = TRUE) #' } #' makeHyperlinkString <- function(sheet, row = 1, col = 1, text = NULL, file = NULL) { op <- get_set_options() on.exit(options(op), add = TRUE) if (missing(sheet)) { if (!missing(row) || !missing(col)) warning("Option for col and/or row found, but no sheet was provided.") str <- sprintf("=HYPERLINK(\"%s\", \"%s\")", file, text) } else { cell <- paste0(int2col(col), row) if (!is.null(file)) { dest <- sprintf("[%s]'%s'!%s", file, sheet, cell) } else { dest <- sprintf("#'%s'!%s", sheet, cell) } if (is.null(text)) { str <- sprintf("=HYPERLINK(\"%s\")", dest) } else { str <- sprintf("=HYPERLINK(\"%s\", \"%s\")", dest, text) } } return(str) } getRId <- function(x) { regmatches(x, gregexpr('(?<= r:id=")[0-9A-Za-z]+', x, perl = TRUE)) } getId <- function(x) { regmatches(x, gregexpr('(?<= Id=")[0-9A-Za-z]+', x, perl = TRUE)) } ## creates style object based on column classes ## Used in writeData for styling when no borders and writeData table for all column-class based styling classStyles <- function(wb, sheet, startRow, startCol, colNames, nRow, colClasses, stack = TRUE) { sheet <- wb$validateSheet(sheet) allColClasses <- unlist(colClasses, use.names = FALSE) rowInds <- (1 + startRow + colNames - 1L):(nRow + startRow + colNames - 1L) startCol <- startCol - 1L newStylesElements <- NULL names(colClasses) <- NULL if ("hyperlink" %in% allColClasses) { ## style hyperlinks inds <- which(sapply(colClasses, function(x) "hyperlink" %in% x)) hyperlinkstyle <- createStyle(textDecoration = "underline") hyperlinkstyle$fontColour <- list("theme" = "10") styleElements <- list( "style" = hyperlinkstyle, "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } if ("date" %in% allColClasses) { ## style dates inds <- which(sapply(colClasses, function(x) "date" %in% x)) styleElements <- list( "style" = createStyle(numFmt = "date"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } if (any(c("posixlt", "posixct", "posixt") %in% allColClasses)) { ## style POSIX inds <- which(sapply(colClasses, function(x) any(c("posixct", "posixt", "posixlt") %in% x))) styleElements <- list( "style" = createStyle(numFmt = "LONGDATE"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } ## style currency as CURRENCY if ("currency" %in% allColClasses) { inds <- which(sapply(colClasses, function(x) "currency" %in% x)) styleElements <- list( "style" = createStyle(numFmt = "CURRENCY"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } ## style accounting as ACCOUNTING if ("accounting" %in% allColClasses) { inds <- which(sapply(colClasses, function(x) "accounting" %in% x)) styleElements <- list( "style" = createStyle(numFmt = "ACCOUNTING"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } ## style percentages if ("percentage" %in% allColClasses) { inds <- which(sapply(colClasses, function(x) "percentage" %in% x)) styleElements <- list( "style" = createStyle(numFmt = "percentage"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } ## style big mark if ("scientific" %in% allColClasses) { inds <- which(sapply(colClasses, function(x) "scientific" %in% x)) styleElements <- list( "style" = createStyle(numFmt = "scientific"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } ## style big mark if ("3" %in% allColClasses | "comma" %in% allColClasses) { inds <- which(sapply(colClasses, function(x) "3" %in% tolower(x) | "comma" %in% tolower(x))) styleElements <- list( "style" = createStyle(numFmt = "3"), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } ## numeric sigfigs (Col must be numeric and numFmt options must only have 0s and \\.) if ("numeric" %in% allColClasses & !grepl("[^0\\.,#\\$\\* %]", getOption("openxlsx.numFmt", "GENERAL"))) { inds <- which(sapply(colClasses, function(x) "numeric" %in% tolower(x))) styleElements <- list( "style" = createStyle(numFmt = getOption("openxlsx.numFmt", "0")), "sheet" = wb$sheet_names[sheet], "rows" = rep.int(rowInds, times = length(inds)), "cols" = rep(inds + startCol, each = length(rowInds)) ) newStylesElements <- append(newStylesElements, list(styleElements)) } if (!is.null(newStylesElements)) { if (stack) { for (i in seq_along(newStylesElements)) { wb$addStyle( sheet = sheet, style = newStylesElements[[i]]$style, rows = newStylesElements[[i]]$rows, cols = newStylesElements[[i]]$cols, stack = TRUE ) } } else { wb$styleObjects <- append(wb$styleObjects, newStylesElements) } } invisible(1) } #' @name validateColour #' @description validate the colour input #' @param colour colour #' @param errorMsg Error message #' @author Philipp Schauberger #' @importFrom grDevices colours #' @keywords internal #' @noRd validateColour <- function(colour, errorMsg = "Invalid colour!") { ## check if if (is.null(colour)) { colour <- "black" } validColours <- colours() if (any(colour %in% validColours)) { colour[colour %in% validColours] <- col2hex(colour[colour %in% validColours]) } if (any(!grepl("^#[A-Fa-f0-9]{6}$", colour))) { stop(errorMsg, call. = FALSE) } colour <- gsub("^#", "FF", toupper(colour)) return(colour) } #' @name col2hex #' @description convert rgb to hex #' @param creator my.col #' @author Philipp Schauberger #' @importFrom grDevices col2rgb rgb #' @keywords internal #' @noRd col2hex <- function(my.col) { rgb(t(col2rgb(my.col)), maxColorValue = 255) } ## header and footer replacements headerFooterSub <- function(x) { if (!is.null(x)) { x <- replaceIllegalCharacters(x) x <- gsub("\\[Page\\]", "P", x) x <- gsub("\\[Pages\\]", "N", x) x <- gsub("\\[Date\\]", "D", x) x <- gsub("\\[Time\\]", "T", x) x <- gsub("\\[Path\\]", "Z", x) x <- gsub("\\[File\\]", "F", x) x <- gsub("\\[Tab\\]", "A", x) } return(x) } writeCommentXML <- function(comment_list, file_name) { authors <- unique(sapply(comment_list, "[[", "author")) xml <- '' xml <- c(xml, paste0("", paste(sprintf("%s", authors), collapse = ""), "")) for (i in seq_along(comment_list)) { authorInd <- which(authors == comment_list[[i]]$author) - 1L xml <- c(xml, sprintf('', comment_list[[i]]$ref, authorInd)) if (length(comment_list[[i]]$style) != 0) { ## check that style information is present for (j in seq_along(comment_list[[i]]$comment)) { xml <- c(xml, sprintf('%s%s', comment_list[[i]]$style[[j]], comment_list[[i]]$comment[[j]])) } } else { ## Case with no styling information. for (j in seq_along(comment_list[[i]]$comment)) { xml <- c(xml, sprintf('%s', comment_list[[i]]$comment[[j]])) } } xml <- c(xml, "") } write_file(body = paste(xml, collapse = ""), tail = "", fl = file_name) NULL } illegalchars <- c("&", '"', "'", "<", ">", "\a", "\b", "\v", "\f") illegalcharsreplace <- c("&", """, "'", "<", ">", "", "", "", "") replaceIllegalCharacters <- function(v) { vEnc <- Encoding(v) v <- as.character(v) flg <- vEnc != "UTF-8" if (any(flg)) { v[flg] <- stri_conv(v[flg], from = "", to = "UTF-8") } v <- stri_replace_all_fixed(v, illegalchars, illegalcharsreplace, vectorize_all = FALSE) return(v) } replaceXMLEntities <- function(v) { v <- gsub("&", "&", v, fixed = TRUE) v <- gsub(""", '"', v, fixed = TRUE) v <- gsub("'", "'", v, fixed = TRUE) v <- gsub("<", "<", v, fixed = TRUE) v <- gsub(">", ">", v, fixed = TRUE) return(v) } pxml <- function(x) { paste(unique(unlist(x)), collapse = "") } removeHeadTag <- function(x) { x <- paste(x, collapse = "") if (any(grepl("<\\?", x))) { x <- gsub("<\\?xml [^>]+", "", x) } x <- gsub("^>", "", x) x } validateBorderStyle <- function(borderStyle) { valid <- c( "none", "thin", "medium", "dashed", "dotted", "thick", "double", "hair", "mediumDashed", "dashDot", "mediumDashDot", "dashDotDot", "mediumDashDotDot", "slantDashDot" ) ind <- match(tolower(borderStyle), tolower(valid)) if (any(is.na(ind))) { stop("Invalid borderStyle", call. = FALSE) } return(valid[ind]) } getAttrsFont <- function(xml, tag) { x <- lapply(xml, getChildlessNode, tag = tag) x[sapply(x, length) == 0] <- "" x <- unlist(x) a <- lapply(x, function(x) unlist(regmatches(x, gregexpr('[a-zA-Z]+=".*?"', x)))) nms <- lapply(a, function(xml) regmatches(xml, regexpr('[a-zA-Z]+(?=\\=".*?")', xml, perl = TRUE))) vals <- lapply(a, function(xml) regmatches(xml, regexpr('(?<=").*?(?=")', xml, perl = TRUE))) vals <- lapply(vals, function(x) { Encoding(x) <- "UTF-8" x }) vals <- lapply(seq_along(vals), function(i) { names(vals[[i]]) <- nms[[i]] vals[[i]] }) return(vals) } getAttrs <- function(xml, tag) { x <- lapply(xml, getChildlessNode_ss, tag = tag) x[sapply(x, length) == 0] <- "" a <- lapply(x, function(x) regmatches(x, regexpr('[a-zA-Z]+=".*?"', x))) names <- lapply(a, function(xml) regmatches(xml, regexpr('[a-zA-Z]+(?=\\=".*?")', xml, perl = TRUE))) vals <- lapply(a, function(xml) regmatches(xml, regexpr('(?<=").*?(?=")', xml, perl = TRUE))) vals <- lapply(vals, function(x) { Encoding(x) <- "UTF-8" x }) names(vals) <- names return(vals) } buildFontList <- function(fonts) { sz <- getAttrs(fonts, "sz") colour <- getAttrsFont(fonts, "color") name <- getAttrs(fonts, tag = "name") family <- getAttrs(fonts, "family") scheme <- getAttrs(fonts, "scheme") italic <- lapply(fonts, getChildlessNode, tag = "i") bold <- lapply(fonts, getChildlessNode, tag = "b") underline <- lapply(fonts, getChildlessNode, tag = "u") strikeout <- lapply(fonts, getChildlessNode, tag = "strike") ## Build font objects ft <- replicate(list(), n = length(fonts)) for (i in seq_along(fonts)) { f <- NULL nms <- NULL if (length(unlist(sz[i])) > 0) { f <- c(f, sz[i]) nms <- c(nms, "sz") } if (length(unlist(colour[i])) > 0) { f <- c(f, colour[i]) nms <- c(nms, "color") } if (length(unlist(name[i])) > 0) { f <- c(f, name[i]) nms <- c(nms, "name") } if (length(unlist(family[i])) > 0) { f <- c(f, family[i]) nms <- c(nms, "family") } if (length(unlist(scheme[i])) > 0) { f <- c(f, scheme[i]) nms <- c(nms, "scheme") } if (length(italic[[i]]) > 0) { f <- c(f, "italic") nms <- c(nms, "italic") } if (length(bold[[i]]) > 0) { f <- c(f, "bold") nms <- c(nms, "bold") } if (length(underline[[i]]) > 0) { f <- c(f, "underline") nms <- c(nms, "underline") } if (length(unlist(strikeout[i])) > 0) { f <- c(f, strikeout[i]) nms <- c(nms, "strikeout") } f <- lapply(seq_along(f), function(i) unlist(f[i])) names(f) <- nms ft[[i]] <- f } ft } get_named_regions_from_string <- function(dn) { dn <- gsub("", "", dn, fixed = TRUE) dn <- gsub("", "", dn, fixed = TRUE) dn <- unique(unlist(strsplit(dn, split = "", fixed = TRUE))) dn <- grep(").*", dn, perl = TRUE)) dn_pos <- gsub("[$']", "", dn_pos) has_bang <- grepl("!", dn_pos, fixed = TRUE) dn_sheets <- ifelse(has_bang, gsub("^(.*)!.*$", "\\1", dn_pos), "" ) dn_coords <- ifelse(has_bang, gsub("^.*!(.*)$", "\\1", dn_pos), "" ) attr(dn_names, "sheet") <- dn_sheets attr(dn_names, "position") <- dn_coords return(dn_names) } nodeAttributes <- function(x) { x <- paste0("<", unlist(strsplit(x, split = "<"))) x <- grep(" 1) tmp <- tmp[[1]] if (length(tmp) == 1) { sideBorder[[i]] <- tmp } } sideBorder <- sideBorder[sideBorder != ""] x <- x[sideBorder != ""] if (length(sideBorder) == 0) { return(NULL) } ## style weight <- gsub('style=|"', "", regmatches(x, regexpr('style="[a-z]+"', x, perl = TRUE, ignore.case = TRUE))) ## Colours cols <- replicate(n = length(sideBorder), list(rgb = "FF000000")) colNodes <- unlist(sapply(x, getChildlessNode, tag = "color", USE.NAMES = FALSE)) if (length(colNodes) > 0) { attrs <- regmatches(colNodes, regexpr('(theme|indexed|rgb|auto)=".+"', colNodes)) } else { attrs <- NULL } if (length(attrs) != length(x)) { return( list( "borders" = paste(sideBorder, collapse = ""), "colour" = cols ) ) } attrs <- strsplit(attrs, split = "=") cols <- sapply(attrs, function(attr) { if (length(attr) == 2) { y <- list(gsub('"', "", attr[2])) names(y) <- gsub(" ", "", attr[[1]]) } else { tmp <- paste(attr[-1], collapse = "=") y <- gsub('^"|"$', "", tmp) names(y) <- gsub(" ", "", attr[[1]]) } return(y) }) ## sideBorder & cols if ("LEFT" %in% sideBorder) { style$borderLeft <- weight[which(sideBorder == "LEFT")] style$borderLeftColour <- cols[which(sideBorder == "LEFT")] } if ("RIGHT" %in% sideBorder) { style$borderRight <- weight[which(sideBorder == "RIGHT")] style$borderRightColour <- cols[which(sideBorder == "RIGHT")] } if ("TOP" %in% sideBorder) { style$borderTop <- weight[which(sideBorder == "TOP")] style$borderTopColour <- cols[which(sideBorder == "TOP")] } if ("BOTTOM" %in% sideBorder) { style$borderBottom <- weight[which(sideBorder == "BOTTOM")] style$borderBottomColour <- cols[which(sideBorder == "BOTTOM")] } if ("DIAGONAL" %in% sideBorder) { style$borderDiagonal <- weight[which(sideBorder == "DIAGONAL")] style$borderDiagonalColour <- cols[which(sideBorder == "DIAGONAL")] } return(style) } genHeaderFooterNode <- function(x) { # # &Lfirst L&CfC&RfR # &LfFootL&CfFootC&RfFootR # &LTIS&CIS&REVEN H # &LEVEN L F&CEVEN C F&REVEN RIGHT F # &L&P&Cfirst C&Rfirst R # &Lfirst L Foot&Cfirst C Foot&Rfirst R Foot # ## ODD if (length(x$oddHeader) > 0) { oddHeader <- paste0( "", sprintf("&L%s", x$oddHeader[[1]]), sprintf("&C%s", x$oddHeader[[2]]), sprintf("&R%s", x$oddHeader[[3]]), "", collapse = "" ) } else { oddHeader <- NULL } if (length(x$oddFooter) > 0) { oddFooter <- paste0( "", sprintf("&L%s", x$oddFooter[[1]]), sprintf("&C%s", x$oddFooter[[2]]), sprintf("&R%s", x$oddFooter[[3]]), "", collapse = "" ) } else { oddFooter <- NULL } ## EVEN if (length(x$evenHeader) > 0) { evenHeader <- paste0( "", sprintf("&L%s", x$evenHeader[[1]]), sprintf("&C%s", x$evenHeader[[2]]), sprintf("&R%s", x$evenHeader[[3]]), "", collapse = "" ) } else { evenHeader <- NULL } if (length(x$evenFooter) > 0) { evenFooter <- paste0( "", sprintf("&L%s", x$evenFooter[[1]]), sprintf("&C%s", x$evenFooter[[2]]), sprintf("&R%s", x$evenFooter[[3]]), "", collapse = "" ) } else { evenFooter <- NULL } ## FIRST if (length(x$firstHeader) > 0) { firstHeader <- paste0( "", sprintf("&L%s", x$firstHeader[[1]]), sprintf("&C%s", x$firstHeader[[2]]), sprintf("&R%s", x$firstHeader[[3]]), "", collapse = "" ) } else { firstHeader <- NULL } if (length(x$firstFooter) > 0) { firstFooter <- paste0( "", sprintf("&L%s", x$firstFooter[[1]]), sprintf("&C%s", x$firstFooter[[2]]), sprintf("&R%s", x$firstFooter[[3]]), "", collapse = "" ) } else { firstFooter <- NULL } headTag <- sprintf( '', as.integer(!(is.null(evenHeader) & is.null(evenFooter))), as.integer(!(is.null(firstHeader) & is.null(firstFooter))) ) paste0( headTag, oddHeader, oddFooter, evenHeader, evenFooter, firstHeader, firstFooter, "" ) } buildFillList <- function(fills) { fillAttrs <- rep(list(list()), length(fills)) ## patternFill inds <- grepl("patternFill", fills) fillAttrs[inds] <- lapply(fills[inds], nodeAttributes) ## gradientFill inds <- grepl("gradientFill", fills) fillAttrs[inds] <- fills[inds] return(fillAttrs) } # Can test with below: # x <- "" getDefinedNamesSheet <- function(x) { sub("'?\\!.*", "", sub("^.*>'", "", x)) } # Not used but kepted in case fix above isn't correct getDefinedNamedSheet_ <- function(x) { belongTo <- unlist(lapply(strsplit(x, split = ">|<"), "[[", 3)) quoted <- grepl("^'", belongTo) belongTo[quoted] <- regmatches(belongTo[quoted], regexpr("(?<=').*(?='!)", belongTo[quoted], perl = TRUE)) belongTo[!quoted] <- gsub("!\\$[A-Z0-9].*", "", belongTo[!quoted]) belongTo[!quoted] <- gsub("!#REF!.*", "", belongTo[!quoted]) return(belongTo) } getSharedStringsFromFile <- function(sharedStringsFile, isFile) { ## read in, get si tags, get t tag value and pull out all string nodes sharedStrings <- get_shared_strings(xmlFile = sharedStringsFile, isFile = isFile) ## read from file Encoding(sharedStrings) <- "UTF-8" z <- tolower(sharedStrings) sharedStrings[z == "true"] <- "TRUE" sharedStrings[z == "false"] <- "FALSE" z <- NULL ## effectivel remove z ## XML replacements sharedStrings <- replaceXMLEntities(sharedStrings) return(sharedStrings) } clean_names <- function(x, schar) { x <- gsub("^[[:space:]]+|[[:space:]]+$", "", x) x <- gsub("[[:space:]]+", schar, x) return(x) } mergeCell2mapping <- function(x) { refs <- regmatches(x, regexpr("(?<=ref=\")[A-Z0-9:]+", x, perl = TRUE)) refs <- strsplit(refs, split = ":") rows <- lapply(refs, function(r) { r <- as.integer(gsub(pattern = "[A-Z]", replacement = "", r, perl = TRUE)) seq(from = r[1], to = r[2], by = 1) }) cols <- lapply(refs, function(r) { r <- convertFromExcelRef(r) seq(from = r[1], to = r[2], by = 1) }) ## for each we grid.expand refs <- do.call("rbind", lapply(seq_along(rows), function(i) { tmp <- expand.grid("cols" = cols[[i]], "rows" = rows[[i]]) tmp$ref <- paste0(convert_to_excel_ref(cols = tmp$cols, LETTERS = LETTERS), tmp$rows) tmp$anchor_cell <- tmp$ref[1] return(tmp[, c("anchor_cell", "ref", "rows")]) })) refs <- refs[refs$anchor_cell != refs$ref, ] return(refs) } splitHeaderFooter <- function(x) { tmp <- gsub("<(/|)(odd|even|first)(Header|Footer)>(&|)", "", x, perl = TRUE) special_tags <- regmatches(tmp, regexpr("&[^LCR]", tmp)) if (length(special_tags) > 0) { for (i in seq_along(special_tags)) { tmp <- gsub(special_tags[i], sprintf("openxlsx__%s67298679", i), tmp, fixed = TRUE) } } tmp <- strsplit(tmp, split = "&")[[1]] if (length(special_tags) > 0) { for (i in seq_along(special_tags)) { tmp <- gsub(sprintf("openxlsx__%s67298679", i), special_tags[i], tmp, fixed = TRUE) } } res <- rep(list(NULL), 3) ind <- substr(tmp, 1, 1) == "L" if (any(ind)) { res[[1]] <- substring(tmp, 2)[ind] } ind <- substr(tmp, 1, 1) == "C" if (any(ind)) { res[[2]] <- substring(tmp, 2)[ind] } ind <- substr(tmp, 1, 1) == "R" if (any(ind)) { res[[3]] <- substring(tmp, 2)[ind] } res } getFile <- function(xlsxFile) { ## Is this a file or URL (code taken from read.table()) on.exit(try(close(fl), silent = TRUE), add = TRUE) fl <- file(description = xlsxFile) ## If URL download if ("url" %in% class(fl)) { tmpFile <- tempfile(fileext = ".xlsx") download.file(url = xlsxFile, destfile = tmpFile, cacheOK = FALSE, mode = "wb", quiet = TRUE) xlsxFile <- tmpFile } return(xlsxFile) } # Rotate the 15-bit integer by n bits to the hashPassword <- function(password) { # password limited to 15 characters chars <- head(strsplit(password, "")[[1]], 15) # See OpenOffice's documentation of the Excel format: http://www.openoffice.org/sc/excelfileformat.pdf # Start from the last character and for each character # - XOR hash with the ASCII character code # - rotate hash (16 bits) one bit to the left # Finally, XOR hash with 0xCE4B and XOR with password length # Output as hex (uppercase) rotate16bit <- function(hash, n = 1) { bitwOr(bitwAnd(bitwShiftR(hash, 15 - n), 0x01), bitwAnd(bitwShiftL(hash, n), 0x7fff)) } hash <- Reduce(function(char, h) { h <- bitwXor(h, as.integer(charToRaw(char))) rotate16bit(h, 1) }, chars, 0, right = TRUE) hash <- bitwXor(bitwXor(hash, length(chars)), 0xCE4B) format(as.hexmode(hash), upper.case = TRUE) } readUTF8 <- function(x) { readLines(x, warn = FALSE, encoding = "UTF-8") } openxlsx/R/StyleClass.R0000644000176200001440000001742514155600363014560 0ustar liggesusers #' @include class_definitions.R Style$methods(initialize = function() { fontName <<- NULL fontColour <<- NULL fontSize <<- NULL fontFamily <<- NULL fontScheme <<- NULL fontDecoration <<- NULL borderTop <<- NULL borderLeft <<- NULL borderRight <<- NULL borderBottom <<- NULL borderTopColour <<- NULL borderLeftColour <<- NULL borderRightColour <<- NULL borderBottomColour <<- NULL borderDiagonal <<- NULL borderDiagonalColour <<- NULL borderDiagonalUp <<- FALSE borderDiagonalDown <<- FALSE halign <<- NULL valign <<- NULL indent <<- NULL textRotation <<- NULL numFmt <<- NULL fill <<- NULL wrapText <<- NULL hidden <<- NULL locked <<- NULL xfId <<- NULL }) mergeStyle <- function(oldStyle, newStyle) { ## This function is used to merge an existing cell style with a new style to create a stacked style. oldStyle <- oldStyle$copy() if (!is.null(newStyle$fontName)) { oldStyle$fontName <- newStyle$fontName } if (!is.null(newStyle$fontColour)) { oldStyle$fontColour <- newStyle$fontColour } if (!is.null(newStyle$fontSize)) { oldStyle$fontSize <- newStyle$fontSize } if (!is.null(newStyle$fontFamily)) { oldStyle$fontFamily <- newStyle$fontFamily } if (!is.null(newStyle$fontScheme)) { oldStyle$fontScheme <- newStyle$fontScheme } if (length(newStyle$fontDecoration) > 0) { if (length(oldStyle$fontDecoration) == 0) { oldStyle$fontDecoration <- newStyle$fontDecoration } else { oldStyle$fontDecoration <- c(oldStyle$fontDecoration, newStyle$fontDecoration) } } ## borders if (!is.null(newStyle$borderTop)) { oldStyle$borderTop <- newStyle$borderTop } if (!is.null(newStyle$borderLeft)) { oldStyle$borderLeft <- newStyle$borderLeft } if (!is.null(newStyle$borderRight)) { oldStyle$borderRight <- newStyle$borderRight } if (!is.null(newStyle$borderBottom)) { oldStyle$borderBottom <- newStyle$borderBottom } if (!is.null(newStyle$borderDiagonal)) { oldStyle$borderDiagonal <- newStyle$borderDiagonal } oldStyle$borderDiagonalUp <- newStyle$borderDiagonalUp oldStyle$borderDiagonalDown <- newStyle$borderDiagonalDown if (!is.null(newStyle$borderTopColour)) { oldStyle$borderTopColour <- newStyle$borderTopColour } if (!is.null(newStyle$borderLeftColour)) { oldStyle$borderLeftColour <- newStyle$borderLeftColour } if (!is.null(newStyle$borderRightColour)) { oldStyle$borderRightColour <- newStyle$borderRightColour } if (!is.null(newStyle$borderBottomColour)) { oldStyle$borderBottomColour <- newStyle$borderBottomColour } ## other if (!is.null(newStyle$halign)) { oldStyle$halign <- newStyle$halign } if (!is.null(newStyle$valign)) { oldStyle$valign <- newStyle$valign } if (!is.null(newStyle$indent)) { oldStyle$indent <- newStyle$indent } if (!is.null(newStyle$textRotation)) { oldStyle$textRotation <- newStyle$textRotation } if (!is.null(newStyle$numFmt)) { oldStyle$numFmt <- newStyle$numFmt } if (!is.null(newStyle$fill)) { oldStyle$fill <- newStyle$fill } if (!is.null(newStyle$wrapText)) { oldStyle$wrapText <- newStyle$wrapText } if (!is.null(newStyle$locked)) { oldStyle$locked <- newStyle$locked } if (!is.null(newStyle$hidden)) { oldStyle$hidden <- newStyle$hidden } if (!is.null(newStyle$xfId)) { oldStyle$xfId <- newStyle$xfId } return(oldStyle) } Style$methods(show = function(print = TRUE) { numFmtMapping <- list( list("numFmtId" = 0), list("numFmtId" = 2), list("numFmtId" = 164), list("numFmtId" = 44), list("numFmtId" = 14), list("numFmtId" = 167), list("numFmtId" = 10), list("numFmtId" = 11), list("numFmtId" = 49) ) validNumFmt <- c("GENERAL", "NUMBER", "CURRENCY", "ACCOUNTING", "DATE", "TIME", "PERCENTAGE", "SCIENTIFIC", "TEXT") if (!is.null(numFmt)) { if (as.integer(numFmt$numFmtId) %in% unlist(numFmtMapping)) { numFmtStr <- validNumFmt[unlist(numFmtMapping) == as.integer(numFmt$numFmtId)] } else { numFmtStr <- sprintf('"%s"', numFmt$formatCode) } } else { numFmtStr <- "GENERAL" } borders <- c(sprintf("Top: %s", borderTop), sprintf("Bottom: %s", borderBottom), sprintf("Left: %s", borderLeft), sprintf("Right: %s", borderRight)) borderColours <- gsub("^FF", "#", c(borderTopColour, borderBottomColour, borderLeftColour, borderRightColour)) fgFill <- fill$fillFg bgFill <- fill$fillBg styleShow <- "A custom cell style. \n\n" styleShow <- append(styleShow, sprintf("Cell formatting: %s \n", numFmtStr)) ## numFmt styleShow <- append(styleShow, sprintf("Font name: %s \n", fontName[[1]])) ## Font name styleShow <- append(styleShow, sprintf("Font size: %s \n", fontSize[[1]])) ## Font size styleShow <- append(styleShow, sprintf("Font colour: %s \n", gsub("^FF", "#", fontColour[[1]]))) ## Font colour ## Font decoration if (length(fontDecoration) > 0) { styleShow <- append(styleShow, sprintf("Font decoration: %s \n", paste(fontDecoration, collapse = ", "))) } if (length(borders) > 0) { styleShow <- append(styleShow, sprintf("Cell borders: %s \n", paste(borders, collapse = ", "))) ## Cell borders styleShow <- append(styleShow, sprintf("Cell border colours: %s \n", paste(borderColours, collapse = ", "))) ## Cell borders } if (!is.null(halign)) { styleShow <- append(styleShow, sprintf("Cell horz. align: %s \n", halign)) } ## Cell horizontal alignment if (!is.null(valign)) { styleShow <- append(styleShow, sprintf("Cell vert. align: %s \n", valign)) } ## Cell vertical alignment if (!is.null(indent)) { styleShow <- append(styleShow, sprintf("Cell indent: %s \n", indent)) } ## Cell indent if (!is.null(textRotation)) { styleShow <- append(styleShow, sprintf("Cell text rotation: %s \n", textRotation)) } ## Cell text rotation ## Cell fill colour if (length(fgFill) > 0) { styleShow <- append(styleShow, sprintf("Cell fill foreground: %s \n", paste(paste0(names(fgFill), ": ", sub("^FF", "#", fgFill)), collapse = ", "))) } if (length(bgFill) > 0) { styleShow <- append(styleShow, sprintf("Cell fill background: %s \n", paste(paste0(names(bgFill), ": ", sub("^FF", "#", bgFill)), collapse = ", "))) } if (!is.null(locked)) { styleShow <- append(styleShow, sprintf("Cell protection: %s \n", locked)) } ## Cell protection if (!is.null(hidden)) { styleShow <- append(styleShow, sprintf("Cell formula hidden: %s \n", hidden)) } ## Cell formula hidden styleShow <- append(styleShow, sprintf("wraptext: %s", wrapText)) ## wrap text styleShow <- c(styleShow, "\n\n") if (print) { cat(styleShow) } return(invisible(styleShow)) }) Style$methods(as.list = function() { l <- list( "fontName" = fontName, "fontColour" = fontColour, "fontSize" = fontSize, "fontFamily" = fontFamily, "fontScheme" = fontScheme, "fontDecoration" = fontDecoration, "borderTop" = borderTop, "borderLeft" = borderLeft, "borderRight" = borderRight, "borderBottom" = borderBottom, "borderTopColour" = borderTopColour, "borderLeftColour" = borderLeftColour, "borderRightColour" = borderRightColour, "borderBottomColour" = borderBottomColour, "halign" = halign, "valign" = valign, "indent" = indent, "textRotation" = textRotation, "numFmt" = numFmt, "fillFg" = fill$fillFg, "fillBg" = fill$fillBg, "wrapText" = wrapText, "locked" = locked, "hidden" = hidden, "xfId" = xfId ) l[sapply(l, length) > 0] }) openxlsx/R/onUnload.R0000644000176200001440000000012214155600363014233 0ustar liggesusers.onUnload <- function(libpath) { library.dynam.unload("openxlsx", libpath) } openxlsx/R/sysdata.rda0000644000176200001440000001172114155600363014500 0ustar liggesusersBZh91AY&SY tcPfp"M.7}=k׊0 BaY$bHFhHWI֨fJ(T Nhp%(tW1\dq AcutIB!8hYNu$Yeap+TaY% Rڶb,3|733s36Ĥg@DGPgvꊊ{ο_KۗS}$QD$E,=s<;o79i 1nYC%u:1glT]vu3~F cg,,`lYgQܚR([V*9ߝcR{2^^R\1kd_zGu|b|eN>UKK9VE^F`&j;W8YcVMI6fEY(b1nQqMgjr` V*.MYzRbLY%uUE]b[.`*]Uٶ1`Fo⫵gi`g<e-moflA$jҶTk*5;,rfKEL3X-H-+lVfGReBxx4ZM`)J  dB$$m;F%TJs,Z֥MjKSdͤífAc%IHD &*u Ja"\šQ W i6jl(*]66Mh4ZI biX w:(EKP lM?m6#fĚY5CM*K6 ] ^IOcܕmb2UcҲf5H i@Ҫ(ZFd)HH,CBIA)͉fKsS1ZpocD@q+&DP a E^[(2TJ)TN dҴ4 `a*6DcLJjdIHS½ݽs $qh@kW[2-(a붶䦥"ͦfQ&Ě <֛7/{nRS6Z嵚)TRKZײ"lS&Meahi$Wy G&XqFO`UҪlʦ)ͥLiH-ݭn3YXElify^iVM/j/g~+b6͝6C$2G%,kK*Y bVXi,Sa51d֚b^=[ǥ޻wʪa$Қ[)s\Ii-h4R4TIih (&aӼBLcb(mUkoZ1V6 #DI5554Lyjn lLu PPbqH76am*jQ2'%m,p"G^ݼhJ1yֽTjWZV[W-3mمfRu$Fr "`DRHhƹZEݣFbMR*ihӷy{RIn2Uamk6RZ]sT -jbL۩PttZtݛ7|*Q$LJݦۣr 3SP@R=U+LrR#"sz  s G*/uNy{zPmVĉrک6w!QҝtdKvo^%]^z5HZ4!ɋ| M:lND}y4sF:nj>W몯'LL #vl">G#y䠢] ;r܋Q];\p(h0x]O*2s$ͽD F,آCae2Ui8M~4JJ.`ӊY5(ԣb4zK62)A)mfĪ!||Y $ͽFMFhز`Pҋ.DFM>IS6 ,(GscysdYI -EWJ?'sԫޙN ]9Z:r̎v2\4j{1JyZI((JQ>⪞{X7XvlJ!Fz%_FIw<ش4$IF&LݯEX9I3L*jvօ׵r|/[:(QHToKͯ+v2bƪ.^P{O}>\qI9t5ls9Y ڋ$|֯[{Dd oU(棩J9gnRl'3!y:@(AwU9@R8EKG+V3F$UTh"y`&L0``JU,EQbv[X%bś2Q]IYURr7JVdr;9(|l^^̣~=QʠsG{_U_՛7CDDb;NE ܋^7+/+y>8w{`:Y>&nCi+7Y}BmET{0wlIY(KG(QG"<Ė]QE8(!3UCҫ'#UZ ;ǸoqK6oy%Fu#ݞϪ]QEҌCR>D]0س@Q{#i$  A@>D /_PD˿]B@dgl4openxlsx/R/baseXML.R0000644000176200001440000006107514155600363013765 0ustar liggesusers genBaseContent_Type <- function() { c( '', '', '', '', '', '', '', '', '' ) } genBaseShapeVML <- function(clientData, id) { if (grepl("visible", clientData, ignore.case = TRUE)) { visible <- "visible" } else { visible <- "hidden" } paste0( sprintf('', visible), '
', clientData, "" ) } genClientData <- function(col, row, visible, height, width) { txt <- sprintf( '%s, 15, %s, 10, %s, 147, %s, 18False%s%s', col, row - 2L, col + width - 1L, row + height - 1L, row - 1L, col - 1L ) if (visible) { txt <- paste0(txt, "") } txt <- paste0(txt, "") return(txt) } # genBaseRels <- function(){ # # ' # # ' # # } # # # genBaseApp <- function(){ # list('Microsoft Excel') # } genBaseCore <- function(creator = "", title = NULL, subject = NULL, category = NULL) { core <- '' core <- stri_c(core, sprintf("%s", replaceIllegalCharacters(creator))) core <- stri_c(core, sprintf("%s", replaceIllegalCharacters(creator))) core <- stri_c(core, sprintf('%s', format(Sys.time(), "%Y-%m-%dT%H:%M:%SZ"))) if (!is.null(title)) { core <- stri_c(core, sprintf("%s", replaceIllegalCharacters(title))) } if (!is.null(subject)) { core <- stri_c(core, sprintf("%s", replaceIllegalCharacters(subject))) } if (!is.null(category)) { core <- stri_c(core, sprintf("%s", replaceIllegalCharacters(category))) } core <- stri_c(core, "") return(core) } # # addAuthor <- function(wb,Author = NULL){ # # if (!is.null(Author)) { # current_creator <- # stri_match(wb$core, regex = "(.*?)")[1, 2] # wb$core <- # stri_replace_all_fixed( # wb$core, # pattern = current_creator, # replacement = stri_c(current_creator, Author, sep = ";") # ) # } # # # } # # # setAuthor <- function(wb,Author = NULL){ # # if (!is.null(Author)) { # current_creator <- # stri_match(wb$core, regex = "(.*?)")[1, 2] # wb$core <- # stri_replace_all_fixed( # wb$core, # pattern = current_creator, # replacement = Author # ) # } # # # } # # setLastModifiedBy <- function(wb,ModifiedBy=NULL){ # # if (!is.null(addmodifier)) { # current_lastmodifier <- # stri_match(wb$core, regex = "(.*?)")[1, 2] # wb$core <- # stri_replace_all_fixed( # wb$core, # pattern = current_lastmodifier, # replacement = ModifiedBy # ) # } # # # } # # # # # setBaseCore <- function(core,setcreator="",setmodifier="", # title = NULL, subject = NULL, category = NULL){ # # # core <- c(core, sprintf('%s', setcreator)) # core <- c(core, sprintf('%s', format(Sys.time(), "%Y-%m-%dT%H:%M:%SZ"))) # # if(!is.null(title)) # core <- c(core, sprintf('%s', replaceIllegalCharacters(title))) # # if(!is.null(subject)) # core <- c(core, sprintf('%s', replaceIllegalCharacters(subject))) # # if(!is.null(category)) # core <- c(core, sprintf('%s', replaceIllegalCharacters(category))) # # core <- c(core, '') # # return(core) # # } genBaseWorkbook.xml.rels <- function() { c( '', '', '' ) } genBaseWorkbook <- function() { list( workbookPr = '', workbookProtection = NULL, bookViews = '', sheets = NULL, externalReferences = NULL, definedNames = NULL, calcPr = NULL, pivotCaches = NULL, extLst = NULL ) } genBaseSheetRels <- function(sheetInd) { c( sprintf('', sheetInd), sprintf('', sheetInd), sprintf('', sheetInd) ) } genBaseStyleSheet <- function(dxfs = NULL, tableStyles = NULL, extLst = NULL) { list( numFmts = NULL, fonts = c(''), fills = c( '', '' ), borders = c(""), cellStyleXfs = c(''), cellXfs = c(''), cellStyles = c(''), dxfs = dxfs, tableStyles = tableStyles, indexedColors = NULL, extLst = extLst ) } genBasePic <- function(imageNo) { sprintf(' ', imageNo, imageNo, imageNo) } genBaseTheme <- function() { ' ' } genPrinterSettings <- function() { "5c 00 5c 00 41 00 55 00 43 00 41 00 4c 00 50 00 52 00 4f 00 44 00 46 00 50 00 5c 00 4c 00 31 00 34 00 78 00 65 00 72 00 6f 00 78 00 31 00 20 00 2d 00 20 00 58 00 65 00 72 00 6f 00 00 00 00 00 01 04 00 52 dc 00 5c 05 13 ff 81 07 02 00 09 00 9a 0b 34 08 64 00 01 00 0f 00 2c 01 02 00 02 00 2c 01 03 00 01 00 41 00 34 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 52 c0 21 46 00 58 00 20 00 41 00 70 00 65 00 6f 00 73 00 50 00 6f 00 72 00 74 00 2d 00 49 00 49 00 49 00 20 00 43 00 34 00 34 00 30 00 30 00 20 00 50 00 43 00 4c 00 20 00 36 00 00 00 00 00 00 00 00 00 4e 08 a0 13 40 09 08 00 0b 01 64 00 01 00 07 00 01 00 00 00 00 00 00 00 00 00 07 00 01 00 08 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 08 08 00 08 08 08 00 08 08 08 00 08 08 08 00 00 01 03 00 02 02 00 01 02 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 02 02 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bc 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 05 00 00 00 00 00 00 08 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0b 96 00 00 00 c8 00 01 01 01 01 01 01 01 01 01 01 01 01 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bc 02 00 00 00 00 00 00 00 00 02 00 41 00 72 00 69 00 61 00 6c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 70 5f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" } gen_databar_extlst <- function(guid, sqref, posColour, negColour, values, border, gradient) { xml <- sprintf('', guid, border, gradient) if (is.null(values)) { xml <- sprintf(' %s %s', xml, posColour, negColour, negColour, sqref) } else { xml <- sprintf(' %s %s%s %s', xml, values[[1]], values[[2]], posColour, negColour, negColour, sqref) } return(xml) } contentTypePivotXML <- function(i) { c( sprintf('', i), sprintf('', i), sprintf('', i) ) } contentTypeSlicerCacheXML <- function(i) { c( sprintf('', i), sprintf('', i) ) } genBaseSlicerXML <- function() { ' ' } genSlicerCachesExtLst <- function(i) { paste0( ' ', paste(sprintf('', i), collapse = ""), "" ) } openxlsx/R/build_workbook.R0000644000176200001440000001350714155600363015503 0ustar liggesusers#' Build Workbook #' #' Build a workbook from a data.frame or named list #' #' @details #' This function can be used as shortcut to create a workbook object from a #' data.frame or named list. If names are available in the list they will be #' used as the worksheet names. The parameters in `...` are collected #' and passed to [writeData()] or [writeDataTable()] to #' initially create the Workbook objects then appropriate parameters are #' passed to [setColWidths()]. #' #' @param x A data.frame or a (named) list of objects that can be handled by #' [writeData()] or [writeDataTable()] to write to file #' @param asTable If `TRUE` will use [writeDataTable()] rather #' than [writeData()] to write `x` to the file (default: #' `FALSE`) #' @param ... Additional arguments passed to [writeData()], #' [writeDataTable()], [setColWidths()] (see Optional #' Parameters) #' @author Jordan Mark Barbone #' @returns A Workbook object #' #' @details #' columns of x with class Date or POSIXt are automatically #' styled as dates and datetimes respectively. #' #' @section Optional Parameters: #' #' **createWorkbook Parameters** #' \itemize{ #' \item{**creator**}{ A string specifying the workbook author} #' } #' #' **addWorksheet Parameters** #' \itemize{ #' \item{**sheetName**}{ Name of the worksheet} #' \item{**gridLines**}{ A logical. If `FALSE`, the worksheet grid lines will be hidden.} #' \item{**tabColour**}{ Colour of the worksheet tab. A valid colour (belonging to colours()) #' or a valid hex colour beginning with "#".} #' \item{**zoom**}{ A numeric between 10 and 400. Worksheet zoom level as a percentage.} #' } #' #' **writeData/writeDataTable Parameters** #' \itemize{ #' \item{**startCol**}{ A vector specifying the starting column(s) to write df} #' \item{**startRow**}{ A vector specifying the starting row(s) to write df} #' \item{**xy**}{ An alternative to specifying startCol and startRow individually. #' A vector of the form c(startCol, startRow)} #' \item{**colNames or col.names**}{ If `TRUE`, column names of x are written.} #' \item{**rowNames or row.names**}{ If `TRUE`, row names of x are written.} #' \item{**headerStyle**}{ Custom style to apply to column names.} #' \item{**borders**}{ Either "surrounding", "columns" or "rows" or NULL. If "surrounding", a border is drawn around the #' data. If "rows", a surrounding border is drawn a border around each row. If "columns", a surrounding border is drawn with a border #' between each column. If "`all`" all cell borders are drawn.} #' \item{**borderColour**}{ Colour of cell border} #' \item{**borderStyle**}{ Border line style.} #' \item{**keepNA**} {If `TRUE`, NA values are converted to #N/A (or `na.string`, if not NULL) in Excel, else NA cells will be empty. Defaults to FALSE.} #' \item{**na.string**} {If not NULL, and if `keepNA` is `TRUE`, NA values are converted to this string in Excel. Defaults to NULL.} #' } #' #' **freezePane Parameters** #' \itemize{ #' \item{**firstActiveRow**} {Top row of active region to freeze pane.} #' \item{**firstActiveCol**} {Furthest left column of active region to freeze pane.} #' \item{**firstRow**} {If `TRUE`, freezes the first row (equivalent to firstActiveRow = 2)} #' \item{**firstCol**} {If `TRUE`, freezes the first column (equivalent to firstActiveCol = 2)} #' } #' #' **colWidths Parameters** #' \itemize{ #' \item{**colWidths**} {May be a single value for all columns (or "auto"), or a list of vectors that will be recycled for each sheet (see examples)} #' } #' #' @examples #' x <- data.frame(a = 1, b = 2) #' wb <- buildWorkbook(x) #' #' y <- list(a = x, b = x, c = x) #' buildWorkbook(y, asTable = TRUE) #' buildWorkbook(y, asTable = TRUE, tableStyle = "TableStyleLight8") #' #' @seealso [write.xlsx()] #' #' @export buildWorkbook <- function(x, asTable = FALSE, ...) { if (!is.logical(asTable)) { stop("asTable must be a logical.") } params <- list(...) isList <- inherits(x, "list") if (isList) { params[["sheetName"]] <- params[["sheetName"]] %||% names(x) %||% paste0("Sheet ", seq_along(x)) } ## create new Workbook object wb <- do_call_params(createWorkbook, params) ## If a list is supplied write to individual worksheets using names if available if (isList) { do_call_params(addWorksheet, params, wb = list(wb), .map = TRUE) } else { params[["sheetName"]] <- params[["sheetName"]] %||% "Sheet 1" do_call_params(addWorksheet, params, wb = wb) } params[["sheet"]] <- params[["sheet"]] %||% params[["sheetName"]] # write Data if (asTable) { do_call_params(writeDataTable, params, x = x, wb = list(wb), .map = TRUE) } else { do_call_params(writeData, params, x = x, wb = wb, .map = TRUE) } do_setColWidths(wb, x, params, isList) do_call_params(freezePane, params, wb = list(wb), .map = TRUE) wb } do_setColWidths <- function(wb, x, params, isList) { if (!isList) { x <- list(x) } params[["startCol"]] <- params[["startCol"]] %||% 1 params[["startCol"]] <- rep_len(list(params[["startCol"]]), length.out = length(x)) params[["colWidths"]] <- params[["colWidths"]] %||% "" params[["colWidths"]] <- rep_len(as.list(params[["colWidths"]]), length.out = length(x)) for (i in seq_along(wb[["worksheets"]])) { if (identical(params[["colWidths"]][[i]], "auto")) { setColWidths( wb, sheet = i, cols = seq_along(x[[i]]) + params[["startCol"]][[i]] - 1L, widths = "auto" ) } else if (!identical(params[["colWidths"]][[i]], "")) { setColWidths( wb, sheet = i, cols = seq_along(x[[i]]) + params[["startCol"]][[i]] - 1L, widths = params[["colWidths"]][[i]] ) } } wb } openxlsx/R/utils.R0000644000176200001440000000250714155600363013625 0ustar liggesusers#' If NULL then ... #' #' Replace NULL #' #' @param x A value to check #' @param y A value to substitute if x is null #' @examples #' \dontrun{ #' x <- NULL #' x <- x %||% "none" #' x <- x %||% NA #' } #' #' @name if_null_then `%||%` <- function(x, y) if (is.null(x)) y else x is_not_class <- function(x, class) { !(inherits(x, class) | is.null(x)) } is_true_false <- function(x) { is.logical(x) && length(x) == 1L && !is.na(x) } do_call_params <- function(fun, params, ..., .map = FALSE) { fun <- match.fun(fun) call_params <- c(list(...), params[names(params) %in% names(formals(fun))]) call_params <- lapply(call_params, function(x) if (is.object(x)) list(x) else x) call_fun <- if (.map) { function(...) mapply(fun, ..., MoreArgs = NULL, SIMPLIFY = FALSE, USE.NAMES = FALSE) } else { fun } do.call(call_fun, call_params) } # sets temporary options # option() returns the original values get_set_options <- function() { options( # increase scipen to avoid writing in scientific scipen = 200, OutDec = ".", digits = 22 ) } #' helper function to create tempory directory for testing purpose #' @param name for the temp file #' @export temp_xlsx <- function(name = "temp_xlsx") { tempfile(pattern = paste0(name, "_"), fileext = ".xlsx") } openxlsx/R/zzz.R0000644000176200001440000000025614155600363013321 0ustar liggesusers.onAttach <- function(libname, pkgname) { op <- options() toset <- !(names(op.openxlsx) %in% names(op)) if (any(toset)) { options(op.openxlsx[toset]) } } openxlsx/R/writeData.R0000644000176200001440000004336014155633377014426 0ustar liggesusers#' @name writeData #' @title Write an object to a worksheet #' @author Alexander Walker #' @import stringi #' @description Write an object to worksheet with optional styling. #' @param wb A Workbook object containing a worksheet. #' @param sheet The worksheet to write to. Can be the worksheet index or name. #' @param x Object to be written. For classes supported look at the examples. #' @param startCol A vector specifying the starting column to write to. #' @param startRow A vector specifying the starting row to write to. #' @param array A bool if the function written is of type array #' @param xy An alternative to specifying `startCol` and #' `startRow` individually. A vector of the form #' `c(startCol, startRow)`. #' @param colNames If `TRUE`, column names of x are written. #' @param rowNames If `TRUE`, data.frame row names of x are written. #' @param row.names,col.names Deprecated, please use `rowNames`, `colNames` instead #' @param headerStyle Custom style to apply to column names. #' @param borders Either "`none`" (default), "`surrounding`", #' "`columns`", "`rows`" or *respective abbreviations*. If #' "`surrounding`", a border is drawn around the data. If "`rows`", #' a surrounding border is drawn with a border around each row. If #' "`columns`", a surrounding border is drawn with a border between #' each column. If "`all`" all cell borders are drawn. #' @param borderColour Colour of cell border. A valid colour (belonging to `colours()` or a hex colour code, eg see [here](https://www.w3schools.com/web-design/color-picker/)). #' @param borderStyle Border line style #' \itemize{ #' \item{**none**}{ no border} #' \item{**thin**}{ thin border} #' \item{**medium**}{ medium border} #' \item{**dashed**}{ dashed border} #' \item{**dotted**}{ dotted border} #' \item{**thick**}{ thick border} #' \item{**double**}{ double line border} #' \item{**hair**}{ hairline border} #' \item{**mediumDashed**}{ medium weight dashed border} #' \item{**dashDot**}{ dash-dot border} #' \item{**mediumDashDot**}{ medium weight dash-dot border} #' \item{**dashDotDot**}{ dash-dot-dot border} #' \item{**mediumDashDotDot**}{ medium weight dash-dot-dot border} #' \item{**slantDashDot**}{ slanted dash-dot border} #' } #' @param withFilter If `TRUE` or `NA`, add filters to the column name row. NOTE can only have one filter per worksheet. #' @param keepNA If `TRUE`, NA values are converted to #N/A (or `na.string`, if not NULL) in Excel, else NA cells will be empty. #' @param na.string If not NULL, and if `keepNA` is `TRUE`, NA values are converted to this string in Excel. #' @param name If not NULL, a named region is defined. #' @param sep Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep). #' @seealso [writeDataTable()] #' @export writeData #' @details Formulae written using writeFormula to a Workbook object will not get picked up by read.xlsx(). #' This is because only the formula is written and left to Excel to evaluate the formula when the file is opened in Excel. #' @rdname writeData #' @return invisible(0) #' @examples #' #' ## See formatting vignette for further examples. #' #' ## Options for default styling (These are the defaults) #' options("openxlsx.borderColour" = "black") #' options("openxlsx.borderStyle" = "thin") #' options("openxlsx.dateFormat" = "mm/dd/yyyy") #' options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") #' options("openxlsx.numFmt" = NULL) #' #' ## Change the default border colour to #4F81BD #' options("openxlsx.borderColour" = "#4F81BD") #' #' #' ##################################################################################### #' ## Create Workbook object and add worksheets #' wb <- createWorkbook() #' #' ## Add worksheets #' addWorksheet(wb, "Cars") #' addWorksheet(wb, "Formula") #' #' #' x <- mtcars[1:6, ] #' writeData(wb, "Cars", x, startCol = 2, startRow = 3, rowNames = TRUE) #' #' ##################################################################################### #' ## Bordering #' #' writeData(wb, "Cars", x, #' rowNames = TRUE, startCol = "O", startRow = 3, #' borders = "surrounding", borderColour = "black" #' ) ## black border #' #' writeData(wb, "Cars", x, #' rowNames = TRUE, #' startCol = 2, startRow = 12, borders = "columns" #' ) #' #' writeData(wb, "Cars", x, #' rowNames = TRUE, #' startCol = "O", startRow = 12, borders = "rows" #' ) #' #' #' ##################################################################################### #' ## Header Styles #' #' hs1 <- createStyle( #' fgFill = "#DCE6F1", halign = "CENTER", textDecoration = "italic", #' border = "Bottom" #' ) #' #' writeData(wb, "Cars", x, #' colNames = TRUE, rowNames = TRUE, startCol = "B", #' startRow = 23, borders = "rows", headerStyle = hs1, borderStyle = "dashed" #' ) #' #' #' hs2 <- createStyle( #' fontColour = "#ffffff", fgFill = "#4F80BD", #' halign = "center", valign = "center", textDecoration = "bold", #' border = "TopBottomLeftRight" #' ) #' #' writeData(wb, "Cars", x, #' colNames = TRUE, rowNames = TRUE, #' startCol = "O", startRow = 23, borders = "columns", headerStyle = hs2 #' ) #' #' #' #' #' ##################################################################################### #' ## Hyperlinks #' ## - vectors/columns with class 'hyperlink' are written as hyperlinks' #' #' v <- rep("https://CRAN.R-project.org/", 4) #' names(v) <- paste0("Hyperlink", 1:4) # Optional: names will be used as display text #' class(v) <- "hyperlink" #' writeData(wb, "Cars", x = v, xy = c("B", 32)) #' #' #' ##################################################################################### #' ## Formulas #' ## - vectors/columns with class 'formula' are written as formulas' #' #' df <- data.frame( #' x = 1:3, y = 1:3, #' z = paste0(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = " + "), #' stringsAsFactors = FALSE #' ) #' #' class(df$z) <- c(class(df$z), "formula") #' #' writeData(wb, sheet = "Formula", x = df) #' #' #' ##################################################################################### #' ## Save workbook #' ## Open in excel without saving file: openXL(wb) #' \dontrun{ #' saveWorkbook(wb, "writeDataExample.xlsx", overwrite = TRUE) #' } writeData <- function( wb, sheet, x, startCol = 1, startRow = 1, array = FALSE, xy = NULL, colNames = TRUE, rowNames = FALSE, headerStyle = openxlsx_getOp("headerStyle"), borders = openxlsx_getOp("borders", "none"), borderColour = openxlsx_getOp("borderColour", "black"), borderStyle = openxlsx_getOp("borderStyle", "thin"), withFilter = openxlsx_getOp("withFilter", FALSE), keepNA = openxlsx_getOp("keepNA", FALSE), na.string = openxlsx_getOp("na.string"), name = NULL, sep = ", ", col.names, row.names ) { x <- force(x) op <- get_set_options() on.exit(options(op), add = TRUE) if (!missing(row.names)) { warning("Please use 'rowNames' instead of 'row.names'", call. = FALSE) rowNames <- row.names } if (!missing(col.names)) { warning("Please use 'colNames' instead of 'col.names'", call. = FALSE) colNames <- col.names } # Set NULLs borders <- borders %||% "none" borderColour <- borderColour %||% "black" borderStyle <- borderStyle %||% "thin" withFilter <- withFilter %||% FALSE keepNA <- keepNA %||% FALSE if (is.null(x)) { return(invisible(0)) } ## All input conversions/validations if (!is.null(xy)) { if (length(xy) != 2) { stop("xy parameter must have length 2") } startCol <- xy[[1]] startRow <- xy[[2]] } ## convert startRow and startCol if (!is.numeric(startCol)) { startCol <- convertFromExcelRef(startCol) } startRow <- as.integer(startRow) assert_class(wb, "Workbook") assert_true_false(colNames) assert_true_false(rowNames) assert_character1(sep) assert_class(headerStyle, "Style", or_null = TRUE) ## borderColours validation borderColour <- validateColour(borderColour, "Invalid border colour") borderStyle <- validateBorderStyle(borderStyle)[[1]] ## special case - vector of hyperlinks hlinkNames <- NULL if (inherits(x, "hyperlink")) { hlinkNames <- names(x) colNames <- FALSE } ## special case - formula if (inherits(x, "formula")) { x <- data.frame("X" = x, stringsAsFactors = FALSE) class(x[[1]]) <- ifelse(array, "array_formula", "formula") colNames <- FALSE } ## named region if (!is.null(name)) { ## validate name ex_names <- regmatches(wb$workbook$definedNames, regexpr('(?<=name=")[^"]+', wb$workbook$definedNames, perl = TRUE)) ex_names <- replaceXMLEntities(ex_names) if (name %in% ex_names) { stop(sprintf("Named region with name '%s' already exists!", name)) } else if (grepl("^[A-Z]{1,3}[0-9]+$", name)) { stop("name cannot look like a cell reference.") } } if (is.vector(x) | is.factor(x) | inherits(x, "Date")) { colNames <- FALSE } ## this will go to coerce.default and rowNames will be ignored ## Coerce to data.frame x <- openxlsxCoerce(x = x, rowNames = rowNames) nCol <- ncol(x) nRow <- nrow(x) ## If no rows and not writing column names return as nothing to write if (nRow == 0 & !colNames) { return(invisible(0)) } ## If no columns and not writing row names return as nothing to write if (nCol == 0 & !rowNames) { return(invisible(0)) } colClasses <- lapply(x, function(x) tolower(class(x))) colClasss2 <- colClasses colClasss2[vapply( colClasses, function(i) inherits(i, "formula") & inherits(i, "hyperlink"), NA )] <- "formula" if (is.numeric(sheet)) { sheetX <- wb$validateSheet(sheet) } else { sheetX <- wb$validateSheet(replaceXMLEntities(sheet)) sheet <- replaceXMLEntities(sheet) } if (wb$isChartSheet[[sheetX]]) { stop("Cannot write to chart sheet.") } ## Check not overwriting existing table headers wb$check_overwrite_tables( sheet = sheet, new_rows = c(startRow, startRow + nRow - 1L + colNames), new_cols = c(startCol, startCol + nCol - 1L), check_table_header_only = TRUE, error_msg = "Cannot overwrite table headers. Avoid writing over the header row or see getTables() & removeTables() to remove the table object." ) ## write autoFilter, can only have a single filter per worksheet if (withFilter) { coords <- data.frame( x = c(startRow, startRow + nRow + colNames - 1L), y = c(startCol, startCol + nCol - 1L) ) ref <- stri_join(getCellRefs(coords), collapse = ":") wb$worksheets[[sheetX]]$autoFilter <- sprintf('', ref) l <- convert_to_excel_ref(cols = unlist(coords[, 2]), LETTERS = LETTERS) dfn <- sprintf("'%s'!%s", names(wb)[sheetX], stri_join("$", l, "$", coords[, 1], collapse = ":")) dn <- sprintf('', sheetX - 1L, dfn) if (length(wb$workbook$definedNames) > 0) { ind <- grepl('name="_xlnm._FilterDatabase"', wb$workbook$definedNames) if (length(ind) > 0) { wb$workbook$definedNames[ind] <- dn } } else { wb$workbook$definedNames <- dn } } ## write data.frame wb$writeData( df = x, colNames = colNames, sheet = sheet, startCol = startCol, startRow = startRow, colClasses = colClasss2, hlinkNames = hlinkNames, keepNA = keepNA, na.string = na.string, list_sep = sep ) ## header style if (inherits(headerStyle, "Style") & colNames) { addStyle( wb = wb, sheet = sheet, style = headerStyle, rows = startRow, cols = 0:(nCol - 1) + startCol, gridExpand = TRUE, stack = TRUE ) } ## If we don't have any rows to write return if (nRow == 0) { return(invisible(0)) } ## named region if (!is.null(name)) { ref1 <- stri_join("$", convert_to_excel_ref(cols = startCol, LETTERS = LETTERS), "$", startRow) ref2 <- stri_join("$", convert_to_excel_ref(cols = startCol + nCol - 1L, LETTERS = LETTERS), "$", startRow + nRow - 1L + colNames) wb$createNamedRegion(ref1 = ref1, ref2 = ref2, name = name, sheet = wb$sheet_names[wb$validateSheet(sheet)]) } ## hyperlink style, if no borders borders <- match.arg(borders, c("none", "surrounding", "rows", "columns", "all")) if (borders == "none") { invisible( classStyles( wb, sheet = sheet, startRow = startRow, startCol = startCol, colNames = colNames, nRow = nrow(x), colClasses = colClasses, stack = TRUE ) ) } else if (borders == "surrounding") { wb$surroundingBorders( colClasses, sheet = sheet, startRow = startRow + colNames, startCol = startCol, nRow = nRow, nCol = nCol, borderColour = list("rgb" = borderColour), borderStyle = borderStyle ) } else if (borders == "rows") { wb$rowBorders( colClasses, sheet = sheet, startRow = startRow + colNames, startCol = startCol, nRow = nRow, nCol = nCol, borderColour = list("rgb" = borderColour), borderStyle = borderStyle ) } else if (borders == "columns") { wb$columnBorders( colClasses, sheet = sheet, startRow = startRow + colNames, startCol = startCol, nRow = nRow, nCol = nCol, borderColour = list("rgb" = borderColour), borderStyle = borderStyle ) } else if (borders == "all") { wb$allBorders( colClasses, sheet = sheet, startRow = startRow + colNames, startCol = startCol, nRow = nRow, nCol = nCol, borderColour = list("rgb" = borderColour), borderStyle = borderStyle ) } invisible(0) } #' @name writeFormula #' @title Write a character vector as an Excel Formula #' @author Alexander Walker #' @description Write a a character vector containing Excel formula to a worksheet. #' @details Currently only the english version of functions are supported. Please don't use the local translation. #' The examples below show a small list of possible formulas: #' \itemize{ #' \item{SUM(B2:B4)} #' \item{AVERAGE(B2:B4)} #' \item{MIN(B2:B4)} #' \item{MAX(B2:B4)} #' \item{...} #' #' } #' @param wb A Workbook object containing a worksheet. #' @param sheet The worksheet to write to. Can be the worksheet index or name. #' @param x A character vector. #' @param startCol A vector specifying the starting column to write to. #' @param startRow A vector specifying the starting row to write to. #' @param array A bool if the function written is of type array #' @param xy An alternative to specifying `startCol` and #' `startRow` individually. A vector of the form #' `c(startCol, startRow)`. #' @seealso [writeData()] [makeHyperlinkString()] #' @export writeFormula #' @rdname writeFormula #' @examples #' #' ## There are 3 ways to write a formula #' #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' writeData(wb, "Sheet 1", x = iris) #' #' ## SEE int2col() to convert int to Excel column label #' #' ## 1. - As a character vector using writeFormula #' #' v <- c("SUM(A2:A151)", "AVERAGE(B2:B151)") ## skip header row #' writeFormula(wb, sheet = 1, x = v, startCol = 10, startRow = 2) #' writeFormula(wb, 1, x = "A2 + B2", startCol = 10, startRow = 10) #' #' #' ## 2. - As a data.frame column with class "formula" using writeData #' #' df <- data.frame( #' x = 1:3, #' y = 1:3, #' z = paste(paste0("A", 1:3 + 1L), paste0("B", 1:3 + 1L), sep = " + "), #' z2 = sprintf("ADDRESS(1,%s)", 1:3), #' stringsAsFactors = FALSE #' ) #' #' class(df$z) <- c(class(df$z), "formula") #' class(df$z2) <- c(class(df$z2), "formula") #' #' addWorksheet(wb, "Sheet 2") #' writeData(wb, sheet = 2, x = df) #' #' #' #' ## 3. - As a vector with class "formula" using writeData #' #' v2 <- c("SUM(A2:A4)", "AVERAGE(B2:B4)", "MEDIAN(C2:C4)") #' class(v2) <- c(class(v2), "formula") #' #' writeData(wb, sheet = 2, x = v2, startCol = 10, startRow = 2) #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "writeFormulaExample.xlsx", overwrite = TRUE) #' } #' #' #' ## 4. - Writing internal hyperlinks #' #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet1") #' addWorksheet(wb, "Sheet2") #' writeFormula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")') #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "writeFormulaHyperlinkExample.xlsx", overwrite = TRUE) #' } #' writeFormula <- function( wb, sheet, x, startCol = 1, startRow = 1, array = FALSE, xy = NULL ) { if (!is.character(x)) { stop("x must be a character vector.") } dfx <- data.frame("X" = x, stringsAsFactors = FALSE) class(dfx$X) <- c("character", ifelse(array, "array_formula", "formula")) if (any(grepl("^(=|)HYPERLINK\\(", x, ignore.case = TRUE))) { class(dfx$X) <- c("character", "formula", "hyperlink") } writeData( wb = wb, sheet = sheet, x = dfx, startCol = startCol, startRow = startRow, array = array, xy = xy, colNames = FALSE, rowNames = FALSE ) invisible(0) } openxlsx/R/class_definitions.R0000644000176200001440000000721614155600363016167 0ustar liggesusers # Workbook ---------------------------------------------------------------- Workbook <- setRefClass("Workbook", fields = c( "sheet_names" = "character", "charts" = "ANY", "isChartSheet" = "logical", "colOutlineLevels" = "ANY", "colWidths" = "ANY", "connections" = "ANY", "Content_Types" = "character", "core" = "character", "drawings" = "ANY", "drawings_rels" = "ANY", "embeddings" = "ANY", "externalLinks" = "ANY", "externalLinksRels" = "ANY", "headFoot" = "ANY", "media" = "ANY", "outlineLevels" = "ANY", "persons" = "ANY", "pivotTables" = "ANY", "pivotTables.xml.rels" = "ANY", "pivotDefinitions" = "ANY", "pivotRecords" = "ANY", "pivotDefinitionsRels" = "ANY", "queryTables" = "ANY", "rowHeights" = "ANY", "slicers" = "ANY", "slicerCaches" = "ANY", "sharedStrings" = "ANY", "styleObjects" = "ANY", "styles" = "ANY", "tables" = "ANY", "tables.xml.rels" = "ANY", "theme" = "ANY", "vbaProject" = "ANY", "vml" = "ANY", "vml_rels" = "ANY", "comments" = "ANY", "threadComments" = "ANY", "workbook" = "ANY", "workbook.xml.rels" = "ANY", "worksheets" = "ANY", "worksheets_rels" = "ANY", "sheetOrder" = "integer", "ActiveSheet" = "integer" ) ) # Style ------------------------------------------------------------------- Style <- setRefClass("Style", fields = c( "fontName", "fontColour", "fontSize", "fontFamily", "fontScheme", "fontDecoration", "borderTop", "borderLeft", "borderRight", "borderBottom", "borderTopColour", "borderLeftColour", "borderRightColour", "borderBottomColour", "borderDiagonal", "borderDiagonalColour", "borderDiagonalUp", "borderDiagonalDown", "halign", "valign", "indent", "textRotation", "numFmt", "fill", "wrapText", "locked", "hidden", "xfId" ), methods = list() ) # Sheet_Data -------------------------------------------------------------- Sheet_Data <- setRefClass("Sheet_Data", fields = c( "rows" = "integer", "cols" = "integer", "t" = "integer", "v" = "character", "f" = "character", "style_id" = "ANY", "data_count" = "integer", "n_elements" = "integer" ) ) # Worksheet --------------------------------------------------------------- WorkSheet <- setRefClass("WorkSheet", fields = c( "sheetPr" = "character", "dimension" = "character", "sheetViews" = "character", "sheetFormatPr" = "character", "cols" = "character", "sheet_data" = "Sheet_Data", "autoFilter" = "character", "mergeCells" = "ANY", "conditionalFormatting" = "character", "dataValidations" = "ANY", "dataValidationsLst" = "character", "freezePane" = "character", "hyperlinks" = "ANY", "sheetProtection" = "character", "pageMargins" = "character", "pageSetup" = "character", "headerFooter" = "ANY", "rowBreaks" = "character", "colBreaks" = "character", "drawing" = "character", "legacyDrawing" = "character", "legacyDrawingHF" = "character", "oleObjects" = "character", "tableParts" = "character", "extLst" = "character" ) ) # ChartSheet -------------------------------------------------------------- ChartSheet <- setRefClass("ChartSheet", fields = c( "sheetPr" = "character", "sheetViews" = "character", "pageMargins" = "character", "drawing" = "character", "hyperlinks" = "ANY" ) ) openxlsx/R/readWorkbook.R0000644000176200001440000004653114155600363015123 0ustar liggesusers #' @name read.xlsx #' @title Read from an Excel file or Workbook object #' @description Read data from an Excel file or Workbook object into a data.frame #' @param xlsxFile An xlsx file, Workbook object or URL to xlsx file. #' @param sheet The name or index of the sheet to read data from. #' @param startRow first row to begin looking for data. Empty rows at the top of a file are always skipped, #' regardless of the value of startRow. #' @param colNames If `TRUE`, the first row of data will be used as column names. #' @param skipEmptyRows If `TRUE`, empty rows are skipped else empty rows after the first row containing data #' will return a row of NAs. #' @param rowNames If `TRUE`, first column of data will be used as row names. #' @param detectDates If `TRUE`, attempt to recognise dates and perform conversion. #' @param cols A numeric vector specifying which columns in the Excel file to read. #' If NULL, all columns are read. #' @param rows A numeric vector specifying which rows in the Excel file to read. #' If NULL, all rows are read. #' @param check.names logical. If TRUE then the names of the variables in the data frame #' are checked to ensure that they are syntactically valid variable names #' @param sep.names One character which substitutes blanks in column names. By default, "." #' @param namedRegion A named region in the Workbook. If not NULL startRow, rows and cols parameters are ignored. #' @param na.strings A character vector of strings which are to be interpreted as NA. Blank cells will be returned as NA. #' @param fillMergedCells If TRUE, the value in a merged cell is given to all cells within the merge. #' @param skipEmptyCols If `TRUE`, empty columns are skipped. #' @seealso [getNamedRegions()] #' @details Formulae written using writeFormula to a Workbook object will not get picked up by read.xlsx(). #' This is because only the formula is written and left to be evaluated when the file is opened in Excel. #' Opening, saving and closing the file with Excel will resolve this. #' @author Alexander Walker #' @return data.frame #' @export #' @examples #' #' xlsxFile <- system.file("extdata", "readTest.xlsx", package = "openxlsx") #' df1 <- read.xlsx(xlsxFile = xlsxFile, sheet = 1, skipEmptyRows = FALSE) #' sapply(df1, class) #' #' df2 <- read.xlsx(xlsxFile = xlsxFile, sheet = 3, skipEmptyRows = TRUE) #' df2$Date <- convertToDate(df2$Date) #' sapply(df2, class) #' head(df2) #' #' df2 <- read.xlsx( #' xlsxFile = xlsxFile, sheet = 3, skipEmptyRows = TRUE, #' detectDates = TRUE #' ) #' sapply(df2, class) #' head(df2) #' #' wb <- loadWorkbook(system.file("extdata", "readTest.xlsx", package = "openxlsx")) #' df3 <- read.xlsx(wb, sheet = 2, skipEmptyRows = FALSE, colNames = TRUE) #' df4 <- read.xlsx(xlsxFile, sheet = 2, skipEmptyRows = FALSE, colNames = TRUE) #' all.equal(df3, df4) #' #' wb <- loadWorkbook(system.file("extdata", "readTest.xlsx", package = "openxlsx")) #' df3 <- read.xlsx(wb, #' sheet = 2, skipEmptyRows = FALSE, #' cols = c(1, 4), rows = c(1, 3, 4) #' ) #' #' ## URL #' ## #' \dontrun{ #' xlsxFile <- "https://github.com/awalker89/openxlsx/raw/master/inst/readTest.xlsx" #' head(read.xlsx(xlsxFile)) #' } #' #' @export read.xlsx <- function( xlsxFile, sheet, startRow = 1, colNames = TRUE, rowNames = FALSE, detectDates = FALSE, skipEmptyRows = TRUE, skipEmptyCols = TRUE, rows = NULL, cols = NULL, check.names = FALSE, sep.names = ".", namedRegion = NULL, na.strings = "NA", fillMergedCells = FALSE ) { UseMethod("read.xlsx", xlsxFile) } #' @export read.xlsx.default <- function( xlsxFile, sheet, startRow = 1, colNames = TRUE, rowNames = FALSE, detectDates = FALSE, skipEmptyRows = TRUE, skipEmptyCols = TRUE, rows = NULL, cols = NULL, check.names = FALSE, sep.names = ".", namedRegion = NULL, na.strings = "NA", fillMergedCells = FALSE ) { ## Validate inputs and get files xlsxFile <- getFile(xlsxFile) if (!file.exists(xlsxFile)) { stop("File does not exist.") } sheetselected <- TRUE if (missing(sheet)) { sheet <- 1 sheetselected <- FALSE } if (!grepl("\\.xlsx|\\.xlsm$", xlsxFile)) { stop("openxlsx can only read .xlsx or .xlsm files", call. = FALSE) } assert_true_false1(colNames) assert_true_false1(rowNames) assert_true_false1(detectDates) assert_true_false1(skipEmptyRows) assert_true_false1(check.names) assert_character1(sep.names, scalar = TRUE) assert_length(sheet, 1L) assert_length(startRow, 1L) if (is.null(rows)) { rows <- NA } else if (length(rows) > 1L) { rows <- as.integer(sort(rows)) } xmlDir <- paste0(tempfile(), "_excelXMLRead") xmlFiles <- unzip(xlsxFile, exdir = xmlDir) on.exit(unlink(xmlDir, recursive = TRUE), add = TRUE) sharedStringsFile <- grep("sharedStrings.xml$", xmlFiles, perl = TRUE, value = TRUE) workbook <- grep("workbook.xml$", xmlFiles, perl = TRUE, value = TRUE) workbookRelsXML <- grep("workbook.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) ## get workbook names workbookRelsXML <- paste(readUTF8(workbookRelsXML), collapse = "") workbookRelsXML <- getChildlessNode(xml = workbookRelsXML, tag = "Relationship") workbook <- unlist(readUTF8(workbook)) workbook <- removeHeadTag(workbook) sheets <- unlist(regmatches( workbook, gregexpr("(?<=).*(?=)", workbook, perl = TRUE) )) sheets <- unlist(regmatches( sheets, gregexpr("]*>", sheets, perl = TRUE) )) ## Some veryHidden sheets do not have a sheet content and their rId is empty. ## Such sheets need to be filtered out because otherwise their sheet names ## occur in the list of all sheet names, leading to a wrong association ## of sheet names with sheet indeces. sheets <- grep('r:id="[[:blank:]]*"', sheets, invert = TRUE, value = TRUE) ## make sure sheetId is 1 based sheetrId <- unlist(getRId(sheets)) sheetNames <- unlist(regmatches( sheets, gregexpr('(?<=name=")[^"]+', sheets, perl = TRUE) )) sheetNames <- replaceXMLEntities(sheetNames) nSheets <- length(sheetrId) if (nSheets == 0) { stop("Workbook has no worksheets") } ## Named region logic reading_named_region <- FALSE if (!is.null(namedRegion)) { dn <- getNodes(xml = workbook, tagIn = "") dn <- unlist(regmatches(dn, gregexpr("' and '!' dn_sheetNames <- gsub(".*[>]([^.]+)[!].*", "\\1", dn) # Check if there are any whitespaces in dn_sheetNames. # Hint: sheet names must not contain: \ / ? * [ ] wsp <- grepl(pattern = "'", dn_sheetNames) if (any(wsp)) { # sheetNames in between ''' and '''. If there is a whitespace in a sheet # name, the name will be "'sheet 1'" instead of "sheet 1. dn_sheetNames[wsp] <- gsub("^'+|'+$", "\\1", dn_sheetNames[wsp]) } # namedRegion in between 'name="' and '"' dn_namedRegion <- gsub(".*name=\"(\\w+)\".*", "\\1", dn) if (length(dn) == 0) { warning("Workbook has no named region.") return(invisible(NULL)) } if (all(dn_namedRegion != namedRegion)) { warning("Workbook has no such named region.") return(invisible(NULL)) } idx <- match(dn_namedRegion, namedRegion) # make sure that the length of both vectors is identical dn <- dn[!is.na(idx)] dn_namedRegion <- dn_namedRegion[!is.na(idx)] dn_sheetNames <- dn_sheetNames[!is.na(idx)] # a sheet was selected if (sheetselected) { idx <- match(dn_sheetNames, sheetNames) if (is.numeric(sheet)) { idx <- which(idx == sheet) } else { idx <- which(dn_sheetNames == sheet) } dn <- dn[idx] if (length(dn) > 1) { warning("unexpectedly found more than one dn.") print(dn) return(invisible(NULL)) } if ( identical(dn, character(0)) ) { warning("Workbook has no such named region on this sheet.") return(invisible(NULL)) } } # Do not print warning if a specific sheet is requested if ((length(dn) > 1) & (!sheetselected)) { msg <- c(sprintf("Region '%s' found on multiple sheets: \n", namedRegion), paste(dn_sheetNames, collapse = "\n"), "\nUsing the first appearance.") message(msg) dn <- dn[1] dn_namedRegion <- dn_namedRegion[1] dn_sheetNames <- dn_sheetNames[1] } # region is redefined later region <- regmatches(dn, regexpr("(?<=>)[^\\<]+", dn, perl = TRUE)) sheet <- sheetNames[vapply(sheetNames, grepl, NA, dn)] if (length(sheet) > 1) { sheet <- sheet[which.max(nchar(sheet))] } region <- gsub("[^A-Z0-9:]", "", gsub(sheet, "", region, fixed = TRUE)) if (grepl(":", region, fixed = TRUE)) { cols <- unlist(lapply( strsplit(region, split = ":", fixed = TRUE), convertFromExcelRef )) rows <- unlist(lapply(strsplit(region, split = ":", fixed = TRUE), function(x) { as.integer(gsub("[A-Z]", "", x, perl = TRUE)) })) cols <- seq.int(min(cols), max(cols)) rows <- seq.int(min(rows), max(rows)) } else { cols <- convertFromExcelRef(region) rows <- as.integer(gsub("[A-Z]", "", region, perl = TRUE)) } startRow <- 1 reading_named_region <- TRUE } ## get the file_name for each sheetrId file_name <- sapply(sheetrId, function(rId) { txt <- grep(sprintf('Id="%s"', rId), workbookRelsXML, fixed = TRUE, value = TRUE) regmatches(txt, regexpr('(?<=Target=").+xml(?=")', txt, perl = TRUE)) }) ## get the correct sheets if (is.character(sheet)) { sheetNames <- replaceXMLEntities(sheetNames) sheetInd <- which(sheetNames == sheet) if (length(sheetInd) == 0) { stop(sprintf('Cannot find sheet named "%s"', sheet)) } sheet <- file_name[sheetInd] } else { if (nSheets < sheet) { stop(sprintf("sheet %s does not exist.", sheet)) } sheet <- file_name[sheet] } if (length(sheet) == 0) { stop("Length of sheet is 0", call. = FALSE) } ## get file worksheet <- xmlFiles[grepl(tolower(sheet), tolower(xmlFiles), fixed = TRUE)] if (length(worksheet) == 0) { stop("Length of worksheet is 0", call. = FALSE) } ## read in sharedStrings if (length(sharedStringsFile) > 0) { sharedStrings <- getSharedStringsFromFile(sharedStringsFile = sharedStringsFile, isFile = TRUE) if (!is.null(na.strings)) { sharedStrings[is.na(sharedStrings) | sharedStrings %in% na.strings] <- "openxlsx_na_vlu" } } else { sharedStrings <- "" } if (is.character(startRow)) { startRowStr <- startRow startRow <- 1 } else { startRowStr <- NULL } ## single function get all r, s (if detect dates is TRUE), t, v cell_info <- getCellInfo( xmlFile = worksheet, sharedStrings = sharedStrings, skipEmptyRows = skipEmptyRows, startRow = startRow, rows = rows, getDates = detectDates ) if (fillMergedCells & length(cell_info$cellMerge) > 0) { # stop("Not implemented") merge_mapping <- mergeCell2mapping(cell_info$cellMerge) ## remove any elements from r, string_refs, b, s that existing in merge_mapping ## insert all missing refs into r to_remove_inds <- cell_info$r %in% merge_mapping$ref to_remove_elems <- cell_info$r[to_remove_inds] if (any(to_remove_inds)) { cell_info$r <- cell_info$r[!to_remove_inds] cell_info$s <- cell_info$s[!to_remove_inds] cell_info$v <- cell_info$v[!to_remove_inds] cell_info$string_refs <- cell_info$string_refs[!cell_info$string_refs %in% to_remove_elems] } ## Now insert inds <- match(merge_mapping$anchor_cell, cell_info$r) ## String refs (must sort) new_string_refs <- merge_mapping$ref[merge_mapping$anchor_cell %in% cell_info$string_refs] cell_info$string_refs <- c(cell_info$string_refs, new_string_refs) cell_info$string_refs <- cell_info$string_refs[order( as.integer(gsub("[A-Z]", "", cell_info$string_refs, perl = TRUE)), nchar(cell_info$string_refs), cell_info$string_refs )] ## r cell_info$r <- c(cell_info$r, merge_mapping$ref) cell_info$v <- c(cell_info$v, cell_info$v[inds]) ord <- order( as.integer(gsub("[A-Z]", "", cell_info$r, perl = TRUE)), nchar(cell_info$r), cell_info$r ) cell_info$r <- cell_info$r[ord] cell_info$v <- cell_info$v[ord] if (length(cell_info$s) > 0) { cell_info$s <- c(cell_info$s, cell_info$s[inds])[ord] } cell_info$nRows <- calc_number_rows(x = cell_info$r, skipEmptyRows = skipEmptyRows) } cell_rows <- as.integer(gsub("[A-Z]", "", cell_info$r, perl = TRUE)) cell_cols <- convert_from_excel_ref(x = cell_info$r) ## subsetting ---- ## Remove cells where cell is NA (na.strings or empty sharedString '') if (length(cell_info$v) == 0) { warning("No data found on worksheet.\n", call. = FALSE) return(NULL) } keep <- !is.na(cell_info$v) if (!is.null(cols)) { keep <- keep & (cell_cols %in% cols) } ## End of subsetting ## Subset cell_rows <- cell_rows[keep] cell_cols <- cell_cols[keep] v <- cell_info$v[keep] s <- cell_info$s[keep] string_refs <- match(cell_info$string_refs, cell_info$r[keep]) string_refs <- string_refs[!is.na(string_refs)] if (skipEmptyRows) { nRows <- length(unique(cell_rows)) } else if (reading_named_region) { ## keep region the correct size nRows <- max(rows) - min(rows) + 1 } else { nRows <- max(cell_rows) - min(cell_rows) + 1 } if (nRows == 0 | length(cell_rows) == 0) { warning("No data found on worksheet.", call. = FALSE) return(NULL) } Encoding(v) <- "UTF-8" ## only works if length(v) > 0 if (!is.null(startRowStr)) { stop("startRowStr not implemented") ind <- grep(startRowStr, v, ignore.case = TRUE) if (length(ind) > 0) { startRow <- as.numeric(gsub("[A-Z]", "", r[ind[[1]]])) toKeep <- grep(sprintf("[A-Z]%s$", startRow), r)[[1]] if (toKeep > 1) { toRemove <- 1:(toKeep - 1) string_refs <- string_refs[!string_refs %in% r[toRemove]] v <- v[-toRemove] r <- r[-toRemove] nRows <- calc_number_rows(x = r, skipEmptyRows = skipEmptyRows) } } } ## Determine date cells (if required) origin <- 25569L if (detectDates) { ## get date origin if (grepl('date1904="1"|date1904="true"', workbook, ignore.case = TRUE)) { origin <- 24107L } stylesXML <- grep("styles.xml", xmlFiles, value = TRUE) styles <- readUTF8(stylesXML) styles <- removeHeadTag(styles) ## Number formats numFmts <- getChildlessNode(xml = styles, tag = "numFmt") dateIds <- NULL if (length(numFmts) > 0) { numFmtsIds <- sapply(numFmts, getAttr, tag = 'numFmtId="', USE.NAMES = FALSE) formatCodes <- sapply(numFmts, getAttr, tag = 'formatCode="', USE.NAMES = FALSE) formatCodes <- gsub(".*(?<=\\])|@", "", formatCodes, perl = TRUE) ## this regex defines what "looks" like a date dateIds <- numFmtsIds[!grepl("[^mdyhsapAMP[:punct:] ]", formatCodes) & nchar(formatCodes > 3)] } dateIds <- c(dateIds, 14) ## which styles are using these dateIds cellXfs <- getNodes(xml = styles, tagIn = ")[^\\<]+", dn, perl = TRUE)) sheet <- names(xlsxFile)[sapply(names(xlsxFile), function(x) grepl(x, dn))] if (length(sheet) > 1) { sheet <- sheet[which.max(nchar(sheet))] } region <- gsub("[^A-Z0-9:]", "", gsub(sheet, "", region, fixed = TRUE)) if (grepl(":", region, fixed = TRUE)) { cols <- unlist(lapply(strsplit(region, split = ":", fixed = TRUE), convertFromExcelRef)) rows <- unlist(lapply(strsplit(region, split = ":", fixed = TRUE), function(x) as.integer(gsub("[A-Z]", "", x)))) cols <- seq(from = cols[1], to = cols[2], by = 1) rows <- seq(from = rows[1], to = rows[2], by = 1) } else { cols <- convertFromExcelRef(region) rows <- as.integer(gsub("[A-Z]", "", region, perl = TRUE)) } startRow <- 1 reading_named_region <- TRUE named_region_rows <- rows } if (is.null(rows)) { rows <- NA } else if (length(rows) > 1) { rows <- as.integer(sort(rows)) } ## check startRow if (!is.null(startRow)) { if (length(startRow) > 1) { stop("startRow must have length 1.") } } ## create temp dir and unzip nSheets <- length(xlsxFile$worksheets) if (nSheets == 0) { stop("Workbook has no worksheets") } ## get workbook names sheetNames <- xlsxFile$sheet_names if ("character" %in% class(sheet)) { sheetNames <- replaceXMLEntities(sheetNames) if (!sheet %in% sheetNames) { stop(sprintf('Cannot find sheet named "%s"', sheet)) } sheet <- which(sheetNames == sheet) } else { sheet <- sheet if (sheet > nSheets) { stop(sprintf("sheet %s does not exist.", sheet)) } } ## read in sharedStrings sharedStrings <- paste(unlist(xlsxFile$sharedStrings), collapse = "\n") if (length(sharedStrings) > 0) { sharedStrings <- getSharedStringsFromFile(sharedStringsFile = sharedStrings, isFile = FALSE) if (!is.null(na.strings)) { sharedStrings[sharedStrings %in% na.strings] <- NA } } ## read in worksheet and get cells with a value node, skip emptyStrs cells xlsxFile$worksheets[[sheet]]$order_sheetdata() sheet_data <- xlsxFile$worksheets[[sheet]]$sheet_data ###################################################### ## What data to read keep <- rep.int(TRUE, length(sheet_data$rows)) if (!is.na(rows[1])) { keep <- keep & (sheet_data$rows %in% rows) } if (!is.null(cols[1])) { keep <- keep & (sheet_data$cols %in% cols) } if (startRow > 1) { keep <- keep & (sheet_data$rows >= startRow) } ## error cells keep <- keep & (sheet_data$t != 4 & !is.na(sheet_data$t) & !is.na(sheet_data$v)) ## "e" or missing if (any(is.na(sharedStrings))) { keep[(sheet_data$t %in% 1 & (sheet_data$v %in% as.character(which(is.na(sharedStrings)) - 1L)))] <- FALSE } ## End what data to read ###################################################### rows <- sheet_data$rows[keep] cols <- sheet_data$cols[keep] v <- sheet_data$v[keep] t <- sheet_data$t[keep] if (length(v) == 0) { warning("No data found on worksheet.", call. = FALSE) return(NULL) } if (is.null(rows)) { warning("No data found on worksheet.", call. = FALSE) return(NULL) } else { if (skipEmptyRows) { nRows <- length(unique(rows)) } else if (reading_named_region) { nRows <- max(named_region_rows) - min(named_region_rows) + 1 } else { nRows <- max(rows) - min(rows) + 1 } } ## get references for string cells string_refs <- which(t == 2 | t == 1) ## "b" or "s" if (length(string_refs) == 0) { string_refs <- -1L } ## get Refs for boolean bool_refs <- which(t == 2) ## "b" if (length(bool_refs) == 0) { bool_refs <- -1L } if (bool_refs[1] != -1L) { false_ind <- which(sharedStrings == "FALSE") - 1L if (length(false_ind) == 0) { false_ind <- length(sharedStrings) sharedStrings <- c(sharedStrings, "FALSE") } true_ind <- which(sharedStrings == "TRUE") - 1L if (length(true_ind) == 0) { true_ind <- length(sharedStrings) sharedStrings <- c(sharedStrings, "TRUE") } logical_vals <- v[bool_refs] logical_vals[logical_vals == "0"] <- false_ind[1] logical_vals[logical_vals == "1"] <- true_ind[1] v[bool_refs] <- logical_vals rm(logical_vals) rm(bool_refs) } ## If any t="str" exist, add v to sharedStrings and replace v with newSharedStringsInd str_inds <- which(t %in% c(3, 5)) ## "str" or "inlineStr" if (length(str_inds) > 0) { unique_strs <- unique(v[str_inds]) unique_strs[unique_strs == "#N/A"] <- NA ## Match references of "str" cells to r new_shared_string_inds <- length(sharedStrings):(length(sharedStrings) + length(unique_strs) - 1L) ## replace strings in v with reference to sharedStrings, (now can convert v to numeric) v[str_inds] <- new_shared_string_inds[match(v[str_inds], unique_strs)] ## append new strings to sharedStrings sharedStrings <- c(sharedStrings, unique_strs) if (string_refs[1] == -1L) { string_refs <- str_inds } else { string_refs <- sort(c(string_refs, str_inds)) } } ## Now safe to convert v to numeric vn <- as.numeric(v) ## Using -1 as a flag for no strings if (length(sharedStrings) == 0 | string_refs[1] == -1L) { string_refs <- as.integer(NA) } else { ## set encoding of sharedStrings & replace values in v with string values Encoding(sharedStrings) <- "UTF-8" v[string_refs] <- sharedStrings[vn[string_refs] + 1L] ## any NA sharedStrings - remove v_na <- which(is.na(v)) if (length(v_na) > 0) { string_refs <- setdiff(string_refs, v_na) } } ## date detection origin <- 25569L isDate <- as.logical(NA) if (detectDates) { ## get date origin if (length(xlsxFile$workbook$workbookPr) > 0) { if (grepl('date1904="1"|date1904="true"', xlsxFile$workbook$workbookPr, ignore.case = TRUE)) { origin <- 24107L } } sO <- xlsxFile$styleObjects sO <- sO[unlist(lapply(sO, "[[", "sheet")) == sheetNames[sheet]] styles <- lapply(sO, function(x) { fc <- x[["style"]][["numFmt"]]$formatCode if (is.null(fc)) { fc <- x[["style"]][["numFmt"]]$numFmtId } fc }) sO <- sO[sapply(styles, length) > 0] format_codes <- unlist(lapply(sO, function(x) { fc <- x[["style"]][["numFmt"]]$formatCode if (is.null(fc)) { fc <- x[["style"]][["numFmt"]]$numFmtId } fc })) # dateIds <- NULL variable not used if (length(format_codes) > 0) { ## this regex defines what "looks" like a date format_codes <- gsub(".*(?<=\\])|@", "", format_codes, perl = TRUE) sO <- sO[(!grepl("[^mdyhsapAMP[:punct:] ]", format_codes) & nchar(format_codes > 3)) | format_codes == 14] } if (length(sO) > 0) { style_rows <- unlist(lapply(sO, "[[", "rows")) style_cols <- unlist(lapply(sO, "[[", "cols")) isDate <- paste(rows, cols, sep = ",") %in% paste(style_rows, style_cols, sep = ",") ## check numbers are also integers not_an_integer <- suppressWarnings(as.numeric(v[isDate])) not_an_integer <- (not_an_integer %% 1L != 0) | is.na(not_an_integer) isDate[not_an_integer] <- FALSE ## perform int to date to character convertsion (way too slow) v[isDate] <- format(as.Date(as.integer(v[isDate]) - origin, origin = "1970-01-01"), "%Y-%m-%d") } } ## end of detectDates ## Build data.frame m <- read_workbook( cols_in = cols, rows_in = rows, v = v, string_inds = string_refs, is_date = isDate, hasColNames = colNames, hasSepNames = sep.names, skipEmptyRows = skipEmptyRows, skipEmptyCols = skipEmptyCols, nRows = nRows, clean_names = clean_names ) if (colNames && check.names) { colnames(m) <- make.names(colnames(m), unique = TRUE) } if (rowNames) { rownames(m) <- m[[1]] m[[1]] <- NULL } return(m) } openxlsx/R/CommentClass.R0000644000176200001440000001655014155600363015060 0ustar liggesusers Comment <- setRefClass("Comment", fields = c( "text", "author", "style", "visible", "width", "height" ), methods = list() ) Comment$methods(initialize = function(text, author, style, visible = TRUE, width = 2, height = 4) { text <<- text author <<- author style <<- style visible <<- visible width <<- width height <<- height }) Comment$methods(show = function() { showText <- sprintf("Author: %s\n", author) showText <- c(showText, sprintf("Text:\n %s\n\n", paste(text, collapse = ""))) styleShow <- "Style:\n" if ("list" %in% class(style)) { for (i in seq_along(style)) { styleShow <- append(styleShow, sprintf("Font name: %s\n", style[[i]]$fontName[[1]])) ## Font name styleShow <- append(styleShow, sprintf("Font size: %s\n", style[[i]]$fontSize[[1]])) ## Font size styleShow <- append(styleShow, sprintf("Font colour: %s\n", gsub("^FF", "#", style[[i]]$fontColour[[1]]))) ## Font colour ## Font decoration if (length(style[[i]]$fontDecoration) > 0) { styleShow <- append(styleShow, sprintf("Font decoration: %s\n", paste(style[[i]]$fontDecoration, collapse = ", "))) } styleShow <- append(styleShow, "\n\n") } } else { styleShow <- append(styleShow, sprintf("Font name: %s \n", style$fontName[[1]])) ## Font name styleShow <- append(styleShow, sprintf("Font size: %s \n", style$fontSize[[1]])) ## Font size styleShow <- append(styleShow, sprintf("Font colour: %s \n", gsub("^FF", "#", style$fontColour[[1]]))) ## Font colour ## Font decoration if (length(style$fontDecoration) > 0) { styleShow <- append(styleShow, sprintf("Font decoration: %s \n", paste(style$fontDecoration, collapse = ", "))) } styleShow <- append(styleShow, "\n\n") } showText <- paste0(paste(showText, collapse = ""), paste(styleShow, collapse = ""), collapse = "") cat(showText) }) #' @name createComment #' @title create a Comment object #' @description Create a cell Comment object to pass to writeComment() #' @param comment Comment text. Character vector. #' @param author Author of comment. Character vector of length 1 #' @param style A Style object or list of style objects the same length as comment vector. See [createStyle()]. #' @param visible TRUE or FALSE. Is comment visible. #' @param width,height Width and height of textbook (in number of cells); #' doubles are rounded with \code{base::round()} #' @export #' @seealso [writeComment()] #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' #' c1 <- createComment(comment = "this is comment") #' writeComment(wb, 1, col = "B", row = 10, comment = c1) #' #' s1 <- createStyle(fontSize = 12, fontColour = "red", textDecoration = c("BOLD")) #' s2 <- createStyle(fontSize = 9, fontColour = "black") #' #' c2 <- createComment(comment = c("This Part Bold red\n\n", "This part black"), style = c(s1, s2)) #' c2 #' #' writeComment(wb, 1, col = 6, row = 3, comment = c2) #' \dontrun{ #' saveWorkbook(wb, file = "createCommentExample.xlsx", overwrite = TRUE) #' } createComment <- function(comment, author = Sys.getenv("USERNAME"), style = NULL, visible = TRUE, width = 2, height = 4) { if (!is.character(comment)) { stop("comment argument must be a character vector") } assert_character1(author) assert_numeric1(width) assert_numeric1(height) assert_true_false1(visible) width <- round(width) height <- round(height) if (is.null(style)) { style <- createStyle(fontName = "Tahoma", fontSize = 9, fontColour = "black") } author <- replaceIllegalCharacters(author) comment <- replaceIllegalCharacters(comment) invisible(Comment$new(text = comment, author = author, style = style, visible = visible, width = width[1], height = height[1])) } #' @name writeComment #' @title write a cell comment #' @description Write a Comment object to a worksheet #' @param wb A workbook object #' @param sheet A vector of names or indices of worksheets #' @param col Column a column number of letter #' @param row A row number. #' @param comment A Comment object. See [createComment()]. #' @param xy An alternative to specifying `col` and #' `row` individually. A vector of the form #' `c(col, row)`. #' @export #' @seealso [createComment()] #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' #' c1 <- createComment(comment = "this is comment") #' writeComment(wb, 1, col = "B", row = 10, comment = c1) #' #' s1 <- createStyle(fontSize = 12, fontColour = "red", textDecoration = c("BOLD")) #' s2 <- createStyle(fontSize = 9, fontColour = "black") #' #' c2 <- createComment(comment = c("This Part Bold red\n\n", "This part black"), style = c(s1, s2)) #' c2 #' #' writeComment(wb, 1, col = 6, row = 3, comment = c2) #' \dontrun{ #' saveWorkbook(wb, file = "writeCommentExample.xlsx", overwrite = TRUE) #' } writeComment <- function(wb, sheet, col, row, comment, xy = NULL) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (!"Comment" %in% class(comment)) { stop("comment argument must be a Comment object") } if (length(comment$style) == 1) { rPr <- wb$createFontNode(comment$style) } else { rPr <- sapply(comment$style, function(x) wb$createFontNode(x)) } rPr <- gsub("font>", "rPr>", rPr) sheet <- wb$validateSheet(sheet) ## All input conversions/validations if (!is.null(xy)) { if (length(xy) != 2) { stop("xy parameter must have length 2") } col <- xy[[1]] row <- xy[[2]] } if (!is.numeric(col)) { col <- convertFromExcelRef(col) } ref <- paste0(convert_to_excel_ref(cols = col, LETTERS = LETTERS), row) comment_list <- list( "ref" = ref, "author" = comment$author, "comment" = comment$text, "style" = rPr, "clientData" = genClientData(col, row, visible = comment$visible, height = comment$height, width = comment$width) ) wb$comments[[sheet]] <- append(wb$comments[[sheet]], list(comment_list)) invisible(wb) } #' @name removeComment #' @title Remove a comment from a cell #' @description Remove a cell comment from a worksheet #' @param wb A workbook object #' @param sheet A vector of names or indices of worksheets #' @param cols Columns to delete comments from #' @param rows Rows to delete comments from #' @param gridExpand If `TRUE`, all data in rectangle min(rows):max(rows) X min(cols):max(cols) #' will be removed. #' @export #' @seealso [createComment()] #' @seealso [writeComment()] removeComment <- function(wb, sheet, cols, rows, gridExpand = TRUE) { sheet <- wb$validateSheet(sheet) assert_class(wb, "Workbook") cols <- convertFromExcelRef(cols) rows <- as.integer(rows) ## rows and cols need to be the same length if (gridExpand) { combs <- expand.grid(rows, cols) rows <- combs[, 1] cols <- combs[, 2] } if (length(rows) != length(cols)) { stop("Length of rows and cols must be equal.") } comb <- paste0(convert_to_excel_ref(cols = cols, LETTERS = LETTERS), rows) toKeep <- !sapply(wb$comments[[sheet]], "[[", "ref") %in% comb wb$comments[[sheet]] <- wb$comments[[sheet]][toKeep] } openxlsx/R/RcppExports.R0000644000176200001440000001103414155631501014747 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 calc_column_widths <- function(sheet_data, sharedStrings, autoColumns, widths, baseFontCharWidth, minW, maxW) { .Call(`_openxlsx_calc_column_widths`, sheet_data, sharedStrings, autoColumns, widths, baseFontCharWidth, minW, maxW) } convert_to_excel_ref <- function(cols, LETTERS) { .Call(`_openxlsx_convert_to_excel_ref`, cols, LETTERS) } convert_from_excel_ref <- function(x) { .Call(`_openxlsx_convert_from_excel_ref`, x) } convert_to_excel_ref_expand <- function(cols, LETTERS, rows) { .Call(`_openxlsx_convert_to_excel_ref_expand`, cols, LETTERS, rows) } isInternalHyperlink <- function(x) { .Call(`_openxlsx_isInternalHyperlink`, x) } write_file <- function(head = "", body = "", tail = "", fl = "") { .Call(`_openxlsx_write_file`, head, body, tail, fl) } cppReadFile <- function(xmlFile) { .Call(`_openxlsx_cppReadFile`, xmlFile) } read_file_newline <- function(xmlFile) { .Call(`_openxlsx_read_file_newline`, xmlFile) } get_letters <- function() { .Call(`_openxlsx_get_letters`) } markUTF8 <- function(x, clone) { .Call(`_openxlsx_markUTF8`, x, clone) } loadworksheets <- function(wb, styleObjects, xmlFiles, is_chart_sheet) { .Call(`_openxlsx_loadworksheets`, wb, styleObjects, xmlFiles, is_chart_sheet) } getNodes <- function(xml, tagIn) { .Call(`_openxlsx_getNodes`, xml, tagIn) } getOpenClosedNode <- function(xml, open_tag, close_tag) { .Call(`_openxlsx_getOpenClosedNode`, xml, open_tag, close_tag) } getAttr <- function(x, tag) { .Call(`_openxlsx_getAttr`, x, tag) } getChildlessNode_ss <- function(xml, tag) { .Call(`_openxlsx_getChildlessNode_ss`, xml, tag) } getChildlessNode <- function(xml, tag) { .Call(`_openxlsx_getChildlessNode`, xml, tag) } get_extLst_Major <- function(xml) { .Call(`_openxlsx_get_extLst_Major`, xml) } cell_ref_to_col <- function(x) { .Call(`_openxlsx_cell_ref_to_col`, x) } int_2_cell_ref <- function(cols) { .Call(`_openxlsx_int_2_cell_ref`, cols) } get_shared_strings <- function(xmlFile, isFile) { .Call(`_openxlsx_get_shared_strings`, xmlFile, isFile) } getCellInfo <- function(xmlFile, sharedStrings, skipEmptyRows, startRow, rows, getDates) { .Call(`_openxlsx_getCellInfo`, xmlFile, sharedStrings, skipEmptyRows, startRow, rows, getDates) } read_workbook <- function(cols_in, rows_in, v, string_inds, is_date, hasColNames, hasSepNames, skipEmptyRows, skipEmptyCols, nRows, clean_names) { .Call(`_openxlsx_read_workbook`, cols_in, rows_in, v, string_inds, is_date, hasColNames, hasSepNames, skipEmptyRows, skipEmptyCols, nRows, clean_names) } calc_number_rows <- function(x, skipEmptyRows) { .Call(`_openxlsx_calc_number_rows`, x, skipEmptyRows) } map_cell_types_to_integer <- function(t) { .Call(`_openxlsx_map_cell_types_to_integer`, t) } map_cell_types_to_char <- function(t) { .Call(`_openxlsx_map_cell_types_to_char`, t) } build_cell_types_integer <- function(classes, n_rows) { .Call(`_openxlsx_build_cell_types_integer`, classes, n_rows) } buildCellTypes <- function(classes, nRows) { .Call(`_openxlsx_buildCellTypes`, classes, nRows) } build_cell_merges <- function(comps) { .Call(`_openxlsx_build_cell_merges`, comps) } buildCellList <- function(r, t, v) { .Call(`_openxlsx_buildCellList`, r, t, v) } #' @import Rcpp write_worksheet_xml <- function(prior, post, sheet_data, R_fileName) { .Call(`_openxlsx_write_worksheet_xml`, prior, post, sheet_data, R_fileName) } buildMatrixNumeric <- function(v, rowInd, colInd, colNames, nRows, nCols) { .Call(`_openxlsx_buildMatrixNumeric`, v, rowInd, colInd, colNames, nRows, nCols) } buildMatrixMixed <- function(v, rowInd, colInd, colNames, nRows, nCols, charCols, dateCols) { .Call(`_openxlsx_buildMatrixMixed`, v, rowInd, colInd, colNames, nRows, nCols, charCols, dateCols) } matrixRowInds <- function(indices) { .Call(`_openxlsx_matrixRowInds`, indices) } build_table_xml <- function(table, tableStyleXML, ref, colNames, showColNames, withFilter) { .Call(`_openxlsx_build_table_xml`, table, tableStyleXML, ref, colNames, showColNames, withFilter) } write_worksheet_xml_2 <- function(prior, post, sheet_data, row_heights_ = NULL, outline_levels_ = NULL, R_fileName = "output") { .Call(`_openxlsx_write_worksheet_xml_2`, prior, post, sheet_data, row_heights_, outline_levels_, R_fileName) } openxlsx/R/writeDataTable.R0000644000176200001440000002510514155600363015360 0ustar liggesusers #' @name writeDataTable #' @title Write to a worksheet as an Excel table #' @description Write to a worksheet and format as an Excel table #' @param wb A Workbook object containing a #' worksheet. #' @param sheet The worksheet to write to. Can be the worksheet index or name. #' @param x A dataframe. #' @param startCol A vector specifying the starting column to write df #' @param startRow A vector specifying the starting row to write df #' @param xy An alternative to specifying startCol and startRow individually. #' A vector of the form c(startCol, startRow) #' @param colNames If `TRUE`, column names of x are written. #' @param rowNames If `TRUE`, row names of x are written. #' @param row.names,col.names Deprecated, please use `rowNames`, `colNames` instead #' @param tableStyle Any excel table style name or "none" (see "formatting" vignette). #' @param tableName name of table in workbook. The table name must be unique. #' @param headerStyle Custom style to apply to column names. #' @param withFilter If `TRUE` or `NA`, columns with have filters in the first row. #' @param keepNA If `TRUE`, NA values are converted to #N/A (or `na.string`, if not NULL) in Excel, else NA cells will be empty. #' @param na.string If not NULL, and if `keepNA` is `TRUE`, NA values are converted to this string in Excel. #' @param sep Only applies to list columns. The separator used to collapse list columns to a character vector e.g. sapply(x$list_column, paste, collapse = sep). #' @param stack If `TRUE` the new style is merged with any existing cell styles. If FALSE, any #' existing style is replaced by the new style. #' \cr\cr #' \cr**The below options correspond to Excel table options:** #' \cr #' \if{html}{\figure{tableoptions.png}{options: width="40\%" alt="Figure: table_options.png"}} #' \if{latex}{\figure{tableoptions.pdf}{options: width=7cm}} #' #' @param firstColumn logical. If TRUE, the first column is bold #' @param lastColumn logical. If TRUE, the last column is bold #' @param bandedRows logical. If TRUE, rows are colour banded #' @param bandedCols logical. If TRUE, the columns are colour banded #' @details columns of x with class Date/POSIXt, currency, accounting, #' hyperlink, percentage are automatically styled as dates, currency, accounting, #' hyperlinks, percentages respectively. #' @seealso [addWorksheet()] #' @seealso [writeData()] #' @seealso [removeTable()] #' @seealso [getTables()] #' @export #' @examples #' ## see package vignettes for further examples. #' #' ##################################################################################### #' ## Create Workbook object and add worksheets #' wb <- createWorkbook() #' addWorksheet(wb, "S1") #' addWorksheet(wb, "S2") #' addWorksheet(wb, "S3") #' #' #' ##################################################################################### #' ## -- write data.frame as an Excel table with column filters #' ## -- default table style is "TableStyleMedium2" #' #' writeDataTable(wb, "S1", x = iris) #' #' writeDataTable(wb, "S2", #' x = mtcars, xy = c("B", 3), rowNames = TRUE, #' tableStyle = "TableStyleLight9" #' ) #' #' df <- data.frame( #' "Date" = Sys.Date() - 0:19, #' "T" = TRUE, "F" = FALSE, #' "Time" = Sys.time() - 0:19 * 60 * 60, #' "Cash" = paste("$", 1:20), "Cash2" = 31:50, #' "hLink" = "https://CRAN.R-project.org/", #' "Percentage" = seq(0, 1, length.out = 20), #' "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE #' ) #' #' ## openxlsx will apply default Excel styling for these classes #' class(df$Cash) <- c(class(df$Cash), "currency") #' class(df$Cash2) <- c(class(df$Cash2), "accounting") #' class(df$hLink) <- "hyperlink" #' class(df$Percentage) <- c(class(df$Percentage), "percentage") #' class(df$TinyNumbers) <- c(class(df$TinyNumbers), "scientific") #' #' writeDataTable(wb, "S3", x = df, startRow = 4, rowNames = TRUE, tableStyle = "TableStyleMedium9") #' #' ##################################################################################### #' ## Additional Header Styling and remove column filters #' #' writeDataTable(wb, #' sheet = 1, x = iris, startCol = 7, headerStyle = createStyle(textRotation = 45), #' withFilter = FALSE #' ) #' #' #' ##################################################################################### #' ## Save workbook #' ## Open in excel without saving file: openXL(wb) #' \dontrun{ #' saveWorkbook(wb, "writeDataTableExample.xlsx", overwrite = TRUE) #' } #' #' #' #' #' #' ##################################################################################### #' ## Pre-defined table styles gallery #' #' wb <- createWorkbook(paste0("tableStylesGallery.xlsx")) #' addWorksheet(wb, "Style Samples") #' for (i in 1:21) { #' style <- paste0("TableStyleLight", i) #' writeDataTable(wb, #' x = data.frame(style), sheet = 1, #' tableStyle = style, startRow = 1, startCol = i * 3 - 2 #' ) #' } #' #' for (i in 1:28) { #' style <- paste0("TableStyleMedium", i) #' writeDataTable(wb, #' x = data.frame(style), sheet = 1, #' tableStyle = style, startRow = 4, startCol = i * 3 - 2 #' ) #' } #' #' for (i in 1:11) { #' style <- paste0("TableStyleDark", i) #' writeDataTable(wb, #' x = data.frame(style), sheet = 1, #' tableStyle = style, startRow = 7, startCol = i * 3 - 2 #' ) #' } #' #' ## openXL(wb) #' \dontrun{ #' saveWorkbook(wb, file = "tableStylesGallery.xlsx", overwrite = TRUE) #' } #' writeDataTable <- function( wb, sheet, x, startCol = 1, startRow = 1, xy = NULL, colNames = TRUE, rowNames = FALSE, tableStyle = openxlsx_getOp("tableStyle", "TableStyleLight9"), tableName = NULL, headerStyle = openxlsx_getOp("headerStyle"), withFilter = openxlsx_getOp("withFilter", TRUE), keepNA = openxlsx_getOp("keepNA", FALSE), na.string = openxlsx_getOp("na.string"), sep = ", ", stack = FALSE, firstColumn = openxlsx_getOp("firstColumn", FALSE), lastColumn = openxlsx_getOp("lastColumn", FALSE), bandedRows = openxlsx_getOp("bandedRows", TRUE), bandedCols = openxlsx_getOp("bandedCols", FALSE), col.names, row.names ) { op <- get_set_options() on.exit(options(op), add = TRUE) ## increase scipen to avoid writing in scientific if (!missing(row.names)) { warning("Please use 'rowNames' instead of 'row.names'", call. = FALSE) row.names <- rowNames } if (!missing(col.names)) { warning("Please use 'colNames' instead of 'col.names'", call. = FALSE) colNames <- col.names } # Set NULLs withFilter <- withFilter %||% TRUE keepNA <- keepNA %||% FALSE firstColumn <- firstColumn %||% FALSE lastColumn <- lastColumn %||% FALSE bandedRows <- bandedRows %||% TRUE bandedCols <- bandedCols %||% FALSE withFilter <- withFilter %||% TRUE if (!is.null(xy)) { if (length(xy) != 2) { stop("xy parameter must have length 2") } startCol <- xy[[1]] startRow <- xy[[2]] } # Assert parameters assert_class(wb, "Workbook") assert_class(x, "data.frame") assert_true_false(colNames) assert_true_false(rowNames) assert_class(headerStyle, "Style", or_null = TRUE) assert_true_false(withFilter) assert_character1(sep) assert_true_false(firstColumn) assert_true_false(lastColumn) assert_true_false(bandedRows) assert_true_false(bandedCols) if (is.null(tableName)) { tableName <- sprintf("Table%i", length(wb$tables) + 3L) } else { tableName <- wb$validate_table_name(tableName) } ## convert startRow and startCol if (!is.numeric(startCol)) { startCol <- convertFromExcelRef(startCol) } startRow <- as.integer(startRow) ## Coordinates for each section if (rowNames) { x <- cbind(data.frame("row names" = rownames(x)), as.data.frame(x)) } ## If 0 rows append a blank row tableStyle <- validate_StyleName(tableStyle) ## header style if (inherits(headerStyle, "Style")) { addStyle( wb = wb, sheet = sheet, style = headerStyle, rows = startRow, cols = 0:(ncol(x) - 1L) + startCol, gridExpand = TRUE ) } showColNames <- colNames if (colNames) { colNames <- colnames(x) assert_unique(colNames, case_sensitive = FALSE) ## zero char names are invalid char0 <- nchar(colNames) == 0 if (any(char0)) { colNames[char0] <- colnames(x)[char0] <- paste0("Column", which(char0)) } } else { colNames <- paste0("Column", seq_along(x)) names(x) <- colNames } ## If zero rows, append an empty row (prevent XML from corrupting) if (nrow(x) == 0) { x <- rbind( as.data.frame(x), matrix("", nrow = 1, ncol = ncol(x), dimnames = list(character(), colnames(x))) ) names(x) <- colNames } ref1 <- paste0(convert_to_excel_ref(cols = startCol, LETTERS = LETTERS), startRow) ref2 <- paste0(convert_to_excel_ref(cols = startCol + ncol(x) - 1, LETTERS = LETTERS), startRow + nrow(x)) ref <- paste(ref1, ref2, sep = ":") ## check not overwriting another table wb$check_overwrite_tables( sheet = sheet, new_rows = c(startRow, startRow + nrow(x) - 1L + 1L), ## + header new_cols = c(startCol, startCol + ncol(x) - 1L) ) ## column class styling # consider not using lowercase and instead use inherits(x, class) colClasses <- lapply(x, function(x) tolower(class(x))) classStyles( wb, sheet = sheet, startRow = startRow, startCol = startCol, colNames = TRUE, nRow = nrow(x), colClasses = colClasses, stack = stack ) ## write data to worksheet wb$writeData( df = x, colNames = TRUE, sheet = sheet, startRow = startRow, startCol = startCol, colClasses = colClasses, hlinkNames = NULL, keepNA = keepNA, na.string = na.string, list_sep = sep ) ## replace invalid XML characters colNames <- replaceIllegalCharacters(colNames) ## create table.xml and assign an id to worksheet tables wb$buildTable( sheet = sheet, colNames = colNames, ref = ref, showColNames = showColNames, tableStyle = tableStyle, tableName = tableName, withFilter = withFilter[1], totalsRowCount = 0L, showFirstColumn = firstColumn[1], showLastColumn = lastColumn[1], showRowStripes = bandedRows[1], showColumnStripes = bandedCols[1] ) } openxlsx/R/WorkbookClass.R0000644000176200001440000040177314155600363015260 0ustar liggesusers #' @include class_definitions.R #' @import stringi Workbook$methods( initialize = function(creator = openxlsx_getOp("creator"), title = NULL, subject = NULL, category = NULL) { charts <<- list() isChartSheet <<- logical(0) colWidths <<- list() colOutlineLevels <<- list() attr(colOutlineLevels, "hidden") <<- NULL connections <<- NULL Content_Types <<- genBaseContent_Type() core <<- genBaseCore( creator = creator, title = title, subject = subject, category = category ) comments <<- list() threadComments <<- list() drawings <<- list() drawings_rels <<- list() embeddings <<- NULL externalLinks <<- NULL externalLinksRels <<- NULL headFoot <<- NULL media <<- list() persons <<- NULL pivotTables <<- NULL pivotTables.xml.rels <<- NULL pivotDefinitions <<- NULL pivotRecords <<- NULL pivotDefinitionsRels <<- NULL queryTables <<- NULL rowHeights <<- list() outlineLevels <<- list() attr(outlineLevels, "hidden") <<- NULL slicers <<- NULL slicerCaches <<- NULL sheet_names <<- character(0) sheetOrder <<- integer(0) sharedStrings <<- list() attr(sharedStrings, "uniqueCount") <<- 0 styles <<- genBaseStyleSheet() styleObjects <<- list() tables <<- NULL tables.xml.rels <<- NULL theme <<- NULL vbaProject <<- NULL vml <<- list() vml_rels <<- list() workbook <<- genBaseWorkbook() workbook.xml.rels <<- genBaseWorkbook.xml.rels() worksheets <<- list() worksheets_rels <<- list() ActiveSheet <<- integer(0) } ) Workbook$methods( addWorksheet = function( sheetName, showGridLines = openxlsx_getOp("showGridLines"), tabColour = openxlsx_getOp("tabColour"), zoom = 100, oddHeader = openxlsx_getOp("oddHeader"), oddFooter = openxlsx_getOp("oddFooter"), evenHeader = openxlsx_getOp("evenHeader"), evenFooter = openxlsx_getOp("evenFooter"), firstHeader = openxlsx_getOp("firstHeader"), firstFooter = openxlsx_getOp("firstFooter"), visible = TRUE, paperSize = openxlsx_getOp("paperSize", 9), orientation = openxlsx_getOp("orientation", "portrait"), hdpi = openxlsx_getOp("hdpi", 300), vdpi = openxlsx_getOp("vdpi", 300) ) { if (!missing(sheetName)) { if (grepl(pattern = ":", x = sheetName)) { stop("colon not allowed in sheet names in Excel") } } newSheetIndex <- length(worksheets) + 1L if (newSheetIndex > 1) { sheetId <- max(as.integer(regmatches( workbook$sheets, regexpr('(?<=sheetId=")[0-9]+', workbook$sheets, perl = TRUE) ))) + 1L } else { sheetId <- 1 ActiveSheet <<- 1L } ## fix visible value visible <- tolower(visible) if (visible == "true") { visible <- "visible" } else if (visible == "false") { visible <- "hidden" } else if (visible == "veryhidden") { visible <- "veryHidden" } ## Add sheet to workbook.xml workbook$sheets <<- c( workbook$sheets, sprintf( '', sheetName, sheetId, visible, newSheetIndex ) ) ## append to worksheets list worksheets <<- append( worksheets, WorkSheet$new( showGridLines = showGridLines, tabSelected = newSheetIndex == 1, tabColour = tabColour, zoom = zoom, oddHeader = oddHeader, oddFooter = oddFooter, evenHeader = evenHeader, evenFooter = evenFooter, firstHeader = firstHeader, firstFooter = firstFooter, paperSize = paperSize, orientation = orientation, hdpi = hdpi, vdpi = vdpi ) ) ## update content_tyes ## add a drawing.xml for the worksheet Content_Types <<- c( Content_Types, sprintf( '', newSheetIndex ), sprintf( '', newSheetIndex ) ) ## Update xl/rels workbook.xml.rels <<- c( workbook.xml.rels, sprintf( '', newSheetIndex ) ) ## create sheet.rels to simplify id assignment worksheets_rels[[newSheetIndex]] <<- genBaseSheetRels(newSheetIndex) drawings_rels[[newSheetIndex]] <<- list() drawings[[newSheetIndex]] <<- list() vml_rels[[newSheetIndex]] <<- list() vml[[newSheetIndex]] <<- list() isChartSheet[[newSheetIndex]] <<- FALSE comments[[newSheetIndex]] <<- list() threadComments[[newSheetIndex]] <<- list() rowHeights[[newSheetIndex]] <<- list() colWidths[[newSheetIndex]] <<- list() colOutlineLevels[[newSheetIndex]] <<- list() outlineLevels[[newSheetIndex]] <<- list() sheetOrder <<- c(sheetOrder, as.integer(newSheetIndex)) sheet_names <<- c(sheet_names, sheetName) invisible(newSheetIndex) } ) Workbook$methods( cloneWorksheet = function(sheetName, clonedSheet) { clonedSheet <- validateSheet(clonedSheet) if (!missing(sheetName)) { if (grepl(pattern = ":", x = sheetName)) { stop("colon not allowed in sheet names in Excel") } } newSheetIndex <- length(worksheets) + 1L if (newSheetIndex > 1) { sheetId <- max(as.integer(regmatches( workbook$sheets, regexpr('(?<=sheetId=")[0-9]+', workbook$sheets, perl = TRUE) ))) + 1L } else { sheetId <- 1 } ## copy visibility from cloned sheet! visible <- regmatches( workbook$sheets[[clonedSheet]], regexpr('(?<=state=")[^"]+', workbook$sheets[[clonedSheet]], perl = TRUE) ) ## Add sheet to workbook.xml workbook$sheets <<- c( workbook$sheets, sprintf( '', sheetName, sheetId, visible, newSheetIndex ) ) ## append to worksheets list worksheets <<- append(worksheets, worksheets[[clonedSheet]]$copy()) ## update content_tyes ## add a drawing.xml for the worksheet Content_Types <<- c( Content_Types, sprintf( '', newSheetIndex ), sprintf( '', newSheetIndex ) ) ## Update xl/rels workbook.xml.rels <<- c( workbook.xml.rels, sprintf( '', newSheetIndex ) ) ## create sheet.rels to simplify id assignment worksheets_rels[[newSheetIndex]] <<- genBaseSheetRels(newSheetIndex) drawings_rels[[newSheetIndex]] <<- drawings_rels[[clonedSheet]] # give each chart its own filename (images can re-use the same file, but charts can't) drawings_rels[[newSheetIndex]] <<- sapply(drawings_rels[[newSheetIndex]], function(rl) { chartfiles <- regmatches( rl, gregexpr("(?<=charts/)chart[0-9]+\\.xml", rl, perl = TRUE) )[[1]] for (cf in chartfiles) { chartid <- length(charts) + 1 newname <- stri_join("chart", chartid, ".xml") fl <- charts[cf] # Read the chartfile and adjust all formulas to point to the new # sheet name instead of the clone source # The result is saved to a new chart xml file newfl <- file.path(dirname(fl), newname) charts[newname] <<- newfl chart <- readUTF8(fl) chart <- gsub( stri_join("(?<=')", sheet_names[[clonedSheet]], "(?='!)"), stri_join("'", sheetName, "'"), chart, perl = TRUE ) chart <- gsub( stri_join("(?<=[^A-Za-z0-9])", sheet_names[[clonedSheet]], "(?=!)"), stri_join("'", sheetName, "'"), chart, perl = TRUE ) writeLines(chart, newfl) # file.copy(fl, newfl) Content_Types <<- c( Content_Types, sprintf( '', newname ) ) rl <- gsub(stri_join("(?<=charts/)", cf), newname, rl, perl = TRUE) } rl }, USE.NAMES = FALSE) # The IDs in the drawings array are sheet-specific, so within the new cloned sheet # the same IDs can be used => no need to modify drawings drawings[[newSheetIndex]] <<- drawings[[clonedSheet]] vml_rels[[newSheetIndex]] <<- vml_rels[[clonedSheet]] vml[[newSheetIndex]] <<- vml[[clonedSheet]] isChartSheet[[newSheetIndex]] <<- isChartSheet[[clonedSheet]] comments[[newSheetIndex]] <<- comments[[clonedSheet]] threadComments[[newSheetIndex]] <<- threadComments[[clonedSheet]] rowHeights[[newSheetIndex]] <<- rowHeights[[clonedSheet]] colWidths[[newSheetIndex]] <<- colWidths[[clonedSheet]] colOutlineLevels[[newSheetIndex]] <<- colOutlineLevels[[clonedSheet]] outlineLevels[[newSheetIndex]] <<- outlineLevels[[clonedSheet]] sheetOrder <<- c(sheetOrder, as.integer(newSheetIndex)) sheet_names <<- c(sheet_names, sheetName) ############################ ## STYLE ## ... objects are stored in a global list, so we need to get all styles ## assigned to the cloned sheet and duplicate them sheetStyles <- Filter(function(s) { s$sheet == sheet_names[[clonedSheet]] }, styleObjects) styleObjects <<- c( styleObjects, Map(function(s) { s$sheet <- sheetName s }, sheetStyles) ) ############################ ## TABLES ## ... are stored in the $tables list, with the name and sheet as attr ## and in the worksheets[]$tableParts list. We also need to adjust the ## worksheets_rels and set the content type for the new table tbls <- tables[attr(tables, "sheet") == clonedSheet] for (t in tbls) { # Extract table name, displayName and ID from the xml oldname <- regmatches(t, regexpr('(?<= name=")[^"]+', t, perl = TRUE)) olddispname <- regmatches(t, regexpr('(?<= displayName=")[^"]+', t, perl = TRUE)) oldid <- regmatches(t, regexpr('(?<= id=")[^"]+', t, perl = TRUE)) ref <- regmatches(t, regexpr('(?<= ref=")[^"]+', t, perl = TRUE)) # Find new, unused table names by appending _n, where n=1,2,... n <- 0 while (stri_join(oldname, "_", n) %in% attr(tables, "tableName")) { n <- n + 1 } newname <- stri_join(oldname, "_", n) newdispname <- stri_join(olddispname, "_", n) newid <- as.character(length(tables) + 3L) # Use the table definition from the cloned sheet and simply replace the names newt <- t newt <- gsub( stri_join(" name=\"", oldname, "\""), stri_join(" name=\"", newname, "\""), newt ) newt <- gsub( stri_join(" displayName=\"", olddispname, "\""), stri_join(" displayName=\"", newdispname, "\""), newt ) newt <- gsub( stri_join("(', newid)) attr(worksheets[[newSheetIndex]]$tableParts, "tableName") <<- c(attr(oldparts, "tableName"), newname) names(attr(worksheets[[newSheetIndex]]$tableParts, "tableName")) <<- c(names(attr(oldparts, "tableName")), ref) Content_Types <<- c( Content_Types, sprintf( '', newid ) ) tables.xml.rels <<- append(tables.xml.rels, "") worksheets_rels[[newSheetIndex]] <<- c( worksheets_rels[[newSheetIndex]], sprintf( '', newid, newid ) ) } # TODO: The following items are currently NOT copied/duplicated for the cloned sheet: # - Comments # - Pivot tables invisible(newSheetIndex) } ) Workbook$methods( addChartSheet = function(sheetName, tabColour = NULL, zoom = 100) { newSheetIndex <- length(worksheets) + 1L if (newSheetIndex > 1) { sheetId <- max(as.integer(regmatches( workbook$sheets, regexpr('(?<=sheetId=")[0-9]+', workbook$sheets, perl = TRUE) ))) + 1L } else { sheetId <- 1 } ## Add sheet to workbook.xml workbook$sheets <<- c( workbook$sheets, sprintf( '', sheetName, sheetId, newSheetIndex ) ) ## append to worksheets list worksheets <<- append( worksheets, ChartSheet$new( tabSelected = newSheetIndex == 1, tabColour = tabColour, zoom = zoom ) ) sheet_names <<- c(sheet_names, sheetName) ## update content_tyes Content_Types <<- c( Content_Types, sprintf( '', newSheetIndex ) ) ## Update xl/rels workbook.xml.rels <<- c( workbook.xml.rels, sprintf( '', newSheetIndex ) ) ## add a drawing.xml for the worksheet Content_Types <<- c( Content_Types, sprintf( '', newSheetIndex ) ) ## create sheet.rels to simplify id assignment worksheets_rels[[newSheetIndex]] <<- genBaseSheetRels(newSheetIndex) drawings_rels[[newSheetIndex]] <<- list() drawings[[newSheetIndex]] <<- list() isChartSheet[[newSheetIndex]] <<- TRUE rowHeights[[newSheetIndex]] <<- list() colWidths[[newSheetIndex]] <<- list() colOutlineLevels[[newSheetIndex]] <<- list() outlineLevels[[newSheetIndex]] <<- list() vml_rels[[newSheetIndex]] <<- list() vml[[newSheetIndex]] <<- list() sheetOrder <<- c(sheetOrder, newSheetIndex) invisible(newSheetIndex) } ) Workbook$methods( saveWorkbook = function() { ## temp directory to save XML files prior to compressing tmpDir <- file.path(tempfile(pattern = "workbookTemp_")) if (file.exists(tmpDir)) { unlink(tmpDir, recursive = TRUE, force = TRUE) } success <- dir.create(path = tmpDir, recursive = TRUE) if (!success) { stop(sprintf("Failed to create temporary directory '%s'", tmpDir)) } .self$preSaveCleanUp() nSheets <- length(worksheets) nThemes <- length(theme) nPivots <- length(pivotDefinitions) nSlicers <- length(slicers) nComments <- sum(sapply(comments, length) > 0) nThreadComments <- sum(sapply(threadComments, length) > 0) nPersons <- length(persons) nVML <- sum(sapply(vml, length) > 0) relsDir <- file.path(tmpDir, "_rels") dir.create(path = relsDir, recursive = TRUE) docPropsDir <- file.path(tmpDir, "docProps") dir.create(path = docPropsDir, recursive = TRUE) xlDir <- file.path(tmpDir, "xl") dir.create(path = xlDir, recursive = TRUE) xlrelsDir <- file.path(tmpDir, "xl", "_rels") dir.create(path = xlrelsDir, recursive = TRUE) xlTablesDir <- file.path(tmpDir, "xl", "tables") dir.create(path = xlTablesDir, recursive = TRUE) xlTablesRelsDir <- file.path(xlTablesDir, "_rels") dir.create(path = xlTablesRelsDir, recursive = TRUE) if (length(media) > 0) { xlmediaDir <- file.path(tmpDir, "xl", "media") dir.create(path = xlmediaDir, recursive = TRUE) } ## will always have a theme xlthemeDir <- file.path(tmpDir, "xl", "theme") dir.create(path = xlthemeDir, recursive = TRUE) if (is.null(theme)) { con <- file(file.path(xlthemeDir, "theme1.xml"), open = "wb") writeBin(charToRaw(genBaseTheme()), con) close(con) } else { lapply(1:nThemes, function(i) { con <- file(file.path(xlthemeDir, stri_join("theme", i, ".xml")), open = "wb") writeBin(charToRaw(pxml(theme[[i]])), con) close(con) }) } ## will always have drawings xlworksheetsDir <- file.path(tmpDir, "xl", "worksheets") dir.create(path = xlworksheetsDir, recursive = TRUE) xlworksheetsRelsDir <- file.path(tmpDir, "xl", "worksheets", "_rels") dir.create(path = xlworksheetsRelsDir, recursive = TRUE) xldrawingsDir <- file.path(tmpDir, "xl", "drawings") dir.create(path = xldrawingsDir, recursive = TRUE) xldrawingsRelsDir <- file.path(tmpDir, "xl", "drawings", "_rels") dir.create(path = xldrawingsRelsDir, recursive = TRUE) ## charts if (length(charts) > 0) { file.copy( from = dirname(charts[1]), to = file.path(tmpDir, "xl"), recursive = TRUE ) } ## xl/comments.xml if (nComments > 0 | nVML > 0) { for (i in 1:nSheets) { if (length(comments[[i]]) > 0) { fn <- sprintf("comments%s.xml", i) Content_Types <<- c( Content_Types, sprintf( '', fn ) ) worksheets_rels[[i]] <<- unique(c( worksheets_rels[[i]], sprintf( '', fn ) )) writeCommentXML( comment_list = comments[[i]], file_name = file.path(tmpDir, "xl", fn) ) } } .self$writeDrawingVML(xldrawingsDir) } ## Threaded Comments xl/threadedComments/threadedComment.xml if (nThreadComments > 0){ xlThreadComments <- file.path(tmpDir, "xl", "threadedComments") dir.create(path = xlThreadComments, recursive = TRUE) for (i in seq_len(nSheets)) { if (length(threadComments[[i]]) > 0) { fl <- threadComments[[i]] file.copy( from = fl, to = file.path(xlThreadComments, basename(fl)), overwrite = TRUE, copy.date = TRUE ) worksheets_rels[[i]] <<- unique(c( worksheets_rels[[i]], sprintf( '', basename(fl) ) )) } } } ## xl/persons/person.xml if (nPersons > 0){ personDir <- file.path(tmpDir, "xl", "persons") dir.create(path = personDir, recursive = TRUE) file.copy( from = persons, to = personDir, overwrite = TRUE ) } if (length(embeddings) > 0) { embeddingsDir <- file.path(tmpDir, "xl", "embeddings") dir.create(path = embeddingsDir, recursive = TRUE) for (fl in embeddings) { file.copy( from = fl, to = embeddingsDir, overwrite = TRUE ) } } if (nPivots > 0) { pivotTablesDir <- file.path(tmpDir, "xl", "pivotTables") dir.create(path = pivotTablesDir, recursive = TRUE) pivotTablesRelsDir <- file.path(tmpDir, "xl", "pivotTables", "_rels") dir.create(path = pivotTablesRelsDir, recursive = TRUE) pivotCacheDir <- file.path(tmpDir, "xl", "pivotCache") dir.create(path = pivotCacheDir, recursive = TRUE) pivotCacheRelsDir <- file.path(tmpDir, "xl", "pivotCache", "_rels") dir.create(path = pivotCacheRelsDir, recursive = TRUE) for (i in seq_along(pivotTables)) { file.copy( from = pivotTables[i], to = file.path(pivotTablesDir, sprintf("pivotTable%s.xml", i)), overwrite = TRUE, copy.date = TRUE ) } for (i in seq_along(pivotDefinitions)) { file.copy( from = pivotDefinitions[i], to = file.path(pivotCacheDir, sprintf("pivotCacheDefinition%s.xml", i)), overwrite = TRUE, copy.date = TRUE ) } for (i in seq_along(pivotRecords)) { file.copy( from = pivotRecords[i], to = file.path(pivotCacheDir, sprintf("pivotCacheRecords%s.xml", i)), overwrite = TRUE, copy.date = TRUE ) } for (i in seq_along(pivotDefinitionsRels)) { file.copy( from = pivotDefinitionsRels[i], to = file.path( pivotCacheRelsDir, sprintf("pivotCacheDefinition%s.xml.rels", i) ), overwrite = TRUE, copy.date = TRUE ) } for (i in seq_along(pivotTables.xml.rels)) { write_file( body = pivotTables.xml.rels[[i]], fl = file.path(pivotTablesRelsDir, sprintf("pivotTable%s.xml.rels", i)) ) } } ## slicers if (nSlicers > 0) { slicersDir <- file.path(tmpDir, "xl", "slicers") dir.create(path = slicersDir, recursive = TRUE) slicerCachesDir <- file.path(tmpDir, "xl", "slicerCaches") dir.create(path = slicerCachesDir, recursive = TRUE) for (i in seq_along(slicers)) { if (nchar(slicers[i]) > 0) { file.copy(from = slicers[i], to = file.path(slicersDir, sprintf("slicer%s.xml", i))) } } for (i in seq_along(slicerCaches)) { write_file( body = slicerCaches[[i]], fl = file.path(slicerCachesDir, sprintf("slicerCache%s.xml", i)) ) } } ## Write content ## write .rels write_file( head = '\n', body = '', tail = "", fl = file.path(relsDir, ".rels") ) app <- "Microsoft Excel" # further protect argument (might be extended with: , , , , , , ) if (!is.null(workbook$apps)) app <- paste0(app, workbook$apps) ## write app.xml write_file( head = '', body = app, tail = "", fl = file.path(docPropsDir, "app.xml") ) ## write core.xml write_file( head = "", body = pxml(core), tail = "", fl = file.path(docPropsDir, "core.xml") ) ## write workbook.xml.rels write_file( head = '', body = pxml(workbook.xml.rels), tail = "", fl = file.path(xlrelsDir, "workbook.xml.rels") ) ## write tables if (length(unlist(tables, use.names = FALSE)) > 0) { for (i in seq_along(unlist(tables, use.names = FALSE))) { if (!grepl("openxlsx_deleted", attr(tables, "tableName")[i], fixed = TRUE)) { write_file( body = pxml(unlist(tables, use.names = FALSE)[[i]]), fl = file.path(xlTablesDir, sprintf("table%s.xml", i + 2)) ) if (tables.xml.rels[[i]] != "") { write_file( body = tables.xml.rels[[i]], fl = file.path(xlTablesRelsDir, sprintf("table%s.xml.rels", i + 2)) ) } } } } ## write query tables if (length(queryTables) > 0) { xlqueryTablesDir <- file.path(tmpDir, "xl", "queryTables") dir.create(path = xlqueryTablesDir, recursive = TRUE) for (i in seq_along(queryTables)) { write_file( body = queryTables[[i]], fl = file.path(xlqueryTablesDir, sprintf("queryTable%s.xml", i)) ) } } ## connections if (length(connections) > 0) { write_file(body = connections, fl = file.path(xlDir, "connections.xml")) } ## externalLinks if (length(externalLinks)) { externalLinksDir <- file.path(tmpDir, "xl", "externalLinks") dir.create(path = externalLinksDir, recursive = TRUE) for (i in seq_along(externalLinks)) { write_file( body = externalLinks[[i]], fl = file.path(externalLinksDir, sprintf("externalLink%s.xml", i)) ) } } ## externalLinks rels if (length(externalLinksRels)) { externalLinksRelsDir <- file.path(tmpDir, "xl", "externalLinks", "_rels") dir.create(path = externalLinksRelsDir, recursive = TRUE) for (i in seq_along(externalLinksRels)) { write_file( body = externalLinksRels[[i]], fl = file.path( externalLinksRelsDir, sprintf("externalLink%s.xml.rels", i) ) ) } } # printerSettings printDir <- file.path(tmpDir, "xl", "printerSettings") dir.create(path = printDir, recursive = TRUE) for (i in 1:nSheets) { writeLines(genPrinterSettings(), file.path(printDir, sprintf("printerSettings%s.bin", i))) } ## media (copy file from origin to destination) for (x in media) { file.copy(x, file.path(xlmediaDir, names(media)[which(media == x)])) } ## VBA Macro if (!is.null(vbaProject)) { file.copy(vbaProject, xlDir) } ## write worksheet, worksheet_rels, drawings, drawing_rels .self$writeSheetDataXML( xldrawingsDir, xldrawingsRelsDir, xlworksheetsDir, xlworksheetsRelsDir ) ## write sharedStrings.xml ct <- Content_Types if (length(sharedStrings) > 0) { write_file( head = sprintf( '', length(sharedStrings), attr(sharedStrings, "uniqueCount") ), body = stri_join(sharedStrings, collapse = "", sep = " "), tail = "", fl = file.path(xlDir, "sharedStrings.xml") ) } else { ## Remove relationship to sharedStrings ct <- ct[!grepl("sharedStrings", ct)] } if (nComments > 0) { ct <- c( ct, '' ) } ## write [Content_type] write_file( head = '', body = pxml(ct), tail = "", fl = file.path(tmpDir, "[Content_Types].xml") ) styleXML <- styles styleXML$numFmts <- stri_join( sprintf('', length(styles$numFmts)), pxml(styles$numFmts), "" ) styleXML$fonts <- stri_join( sprintf('', length(styles$fonts)), pxml(styles$fonts), "" ) styleXML$fills <- stri_join( sprintf('', length(styles$fills)), pxml(styles$fills), "" ) styleXML$borders <- stri_join( sprintf('', length(styles$borders)), pxml(styles$borders), "" ) styleXML$cellStyleXfs <- c( sprintf('', length(styles$cellStyleXfs)), pxml(styles$cellStyleXfs), "" ) styleXML$cellXfs <- stri_join( sprintf('', length(styles$cellXfs)), pxml(styles$cellXfs), "" ) styleXML$cellStyles <- stri_join( sprintf('', length(styles$cellStyles)), pxml(styles$cellStyles), "" ) styleXML$dxfs <- ifelse( length(styles$dxfs) == 0, '', stri_join( sprintf('', length(styles$dxfs)), stri_join(unlist(styles$dxfs), sep = " ", collapse = ""), "" ) ) ## write styles.xml write_file( head = '', body = pxml(styleXML), tail = "", fl = file.path(xlDir, "styles.xml") ) ## write workbook.xml workbookXML <- workbook workbookXML$sheets <- stri_join("", pxml(workbookXML$sheets), "") if (length(workbookXML$definedNames) > 0) { workbookXML$definedNames <- stri_join( "", pxml(workbookXML$definedNames), "" ) } write_file( head = '', body = pxml(workbookXML), tail = "", fl = file.path(xlDir, "workbook.xml") ) workbook$sheets <<- workbook$sheets[order(sheetOrder)] ## Need to reset sheet order to allow multiple savings ## compress to xlsx wd <- getwd() tmpFile <- basename(tempfile(fileext = ifelse(is.null(vbaProject), ".xlsx", ".xlsm"))) on.exit(expr = setwd(wd), add = TRUE) ## zip it setwd(dir = tmpDir) cl <- ifelse( !is.null(getOption("openxlsx.compresssionLevel")), getOption("openxlsx.compresssionLevel"), getOption("openxlsx.compresssionevel", 6) ) zipr( zipfile = tmpFile, include_directories = FALSE, files = list.files(path = tmpDir, all.files = FALSE), recurse = TRUE, compression_level = cl ) ## reset styles - maintain any changes to base font baseFont <- styles$fonts[[1]] styles <<- genBaseStyleSheet(styles$dxfs, tableStyles = styles$tableStyles, extLst = styles$extLst ) styles$fonts[[1]] <<- baseFont return(file.path(tmpDir, tmpFile)) } ) Workbook$methods( updateSharedStrings = function(uNewStr) { ## Function will return named list of references to new strings uStr <- uNewStr[which(!uNewStr %in% sharedStrings)] uCount <- attr(sharedStrings, "uniqueCount") sharedStrings <<- append(sharedStrings, uStr) attr(sharedStrings, "uniqueCount") <<- uCount + length(uStr) } ) Workbook$methods( validateSheet = function(sheetName) { if (!is.numeric(sheetName)) { if (is.null(sheet_names)) { stop("Workbook does not contain any worksheets.", call. = FALSE) } } if (is.numeric(sheetName)) { if (sheetName > length(sheet_names)) { stop("This Workbook only has ", length(sheet_names), " sheets, ", sheetName, " is not valid", call. = FALSE ) } return(sheetName) } else if (!sheetName %in% replaceXMLEntities(sheet_names)) { stop(sprintf("Sheet '%s' does not exist.", replaceXMLEntities(sheetName)), call. = FALSE) } which(replaceXMLEntities(sheet_names) == sheetName) } ) Workbook$methods( getSheetName = function(sheetIndex) { if (any(length(sheet_names) < sheetIndex)) { stop(sprintf("Workbook only contains %s sheet(s).", length(sheet_names))) } sheet_names[sheetIndex] } ) Workbook$methods( buildTable = function(sheet, colNames, ref, showColNames, tableStyle, tableName, withFilter, totalsRowCount = 0, showFirstColumn = 0, showLastColumn = 0, showRowStripes = 1, showColumnStripes = 0) { ## id will start at 3 and drawing will always be 1, printer Settings at 2 (printer settings has been removed) id <- as.character(length(tables) + 3L) sheet <- validateSheet(sheet) ## build table XML and save to tables field table <- sprintf( '
', tableStyle, as.integer(showFirstColumn), as.integer(showLastColumn), as.integer(showRowStripes), as.integer(showColumnStripes) ) tables <<- c( tables, build_table_xml( table = table, tableStyleXML = tableStyleXML, ref = ref, colNames = gsub("\n|\r", "_x000a_", colNames), showColNames = showColNames, withFilter = withFilter ) ) names(tables) <<- c(nms, ref) attr(tables, "sheet") <<- c(tSheets, sheet) attr(tables, "tableName") <<- c(tNames, tableName) worksheets[[sheet]]$tableParts <<- append( worksheets[[sheet]]$tableParts, sprintf('', id) ) attr(worksheets[[sheet]]$tableParts, "tableName") <<- c(tNames[tSheets == sheet & !grepl("openxlsx_deleted", tNames, fixed = TRUE)], tableName) ## update Content_Types Content_Types <<- c( Content_Types, sprintf( '', id ) ) ## create a table.xml.rels tables.xml.rels <<- append(tables.xml.rels, "") ## update worksheets_rels worksheets_rels[[sheet]] <<- c( worksheets_rels[[sheet]], sprintf( '', id, id ) ) } ) Workbook$methods( writeDrawingVML = function(dir) { for (i in seq_along(comments)) { id <- 1025 cd <- unlist(lapply(comments[[i]], "[[", "clientData")) nComments <- length(cd) ## write head if (nComments > 0 | length(vml[[i]]) > 0) { write( x = stri_join( ' ' ), file = file.path(dir, sprintf("vmlDrawing%s.vml", i)), sep = " " ) } if (nComments > 0) { for (j in 1:nComments) { id <- id + 1L write( x = genBaseShapeVML(cd[j], id), file = file.path(dir, sprintf("vmlDrawing%s.vml", i)), append = TRUE ) } } if (length(vml[[i]]) > 0) { write( x = vml[[i]], file = file.path(dir, sprintf("vmlDrawing%s.vml", i)), append = TRUE ) } if (nComments > 0 | length(vml[[i]]) > 0) { write( x = "", file = file.path(dir, sprintf("vmlDrawing%s.vml", i)), append = TRUE ) worksheets[[i]]$legacyDrawing <<- '' } } } ) Workbook$methods( updateStyles = function(style) { ## Updates styles.xml xfNode <- list( numFmtId = 0, fontId = 0, fillId = 0, borderId = 0, xfId = 0 ) alignmentFlag <- FALSE ## Font if (!is.null(style$fontName) | !is.null(style$fontSize) | !is.null(style$fontColour) | !is.null(style$fontDecoration) | !is.null(style$fontFamily) | !is.null(style$fontScheme)) { fontNode <- .self$createFontNode(style) fontId <- which(styles$fonts == fontNode) - 1L if (length(fontId) == 0) { fontId <- length(styles$fonts) styles$fonts <<- append(styles[["fonts"]], fontNode) } xfNode$fontId <- fontId xfNode <- append(xfNode, list("applyFont" = "1")) } ## numFmt if (!is.null(style$numFmt)) { if (as.integer(style$numFmt$numFmtId) > 0) { numFmtId <- style$numFmt$numFmtId if (as.integer(numFmtId) > 163L) { tmp <- style$numFmt$formatCode styles$numFmts <<- unique(c( styles$numFmts, sprintf( '', numFmtId, tmp ) )) } xfNode$numFmtId <- numFmtId xfNode <- append(xfNode, list("applyNumberFormat" = "1")) } } ## Fill if (!is.null(style$fill)) { fillNode <- .self$createFillNode(style) if (!is.null(fillNode)) { fillId <- which(styles$fills == fillNode) - 1L if (length(fillId) == 0) { fillId <- length(styles$fills) styles$fills <<- c(styles$fills, fillNode) } xfNode$fillId <- fillId xfNode <- append(xfNode, list("applyFill" = "1")) } } ## Border if (any(!is.null( c( style$borderLeft, style$borderRight, style$borderTop, style$borderBottom, style$borderDiagonal ) ))) { borderNode <- .self$createBorderNode(style) borderId <- which(styles$borders == borderNode) - 1L if (length(borderId) == 0) { borderId <- length(styles$borders) styles$borders <<- c(styles$borders, borderNode) } xfNode$borderId <- borderId xfNode <- append(xfNode, list("applyBorder" = "1")) } # if(!is.null(style$xfId)) # xfNode$xfId <- style$xfId childNodes <- "" ## Alignment if (!is.null(style$halign) | !is.null(style$valign) | !is.null(style$wrapText) | !is.null(style$textRotation) | !is.null(style$indent)) { attrs <- list() alignNode <- "") alignmentFlag <- TRUE xfNode <- append(xfNode, list("applyAlignment" = "1")) childNodes <- stri_join(childNodes, alignNode) } if (!is.null(style$hidden) | !is.null(style$locked)) { xfNode <- append(xfNode, list("applyProtection" = "1")) protectionNode <- "") childNodes <- stri_join(childNodes, protectionNode) } if (length(childNodes) > 0) { xfNode <- stri_join( "", childNodes, "" ) } else { xfNode <- stri_join("") } styleId <- which(styles$cellXfs == xfNode) - 1L if (length(styleId) == 0) { styleId <- length(styles$cellXfs) styles$cellXfs <<- c(styles$cellXfs, xfNode) } return(as.integer(styleId)) } ) Workbook$methods( updateCellStyles = function() { flag <- TRUE for (style in cellStyleObjects) { ## Updates styles.xml xfNode <- list( numFmtId = 0, fontId = 0, fillId = 0, borderId = 0 ) alignmentFlag <- FALSE ## Font if (!is.null(style$fontName) | !is.null(style$fontSize) | !is.null(style$fontColour) | !is.null(style$fontDecoration) | !is.null(style$fontFamily) | !is.null(style$fontScheme)) { fontNode <- .self$createFontNode(style) fontId <- which(styles$font == fontNode) - 1L if (length(fontId) == 0) { fontId <- length(styles$fonts) styles$fonts <<- append(styles[["fonts"]], fontNode) } xfNode$fontId <- fontId xfNode <- append(xfNode, list("applyFont" = "1")) } ## numFmt if (!is.null(style$numFmt)) { if (as.integer(style$numFmt$numFmtId) > 0) { numFmtId <- style$numFmt$numFmtId if (as.integer(numFmtId) > 163L) { tmp <- style$numFmt$formatCode styles$numFmts <<- unique(c( styles$numFmts, sprintf( '', numFmtId, tmp ) )) } xfNode$numFmtId <- numFmtId xfNode <- append(xfNode, list("applyNumberFormat" = "1")) } } ## Fill if (!is.null(style$fill)) { fillNode <- .self$createFillNode(style) if (!is.null(fillNode)) { fillId <- which(styles$fills == fillNode) - 1L if (length(fillId) == 0) { fillId <- length(styles$fills) styles$fills <<- c(styles$fills, fillNode) } xfNode$fillId <- fillId xfNode <- append(xfNode, list("applyFill" = "1")) } } ## Border if (any(!is.null( c( style$borderLeft, style$borderRight, style$borderTop, style$borderBottom, style$borderDiagonal ) ))) { borderNode <- .self$createBorderNode(style) borderId <- which(styles$borders == borderNode) - 1L if (length(borderId) == 0) { borderId <- length(styles$borders) styles$borders <<- c(styles$borders, borderNode) } xfNode$borderId <- borderId xfNode <- append(xfNode, list("applyBorder" = "1")) } xfNode <- stri_join("") if (flag) { styles$cellStyleXfs <<- xfNode flag <- FALSE } else { styles$cellStyleXfs <<- c(styles$cellStyleXfs, xfNode) } } } ) Workbook$methods( getBaseFont = function() { baseFont <- styles$fonts[[1]] sz <- getAttrs(baseFont, "sz") colour <- getAttrs(baseFont, "color") name <- getAttrs(baseFont, "name") if (length(sz[[1]]) == 0) { sz <- list("val" = "10") } if (length(colour[[1]]) == 0) { colour <- list("rgb" = "#000000") } if (length(name[[1]]) == 0) { name <- list("val" = "Calibri") } list( "size" = sz, "colour" = colour, "name" = name ) } ) Workbook$methods( createFontNode = function(style) { baseFont <- .self$getBaseFont() fontNode <- "" ## size if (is.null(style$fontSize[[1]])) { fontNode <- stri_join(fontNode, sprintf('', names(baseFont$size), baseFont$size)) } else { fontNode <- stri_join(fontNode, sprintf('', names(style$fontSize), style$fontSize)) } ## colour if (is.null(style$fontColour[[1]])) { fontNode <- stri_join( fontNode, sprintf( '', names(baseFont$colour), baseFont$colour ) ) } else { if (length(style$fontColour) > 1) { fontNode <- stri_join(fontNode, sprintf( "", stri_join( sapply(seq_along(style$fontColour), function(i) { sprintf('%s="%s"', names(style$fontColour)[i], style$fontColour[i]) }), sep = " ", collapse = " " ) )) } else { fontNode <- stri_join( fontNode, sprintf( '', names(style$fontColour), style$fontColour ) ) } } ## name if (is.null(style$fontName[[1]])) { fontNode <- stri_join( fontNode, sprintf('', names(baseFont$name), baseFont$name) ) } else { fontNode <- stri_join( fontNode, sprintf('', names(style$fontName), style$fontName) ) } ### Create new font and return Id if (!is.null(style$fontFamily)) { fontNode <- stri_join(fontNode, sprintf('', style$fontFamily)) } if (!is.null(style$fontScheme)) { fontNode <- stri_join(fontNode, sprintf('', style$fontScheme)) } if ("BOLD" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, "") } if ("ITALIC" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, "") } if ("UNDERLINE" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, '') } if ("UNDERLINE2" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, '') } if ("ACCOUNTING" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, '') } if ("ACCOUNTING2" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, '') } if ("STRIKEOUT" %in% style$fontDecoration) { fontNode <- stri_join(fontNode, "") } stri_join(fontNode, "") } ) Workbook$methods( createBorderNode = function(style) { borderNode <- "") if (!is.null(style$borderLeft)) { borderNode <- stri_join( borderNode, sprintf('', style$borderLeft), sprintf( '', names(style$borderLeftColour), style$borderLeftColour ), "" ) } if (!is.null(style$borderRight)) { borderNode <- stri_join( borderNode, sprintf('', style$borderRight), sprintf( '', names(style$borderRightColour), style$borderRightColour ), "" ) } if (!is.null(style$borderTop)) { borderNode <- stri_join( borderNode, sprintf('', style$borderTop), sprintf( '', names(style$borderTopColour), style$borderTopColour ), "" ) } if (!is.null(style$borderBottom)) { borderNode <- stri_join( borderNode, sprintf('', style$borderBottom), sprintf( '', names(style$borderBottomColour), style$borderBottomColour ), "" ) } if (!is.null(style$borderDiagonal)) { borderNode <- stri_join( borderNode, sprintf('', style$borderDiagonal), sprintf( '', names(style$borderDiagonalColour), style$borderDiagonalColour ), "" ) } stri_join(borderNode, "") } ) Workbook$methods( createFillNode = function(style, patternType = "solid") { fill <- style$fill ## gradientFill if (any(grepl("gradientFill", fill))) { fillNode <- fill # stri_join("", fill, "") } else if (!is.null(fill$fillFg) | !is.null(fill$fillBg)) { fillNode <- stri_join( "", sprintf('', patternType) ) if (!is.null(fill$fillFg)) { fillNode <- stri_join(fillNode, sprintf( "", stri_join( stri_join(names(fill$fillFg), '="', fill$fillFg, '"'), sep = " ", collapse = " " ) )) } if (!is.null(fill$fillBg)) { fillNode <- stri_join(fillNode, sprintf( "", stri_join( stri_join(names(fill$fillBg), '="', fill$fillBg, '"'), sep = " ", collapse = " " ) )) } fillNode <- stri_join(fillNode, "") } else { return(NULL) } return(fillNode) } ) Workbook$methods( setSheetName = function(sheet, newSheetName) { if (newSheetName %in% sheet_names) { stop(sprintf("Sheet %s already exists!", newSheetName)) } sheet <- validateSheet(sheet) oldName <- sheet_names[[sheet]] sheet_names[[sheet]] <<- newSheetName ## Rename in workbook sheetId <- regmatches( workbook$sheets[[sheet]], regexpr('(?<=sheetId=")[0-9]+', workbook$sheets[[sheet]], perl = TRUE) ) rId <- regmatches( workbook$sheets[[sheet]], regexpr('(?<= r:id="rId)[0-9]+', workbook$sheets[[sheet]], perl = TRUE) ) workbook$sheets[[sheet]] <<- sprintf( '', newSheetName, sheetId, rId ) ## rename styleObjects sheet component if (length(styleObjects) > 0) { styleObjects <<- lapply(styleObjects, function(x) { if (x$sheet == oldName) { x$sheet <- newSheetName } return(x) }) } ## rename defined names if (length(workbook$definedNames) > 0) { belongTo <- getDefinedNamesSheet(workbook$definedNames) toChange <- belongTo == oldName if (any(toChange)) { newSheetName <- sprintf("'%s'", newSheetName) tmp <- gsub(oldName, newSheetName, workbook$definedName[toChange], fixed = TRUE) tmp <- gsub("'+", "'", tmp) workbook$definedNames[toChange] <<- tmp } } } ) Workbook$methods( writeSheetDataXML = function(xldrawingsDir, xldrawingsRelsDir, xlworksheetsDir, xlworksheetsRelsDir) { ## write worksheets # nSheets <- length(worksheets) for (i in seq_along(worksheets)) { ## Write drawing i (will always exist) skip those that are empty if (any(drawings[[i]] != "")) { write_file( head = '', body = pxml(drawings[[i]]), tail = "", fl = file.path(xldrawingsDir, stri_join("drawing", i, ".xml")) ) write_file( head = '', body = pxml(drawings_rels[[i]]), tail = "", fl = file.path(xldrawingsRelsDir, stri_join("drawing", i, ".xml.rels")) ) } else { worksheets[[i]]$drawing <<- character(0) } ## vml drawing if (length(vml_rels[[i]]) > 0) { file.copy( from = vml_rels[[i]], to = file.path( xldrawingsRelsDir, stri_join("vmlDrawing", i, ".vml.rels") ) ) } # outlineLevelRow in SheetformatPr if ((length(outlineLevels[[i]]) > 0) && (!grepl("outlineLevelRow", worksheets[[i]]$sheetFormatPr))) { worksheets[[i]]$sheetFormatPr <<- gsub("/>", ' outlineLevelRow="1"/>', worksheets[[i]]$sheetFormatPr) } if (isChartSheet[i]) { chartSheetDir <- file.path(dirname(xlworksheetsDir), "chartsheets") chartSheetRelsDir <- file.path(dirname(xlworksheetsDir), "chartsheets", "_rels") if (!file.exists(chartSheetDir)) { dir.create(chartSheetDir, recursive = TRUE) dir.create(chartSheetRelsDir, recursive = TRUE) } write_file( body = worksheets[[i]]$get_prior_sheet_data(), fl = file.path(chartSheetDir, stri_join("sheet", i, ".xml")) ) write_file( head = '', body = pxml(worksheets_rels[[i]]), tail = "", fl = file.path(chartSheetRelsDir, sprintf("sheet%s.xml.rels", i)) ) } else { ## Write worksheets ws <- worksheets[[i]] hasHL <- ifelse(length(worksheets[[i]]$hyperlinks) > 0, TRUE, FALSE) ## reorder sheet data worksheets[[i]]$order_sheetdata() prior <- ws$get_prior_sheet_data() post <- ws$get_post_sheet_data() worksheets[[i]]$sheet_data$style_id <<- as.character(worksheets[[i]]$sheet_data$style_id) if ((length(rowHeights[[i]]) == 0) & (length(outlineLevels[[i]]) == 0)) { write_worksheet_xml( prior = prior, post = post, sheet_data = ws$sheet_data, R_fileName = file.path(xlworksheetsDir, sprintf("sheet%s.xml", i)) ) } else if ((length(rowHeights[[i]]) == 0) & (length(outlineLevels[[i]]) > 0)) { write_worksheet_xml_2( prior = prior, post = post, sheet_data = ws$sheet_data, row_heights_ = NULL, outline_levels_ = unlist(outlineLevels[[i]]), R_fileName = file.path(xlworksheetsDir, sprintf("sheet%s.xml", i)) ) } else if ((length(rowHeights[[i]]) > 0) & (length(outlineLevels[[i]]) == 0)) { write_worksheet_xml_2( prior = prior, post = post, sheet_data = ws$sheet_data, row_heights_ = unlist(rowHeights[[i]]), outline_levels_ = NULL, R_fileName = file.path(xlworksheetsDir, sprintf("sheet%s.xml", i)) ) } else { ## row heights will always be in order and all row heights are given rows in preSaveCleanup write_worksheet_xml_2( prior = prior, post = post, sheet_data = ws$sheet_data, row_heights_ = unlist(rowHeights[[i]]), outline_levels_ = unlist(outlineLevels[[i]]), R_fileName = file.path(xlworksheetsDir, sprintf("sheet%s.xml", i)) ) } worksheets[[i]]$sheet_data$style_id <<- integer(0) ## write worksheet rels if (length(worksheets_rels[[i]]) > 0) { ws_rels <- worksheets_rels[[i]] if (hasHL) { h_inds <- stri_join(seq_along(worksheets[[i]]$hyperlinks), "h") ws_rels <- c(ws_rels, unlist( lapply(seq_along(h_inds), function(j) { worksheets[[i]]$hyperlinks[[j]]$to_target_xml(h_inds[j]) }) )) } ## Check if any tables were deleted - remove these from rels if (length(tables) > 0) { table_inds <- grep("tables/table[0-9].xml", ws_rels) if (length(table_inds) > 0) { ids <- regmatches( ws_rels[table_inds], regexpr( '(?<=Relationship Id=")[0-9A-Za-z]+', ws_rels[table_inds], perl = TRUE ) ) inds <- as.integer(gsub("[^0-9]", "", ids, perl = TRUE)) - 2L table_nms <- attr(tables, "tableName")[inds] is_deleted <- grepl("openxlsx_deleted", table_nms, fixed = TRUE) if (any(is_deleted)) { ws_rels <- ws_rels[-table_inds[is_deleted]] } } } write_file( head = '', body = pxml(ws_rels), tail = "", fl = file.path(xlworksheetsRelsDir, sprintf("sheet%s.xml.rels", i)) ) } } ## end of isChartSheet[i] } ## end of loop through 1:nSheets invisible(0) } ) Workbook$methods( setRowHeights = function(sheet, rows, heights) { sheet <- validateSheet(sheet) ## remove any conflicting heights flag <- names(rowHeights[[sheet]]) %in% rows if (any(flag)) { rowHeights[[sheet]] <<- rowHeights[[sheet]][!flag] } nms <- c(names(rowHeights[[sheet]]), rows) allRowHeights <- unlist(c(rowHeights[[sheet]], heights)) names(allRowHeights) <- nms allRowHeights <- allRowHeights[order(as.integer(names(allRowHeights)))] rowHeights[[sheet]] <<- allRowHeights } ) Workbook$methods( groupColumns = function(sheet) { sheet <- validateSheet(sheet) hidden <- attr(colOutlineLevels[[sheet]], "hidden", exact = TRUE) cols <- names(colOutlineLevels[[sheet]]) if (!grepl("outlineLevelCol", worksheets[[sheet]]$sheetFormatPr)) { worksheets[[sheet]]$sheetFormatPr <<- sub("/>", ' outlineLevelCol="1"/>', worksheets[[sheet]]$sheetFormatPr) } # Check if column is already created (by `setColWidths()` or on import) # Note that columns are initiated by `setColWidths` first (see: order of execution in `preSaveCleanUp()`) if (any(cols %in% names(worksheets[[sheet]]$cols))) { for (i in intersect(cols, names(worksheets[[sheet]]$cols))) { outline_hidden <- attr(colOutlineLevels[[sheet]], "hidden")[attr(colOutlineLevels[[sheet]], "names") == i] if (grepl("outlineLevel", worksheets[[sheet]]$cols[[i]], perl = TRUE)) { worksheets[[sheet]]$cols[[i]] <<- sub("((?<=hidden=\")(\\w+)\")", paste0(outline_hidden, "\""), worksheets[[sheet]]$cols[[i]], perl = TRUE) } else { worksheets[[sheet]]$cols[[i]] <<- sub("((?<=hidden=\")(\\w+)\")", paste0(outline_hidden, "\" outlineLevel=\"1\""), worksheets[[sheet]]$cols[[i]], perl = TRUE) } } cols <- cols[!cols %in% names(worksheets[[sheet]]$cols)] hidden <- attr(colOutlineLevels[[sheet]], "hidden")[attr(colOutlineLevels[[sheet]], "names") %in% cols] } if (length(cols) > 0) { colNodes <- sprintf('', cols, cols, hidden) names(colNodes) <- cols worksheets[[sheet]]$cols <<- append(worksheets[[sheet]]$cols, colNodes) } } ) Workbook$methods( groupRows = function(sheet, rows, hidden, levels) { sheet <- validateSheet(sheet) flag <- names(outlineLevels[[sheet]]) %in% rows if (any(flag)) { outlineLevels[[sheet]] <<- outlineLevels[[sheet]][!flag] } nms <- c(names(outlineLevels[[sheet]]), rows) allOutlineLevels <- unlist(c(outlineLevels[[sheet]], levels)) names(allOutlineLevels) <- nms existing_hidden <- attr(outlineLevels[[sheet]], "hidden", exact = TRUE) all_hidden <- c(existing_hidden, as.character(as.integer(hidden))) allOutlineLevels <- allOutlineLevels[order(as.integer(names(allOutlineLevels)))] outlineLevels[[sheet]] <<- allOutlineLevels attr(outlineLevels[[sheet]], "hidden") <<- as.character(as.integer(all_hidden)) if (!grepl("outlineLevelRow", worksheets[[sheet]]$sheetFormatPr)) { worksheets[[sheet]]$sheetFormatPr <<- gsub("/>", ' outlineLevelRow="1"/>', worksheets[[sheet]]$sheetFormatPr) } } ) Workbook$methods( deleteWorksheet = function(sheet) { # To delete a worksheet # Remove colwidths element # Remove drawing partname from Content_Types (drawing(sheet).xml) # Remove highest sheet from Content_Types # Remove drawings element # Remove drawings_rels element # Remove vml element # Remove vml_rels element # Remove rowHeights element # Remove styleObjects on sheet # Remove last sheet element from workbook # Remove last sheet element from workbook.xml.rels # Remove element from worksheets # Remove element from worksheets_rels # Remove hyperlinks # Reduce calcChain i attributes & remove calcs on sheet # Remove sheet from sheetOrder # Remove queryTable references from workbook$definedNames to worksheet # remove tables sheet <- validateSheet(sheet) sheetNames <- sheet_names nSheets <- length(unlist(sheetNames, use.names = FALSE)) sheetName <- sheetNames[[sheet]] colWidths[[sheet]] <<- NULL sheet_names <<- sheet_names[-sheet] ## remove last drawings(sheet).xml from Content_Types Content_Types <<- Content_Types[!grepl(sprintf("drawing%s.xml", nSheets), Content_Types)] ## remove highest sheet Content_Types <<- Content_Types[!grepl(sprintf("sheet%s.xml", nSheets), Content_Types)] drawings[[sheet]] <<- NULL drawings_rels[[sheet]] <<- NULL vml[[sheet]] <<- NULL vml_rels[[sheet]] <<- NULL rowHeights[[sheet]] <<- NULL colOutlineLevels[[sheet]] <<- NULL outlineLevels[[sheet]] <<- NULL comments[[sheet]] <<- NULL threadComments[[sheet]] <<- NULL isChartSheet <<- isChartSheet[-sheet] ## sheetOrder toRemove <- which(sheetOrder == sheet) sheetOrder[sheetOrder > sheet] <<- sheetOrder[sheetOrder > sheet] - 1L sheetOrder <<- sheetOrder[-toRemove] ## remove styleObjects if (length(styleObjects) > 0) { styleObjects <<- styleObjects[unlist(lapply(styleObjects, "[[", "sheet"), use.names = FALSE) != sheetName] } ## Need to remove reference from workbook.xml.rels to pivotCache removeRels <- grep("pivotTables", worksheets_rels[[sheet]], value = TRUE) if (length(removeRels) > 0) { ## sheet rels links to a pivotTable file, the corresponding pivotTable_rels file links to the cacheDefn which is listing in workbook.xml.rels ## remove reference to this file from the workbook.xml.rels fileNo <- as.integer(unlist(regmatches( removeRels, gregexpr("(?<=pivotTable)[0-9]+(?=\\.xml)", removeRels, perl = TRUE) ))) toRemove <- stri_join( sprintf("(pivotCacheDefinition%s\\.xml)", fileNo), sep = " ", collapse = "|" ) fileNo <- grep(toRemove, pivotTables.xml.rels) toRemove <- stri_join( sprintf("(pivotCacheDefinition%s\\.xml)", fileNo), sep = " ", collapse = "|" ) ## remove reference to file from workbook.xml.res workbook.xml.rels <<- workbook.xml.rels[!grepl(toRemove, workbook.xml.rels)] } ## As above for slicers ## Need to remove reference from workbook.xml.rels to pivotCache removeRels <- grepl("slicers", worksheets_rels[[sheet]]) if (any(removeRels)) { workbook.xml.rels <<- workbook.xml.rels[!grepl(sprintf("(slicerCache%s\\.xml)", sheet), workbook.xml.rels)] } ## wont't remove tables and then won't need to reassign table r:id's but will rename them! worksheets[[sheet]] <<- NULL worksheets_rels[[sheet]] <<- NULL if (length(tables) > 0) { tableSheets <- attr(tables, "sheet") tableNames <- attr(tables, "tableName") inds <- tableSheets %in% sheet & !grepl("openxlsx_deleted", attr(tables, "tableName"), fixed = TRUE) tableSheets[tableSheets > sheet] <- tableSheets[tableSheets > sheet] - 1L ## Need to flag a table as deleted if (any(inds)) { tableSheets[inds] <- 0 tableNames[inds] <- stri_join(tableNames[inds], "_openxlsx_deleted") } attr(tables, "tableName") <<- tableNames attr(tables, "sheet") <<- tableSheets } ## drawing will always be the first relationship and printerSettings second if (nSheets > 1) { for (i in 1:(nSheets - 1L)) { worksheets_rels[[i]][1:3] <<- genBaseSheetRels(i) } } else { worksheets_rels <<- list() } ## remove sheet sn <- unlist(lapply(workbook$sheets, function(x) { regmatches( x, regexpr('(?<= name=")[^"]+', x, perl = TRUE) ) })) workbook$sheets <<- workbook$sheets[!sn %in% sheetName] ## Reset rIds if (nSheets > 1) { for (i in (sheet + 1L):nSheets) { workbook$sheets <<- gsub(stri_join("rId", i), stri_join("rId", i - 1L), workbook$sheets, fixed = TRUE ) } } else { workbook$sheets <<- NULL } ## Can remove highest sheet workbook.xml.rels <<- workbook.xml.rels[!grepl(sprintf("sheet%s.xml", nSheets), workbook.xml.rels)] ## definedNames if (length(workbook$definedNames) > 0) { belongTo <- getDefinedNamesSheet(workbook$definedNames) workbook$definedNames <<- workbook$definedNames[!belongTo %in% sheetName] } invisible(1) } ) Workbook$methods( addDXFS = function(style) { dxf <- "" dxf <- stri_join(dxf, createFontNode(style)) fillNode <- NULL if (!is.null(style$fill$fillFg) | !is.null(style$fill$fillBg)) { dxf <- stri_join(dxf, createFillNode(style)) } if (any(!is.null( c( style$borderLeft, style$borderRight, style$borderTop, style$borderBottom, style$borderDiagonal ) ))) { dxf <- stri_join(dxf, createBorderNode(style)) } dxf <- stri_join(dxf, "", sep = " ") if (dxf %in% styles$dxfs) { return(which(styles$dxfs == dxf) - 1L) } dxfId <- length(styles$dxfs) styles$dxfs <<- c(styles$dxfs, dxf) return(dxfId) } ) Workbook$methods( dataValidation = function(sheet, startRow, endRow, startCol, endCol, type, operator, value, allowBlank, showInputMsg, showErrorMsg) { sheet <- validateSheet(sheet) sqref <- stri_join(getCellRefs(data.frame( "x" = c(startRow, endRow), "y" = c(startCol, endCol) )), sep = " ", collapse = ":" ) header <- sprintf( '', type, operator, allowBlank, showInputMsg, showErrorMsg, sqref ) if (type == "date") { origin <- 25569L if (grepl( 'date1904="1"|date1904="true"', stri_join(unlist(workbook), sep = " ", collapse = ""), ignore.case = TRUE )) { origin <- 24107L } value <- as.integer(value) + origin } if (type == "time") { origin <- 25569L if (grepl( 'date1904="1"|date1904="true"', stri_join(unlist(workbook), sep = " ", collapse = ""), ignore.case = TRUE )) { origin <- 24107L } t <- format(value[1], "%z") offSet <- suppressWarnings(ifelse(substr(t, 1, 1) == "+", 1L, -1L) * (as.integer(substr(t, 2, 3)) + as.integer(substr(t, 4, 5)) / 60) / 24) if (is.na(offSet)) { offSet[i] <- 0 } value <- as.numeric(as.POSIXct(value)) / 86400 + origin + offSet } form <- sapply(seq_along(value), function(i) { sprintf("%s", i, value[i], i) }) worksheets[[sheet]]$dataValidations <<- c( worksheets[[sheet]]$dataValidations, stri_join(header, stri_join(form, collapse = ""), "") ) invisible(0) } ) Workbook$methods( dataValidation_list = function(sheet, startRow, endRow, startCol, endCol, value, allowBlank, showInputMsg, showErrorMsg) { sheet <- validateSheet(sheet) sqref <- stri_join(getCellRefs(data.frame( "x" = c(startRow, endRow), "y" = c(startCol, endCol) )), sep = " ", collapse = ":" ) data_val <- sprintf( '', allowBlank, showInputMsg, showErrorMsg, sqref ) formula <- sprintf("%s", value) sqref <- sprintf("%s", sqref) xmlData <- stri_join(data_val, formula, sqref, "") worksheets[[sheet]]$dataValidationsLst <<- c(worksheets[[sheet]]$dataValidationsLst, xmlData) invisible(0) } ) Workbook$methods( conditionalFormatting = function(sheet, startRow, endRow, startCol, endCol, dxfId, formula, type, values, params) { sheet <- validateSheet(sheet) sqref <- stri_join(getCellRefs(data.frame( "x" = c(startRow, endRow), "y" = c(startCol, endCol) )), collapse = ":") ## Increment priority of conditional formatting rule if (length(worksheets[[sheet]]$conditionalFormatting) > 0) { for (i in rev(seq_along(worksheets[[sheet]]$conditionalFormatting))) { priority <- regmatches( worksheets[[sheet]]$conditionalFormatting[[i]], regexpr( '(?<=priority=")[0-9]+', worksheets[[sheet]]$conditionalFormatting[[i]], perl = TRUE ) ) priority_new <- as.integer(priority) + 1L priority_pattern <- sprintf('priority="%s"', priority) priority_new <- sprintf('priority="%s"', priority_new) ## now replace worksheets[[sheet]]$conditionalFormatting[[i]] <<- gsub(priority_pattern, priority_new, worksheets[[sheet]]$conditionalFormatting[[i]], fixed = TRUE ) } } nms <- c(names(worksheets[[sheet]]$conditionalFormatting), sqref) if (type == "colorScale") { ## formula contains the colours ## values contains numerics or is NULL ## dxfId is ignored if (is.null(values)) { if (length(formula) == 2L) { cfRule <- sprintf( ' ', formula[[1]], formula[[2]] ) } else { cfRule <- sprintf( ' ', formula[[1]], formula[[2]], formula[[3]] ) } } else { if (length(formula) == 2L) { cfRule <- sprintf( ' ', values[[1]], values[[2]], formula[[1]], formula[[2]] ) } else { cfRule <- sprintf( ' ', values[[1]], values[[2]], values[[3]], formula[[1]], formula[[2]], formula[[3]] ) } } } else if (type == "dataBar") { # forumula is a vector of colours of length 1 or 2 # values is NULL or a numeric vector of equal length as formula if (length(formula) == 2L) { negColour <- formula[[1]] posColour <- formula[[2]] } else { posColour <- formula negColour <- "FFFF0000" } guid <- stri_join( "F7189283-14F7-4DE0-9601-54DE9DB", 40000L + length(worksheets[[sheet]]$extLst) ) showValue <- 1 if ("showValue" %in% names(params)) { showValue <- as.integer(params$showValue) } gradient <- 1 if ("gradient" %in% names(params)) { gradient <- as.integer(params$gradient) } border <- 1 if ("border" %in% names(params)) { border <- as.integer(params$border) } if (is.null(values)) { cfRule <- sprintf( ' {%s} ', showValue, posColour, guid ) } else { cfRule <- sprintf( ' {%s}', showValue, values[[1]], values[[2]], posColour, guid ) } worksheets[[sheet]]$extLst <<- c( worksheets[[sheet]]$extLst, gen_databar_extlst( guid = guid, sqref = sqref, posColour = posColour, negColour = negColour, values = values, border = border, gradient = gradient ) ) } else if (type == "expression") { cfRule <- sprintf( '%s', dxfId, formula ) } else if (type == "duplicatedValues") { cfRule <- sprintf( '', dxfId ) } else if (type == "containsText") { cfRule <- sprintf( ' NOT(ISERROR(SEARCH("%s", %s))) ', dxfId, values, values, unlist(strsplit(sqref, split = ":"))[[1]] ) } else if (type == "notContainsText") { cfRule <- sprintf( ' ISERROR(SEARCH("%s", %s)) ', dxfId, values, values, unlist(strsplit(sqref, split = ":"))[[1]] ) } else if (type == "beginsWith") { cfRule <- sprintf( ' LEFT(%s,LEN("%s"))="%s" ', dxfId, values, unlist(strsplit(sqref, split = ":"))[[1]], values, values ) } else if (type == "endsWith") { cfRule <- sprintf( ' RIGHT(%s,LEN("%s"))="%s" ', dxfId, values, unlist(strsplit(sqref, split = ":"))[[1]], values, values ) } else if (type == "between") { cfRule <- sprintf( '%s%s', dxfId, formula[1], formula[2] ) } else if (type == "topN") { cfRule <- sprintf( '', dxfId, values[1], values[2] ) } else if (type == "bottomN") { cfRule <- sprintf( '', dxfId, values[1], values[2] ) } worksheets[[sheet]]$conditionalFormatting <<- append(worksheets[[sheet]]$conditionalFormatting, cfRule) names(worksheets[[sheet]]$conditionalFormatting) <<- nms invisible(0) } ) Workbook$methods( mergeCells = function(sheet, startRow, endRow, startCol, endCol) { sheet <- validateSheet(sheetName = sheet) sqref <- getCellRefs(data.frame( "x" = c(startRow, endRow), "y" = c(startCol, endCol) )) exMerges <- regmatches( worksheets[[sheet]]$mergeCells, regexpr("[A-Z0-9]+:[A-Z0-9]+", worksheets[[sheet]]$mergeCells) ) if (!is.null(exMerges)) { comps <- lapply(exMerges, function(rectCoords) { unlist(strsplit(rectCoords, split = ":")) }) exMergedCells <- build_cell_merges(comps = comps) newMerge <- unlist(build_cell_merges(comps = list(sqref))) ## Error if merge intersects mergeIntersections <- sapply(exMergedCells, function(x) { any(x %in% newMerge) }) if (any(mergeIntersections)) { stop( sprintf( "Merge intersects with existing merged cells: \n\t\t%s.\nRemove existing merge first.", stri_join(exMerges[mergeIntersections], collapse = "\n\t\t") ) ) } } worksheets[[sheet]]$mergeCells <<- c( worksheets[[sheet]]$mergeCells, sprintf( '', stri_join(sqref, collapse = ":", sep = " " ) ) ) } ) Workbook$methods( removeCellMerge = function(sheet, startRow, endRow, startCol, endCol) { sheet <- validateSheet(sheet) sqref <- getCellRefs(data.frame( "x" = c(startRow, endRow), "y" = c(startCol, endCol) )) exMerges <- regmatches( worksheets[[sheet]]$mergeCells, regexpr("[A-Z0-9]+:[A-Z0-9]+", worksheets[[sheet]]$mergeCells) ) if (!is.null(exMerges)) { comps <- lapply(exMerges, function(x) { unlist(strsplit(x, split = ":")) }) exMergedCells <- build_cell_merges(comps = comps) newMerge <- unlist(build_cell_merges(comps = list(sqref))) ## Error if merge intersects mergeIntersections <- sapply(exMergedCells, function(x) { any(x %in% newMerge) }) } ## Remove intersection worksheets[[sheet]]$mergeCells <<- worksheets[[sheet]]$mergeCells[!mergeIntersections] } ) Workbook$methods( freezePanes = function(sheet, firstActiveRow = NULL, firstActiveCol = NULL, firstRow = FALSE, firstCol = FALSE) { sheet <- validateSheet(sheet) paneNode <- NULL if (firstRow) { paneNode <- '' } else if (firstCol) { paneNode <- '' } if (is.null(paneNode)) { if (firstActiveRow == 1 & firstActiveCol == 1) { ## nothing to do return(NULL) } if (firstActiveRow > 1 & firstActiveCol == 1) { attrs <- sprintf('ySplit="%s"', firstActiveRow - 1L) activePane <- "bottomLeft" } if (firstActiveRow == 1 & firstActiveCol > 1) { attrs <- sprintf('xSplit="%s"', firstActiveCol - 1L) activePane <- "topRight" } if (firstActiveRow > 1 & firstActiveCol > 1) { attrs <- sprintf( 'ySplit="%s" xSplit="%s"', firstActiveRow - 1L, firstActiveCol - 1L ) activePane <- "bottomRight" } topLeftCell <- getCellRefs(data.frame(firstActiveRow, firstActiveCol)) paneNode <- sprintf( '', stri_join(attrs, collapse = " ", sep = " "), topLeftCell, activePane, activePane ) } worksheets[[sheet]]$freezePane <<- paneNode } ) Workbook$methods( insertImage = function(sheet, file, startRow, startCol, width, height, rowOffset = 0, colOffset = 0) { ## within the sheet the drawing node's Id refernce an id in the sheetRels ## sheet rels reference the drawingi.xml file ## drawingi.xml refernece drawingRels ## drawing rels reference an image in the media folder ## worksheetRels(sheet(i)) references drawings(j) sheet <- validateSheet(sheet) imageType <- regmatches(file, gregexpr("\\.[a-zA-Z]*$", file)) imageType <- gsub("^\\.", "", imageType) imageNo <- length((drawings[[sheet]])) + 1L mediaNo <- length(media) + 1L startCol <- convertFromExcelRef(startCol) ## update Content_Types if (!any(grepl(stri_join("image/", imageType), Content_Types))) { Content_Types <<- unique(c( sprintf( '', imageType, imageType ), Content_Types )) } ## drawings rels (Reference from drawings.xml to image file in media folder) drawings_rels[[sheet]] <<- c( drawings_rels[[sheet]], sprintf( '', imageNo, mediaNo, imageType ) ) ## write file path to media slot to copy across on save tmp <- file names(tmp) <- stri_join("image", mediaNo, ".", imageType) media <<- append(media, tmp) ## create drawing.xml anchor <- '' from <- sprintf( ' %s %s %s %s ', startCol - 1L, colOffset, startRow - 1L, rowOffset ) drawingsXML <- stri_join( anchor, from, sprintf( '', width, height ), genBasePic(imageNo), "", "" ) ## append to workbook drawing drawings[[sheet]] <<- c(drawings[[sheet]], drawingsXML) } ) Workbook$methods( preSaveCleanUp = function() { ## Steps # Order workbook.xml.rels: # sheets -> style -> theme -> sharedStrings -> persons -> tables -> calcChain # Assign workbook.xml.rels children rIds, seq_along(workbook.xml.rels) # Assign workbook$sheets rIds 1:nSheets # ## drawings will always be r:id1 on worksheet ## tables will always have r:id equal to table xml file number tables/table(i).xml ## Every worksheet has a drawingXML as r:id 1 ## Every worksheet has a printerSettings as r:id 2 ## Tables from r:id 3 to nTables+3 - 1 ## HyperLinks from nTables+3 to nTables+3+nHyperLinks-1 ## vmlDrawing to have rId sheetRIds <- as.integer(unlist(regmatches( workbook$sheets, gregexpr('(?<=r:id="rId)[0-9]+', workbook$sheets, perl = TRUE) ))) nSheets <- length(sheetRIds) nExtRefs <- length(externalLinks) nPivots <- length(pivotDefinitions) ## add a worksheet if none added if (nSheets == 0) { warning("Workbook does not contain any worksheets. A worksheet will be added.", call. = FALSE ) .self$addWorksheet("Sheet 1") nSheets <- 1L } ## get index of each child element for ordering sheetInds <- grep("(worksheets|chartsheets)/sheet[0-9]+\\.xml", workbook.xml.rels) stylesInd <- grep("styles\\.xml", workbook.xml.rels) themeInd <- grep("theme/theme[0-9]+.xml", workbook.xml.rels) connectionsInd <- grep("connections.xml", workbook.xml.rels) extRefInds <- grep("externalLinks/externalLink[0-9]+.xml", workbook.xml.rels) sharedStringsInd <- grep("sharedStrings.xml", workbook.xml.rels) tableInds <- grep("table[0-9]+.xml", workbook.xml.rels) personInds <- grep("person.xml", workbook.xml.rels) ## Reordering of workbook.xml.rels ## don't want to re-assign rIds for pivot tables or slicer caches pivotNode <- grep("pivotCache/pivotCacheDefinition[0-9].xml", workbook.xml.rels, value = TRUE) slicerNode <- grep("slicerCache[0-9]+.xml", workbook.xml.rels, value = TRUE) ## Reorder children of workbook.xml.rels workbook.xml.rels <<- workbook.xml.rels[c( sheetInds, extRefInds, themeInd, connectionsInd, stylesInd, sharedStringsInd, tableInds, personInds )] ## Re assign rIds to children of workbook.xml.rels workbook.xml.rels <<- unlist(lapply(seq_along(workbook.xml.rels), function(i) { gsub('(?<=Relationship Id="rId)[0-9]+', i, workbook.xml.rels[[i]], perl = TRUE ) })) workbook.xml.rels <<- c(workbook.xml.rels, pivotNode, slicerNode) if (!is.null(vbaProject)) { workbook.xml.rels <<- c( workbook.xml.rels, sprintf( '', 1L + length(workbook.xml.rels) ) ) } ## Reassign rId to workbook sheet elements, (order sheets by sheetId first) workbook$sheets <<- unlist(lapply(seq_along(workbook$sheets), function(i) { gsub('(?<= r:id="rId)[0-9]+', i, workbook$sheets[[i]], perl = TRUE) })) ## re-order worksheets if need to if (any(sheetOrder != seq_len(nSheets))) { workbook$sheets <<- workbook$sheets[sheetOrder] } ## re-assign tabSelected state <- rep.int("visible", nSheets) state[grepl("hidden", workbook$sheets)] <- "hidden" visible_sheet_index <- which(state %in% "visible")[[1]] visible_sheets <- which(state %in% "visible") workbook$bookViews <<- sprintf( '', visible_sheet_index - 1L, ActiveSheet - 1L ) for(i in seq_len(nSheets)) { worksheets[[i]]$sheetViews <<- sub( ' tabSelected="(1|true|false|0)"', ifelse( sheetOrder[ActiveSheet] == i, ' tabSelected="true"', ' tabSelected="false"' ), worksheets[[i]]$sheetViews, ignore.case = TRUE ) } # worksheets[[visible_sheet_index]]$sheetViews # worksheets[[visible_sheet_index]]$sheetViews <<- # sub( # '( tabSelected="0")|( tabSelected="false")', # ' tabSelected="1"', # worksheets[[visible_sheet_index]]$sheetViews, # ignore.case = TRUE # ) # if (nSheets > 1) { # for (i in (1:nSheets)[!(1:nSheets) %in% visible_sheet_index]) { # worksheets[[i]]$sheetViews <<- # sub( # ' tabSelected="(1|true|false|0)"', # ' tabSelected="false"', # worksheets[[i]]$sheetViews, # ignore.case = TRUE # ) # } # } if (length(workbook$definedNames) > 0) { sheetNames <- sheet_names[sheetOrder] belongTo <- getDefinedNamesSheet(workbook$definedNames) ## sheetNames is in re-ordered order (order it will be displayed) newId <- match(belongTo, sheetNames) - 1L oldId <- as.numeric(regmatches( workbook$definedNames, regexpr( '(?<= localSheetId=")[0-9]+', workbook$definedNames, perl = TRUE ) )) for (i in seq_along(workbook$definedNames)) { if (!is.na(newId[i])) { workbook$definedNames[[i]] <<- gsub( sprintf('localSheetId=\"%s\"', oldId[i]), sprintf('localSheetId=\"%s\"', newId[i]), workbook$definedNames[[i]], fixed = TRUE ) } } } ## update workbook r:id to match reordered workbook.xml.rels externalLink element if (length(extRefInds) > 0) { newInds <- as.integer(seq_along(extRefInds) + length(sheetInds)) workbook$externalReferences <<- stri_join( "", stri_join( sprintf('', newInds), collapse = "" ), "" ) } ## styles numFmtIds <- 50000L for (i in which(!isChartSheet)) { worksheets[[i]]$sheet_data$style_id <<- rep.int(x = as.integer(NA), times = worksheets[[i]]$sheet_data$n_elements) } for (x in styleObjects) { if (length(x$rows) > 0 & length(x$cols) > 0) { this.sty <- x$style$copy() if (!is.null(this.sty$numFmt)) { if (this.sty$numFmt$numFmtId == 9999) { this.sty$numFmt$numFmtId <- numFmtIds numFmtIds <- numFmtIds + 1L } } ## convert sheet name to index sheet <- which(sheet_names == x$sheet) sId <- .self$updateStyles(this.sty) ## this creates the XML for styles.XML cells_to_style <- stri_join(x$rows, x$cols, sep = ",") existing_cells <- stri_join(worksheets[[sheet]]$sheet_data$rows, worksheets[[sheet]]$sheet_data$cols, sep = "," ) ## In here we create any style_ids that don't yet exist in sheet_data worksheets[[sheet]]$sheet_data$style_id[existing_cells %in% cells_to_style] <<- sId new_cells_to_append <- which(!cells_to_style %in% existing_cells) if (length(new_cells_to_append) > 0) { worksheets[[sheet]]$sheet_data$style_id <<- c( worksheets[[sheet]]$sheet_data$style_id, rep.int(x = sId, times = length(new_cells_to_append)) ) worksheets[[sheet]]$sheet_data$rows <<- c(worksheets[[sheet]]$sheet_data$rows, x$rows[new_cells_to_append]) worksheets[[sheet]]$sheet_data$cols <<- c(worksheets[[sheet]]$sheet_data$cols, x$cols[new_cells_to_append]) worksheets[[sheet]]$sheet_data$t <<- c(worksheets[[sheet]]$sheet_data$t, rep(as.integer(NA), length(new_cells_to_append))) worksheets[[sheet]]$sheet_data$v <<- c( worksheets[[sheet]]$sheet_data$v, rep(as.character(NA), length(new_cells_to_append)) ) worksheets[[sheet]]$sheet_data$f <<- c( worksheets[[sheet]]$sheet_data$f, rep(as.character(NA), length(new_cells_to_append)) ) worksheets[[sheet]]$sheet_data$data_count <<- worksheets[[sheet]]$sheet_data$data_count + 1L worksheets[[sheet]]$sheet_data$n_elements <<- as.integer(length(worksheets[[sheet]]$sheet_data$rows)) } } } ## Make sure all rowHeights have rows, if not append them! for (i in seq_along(worksheets)) { if (length(rowHeights[[i]]) > 0) { rh <- as.integer(names(rowHeights[[i]])) missing_rows <- rh[!rh %in% worksheets[[i]]$sheet_data$rows] n <- length(missing_rows) if (n > 0) { worksheets[[i]]$sheet_data$style_id <<- c( worksheets[[i]]$sheet_data$style_id, rep.int(as.integer(NA), times = n) ) worksheets[[i]]$sheet_data$rows <<- c(worksheets[[i]]$sheet_data$rows, missing_rows) worksheets[[i]]$sheet_data$cols <<- c( worksheets[[i]]$sheet_data$cols, rep.int(as.integer(NA), times = n) ) worksheets[[i]]$sheet_data$t <<- c(worksheets[[i]]$sheet_data$t, rep(as.integer(NA), times = n)) worksheets[[i]]$sheet_data$v <<- c( worksheets[[i]]$sheet_data$v, rep(as.character(NA), times = n) ) worksheets[[i]]$sheet_data$f <<- c( worksheets[[i]]$sheet_data$f, rep(as.character(NA), times = n) ) worksheets[[i]]$sheet_data$data_count <<- worksheets[[i]]$sheet_data$data_count + 1L worksheets[[i]]$sheet_data$n_elements <<- as.integer(length(worksheets[[i]]$sheet_data$rows)) } } ## write colwidth and coloutline XML if (length(colWidths[[i]]) > 0) { invisible(.self$setColWidths(i)) } if (length(colOutlineLevels[[i]]) > 0) { invisible(.self$groupColumns(i)) } if(ActiveSheet==i) { worksheets[[sheetOrder[i]]]$sheetViews <<- stri_replace_all_regex( worksheets[[sheetOrder[i]]]$sheetViews, "tabSelected=\"(1|true|false|0)\"", paste0("tabSelected=\"true\"") ) } else { worksheets[[sheetOrder[i]]]$sheetViews <<- stri_replace_all_regex( worksheets[[sheetOrder[i]]]$sheetViews, "tabSelected=\"(1|true|false|0)\"", paste0("tabSelected=\"false\"") ) } } } ) Workbook$methods( addStyle = function(sheet, style, rows, cols, stack) { sheet <- sheet_names[[sheet]] if (length(styleObjects) == 0) { styleObjects <<- list(list( style = style, sheet = sheet, rows = rows, cols = cols )) } else if (stack) { nStyles <- length(styleObjects) ## ********** Assume all styleObjects cells have one a single worksheet ********** ## Loop through existing styleObjects newInds <- seq_along(rows) keepStyle <- rep(TRUE, nStyles) for (i in 1:nStyles) { if (sheet == styleObjects[[i]]$sheet) { ## Now check rows and cols intersect ## toRemove are the elements that the new style doesn't apply to, we remove these from the style object as it ## is copied, merged with the new style and given the new data points ex_row_cols <- stri_join(styleObjects[[i]]$rows, styleObjects[[i]]$cols, sep = "-") new_row_cols <- stri_join(rows, cols, sep = "-") ## mergeInds are the intersection of the two styles that will need to merge mergeInds <- which(new_row_cols %in% ex_row_cols) ## newInds are inds that don't exist in the current - this cumulates until the end to see if any are new newInds <- newInds[!newInds %in% mergeInds] ## If the new style does not merge if (length(mergeInds) > 0) { to_remove_from_this_style_object <- which(ex_row_cols %in% new_row_cols) ## the new style intersects with this styleObjects[[i]], we need to remove the intersecting rows and ## columns from styleObjects[[i]] if (length(to_remove_from_this_style_object) > 0) { ## remove these from style object styleObjects[[i]]$rows <<- styleObjects[[i]]$rows[-to_remove_from_this_style_object] styleObjects[[i]]$cols <<- styleObjects[[i]]$cols[-to_remove_from_this_style_object] if (length(styleObjects[[i]]$rows) == 0 | length(styleObjects[[i]]$cols) == 0) { keepStyle[i] <- FALSE } ## this style applies to no rows or columns anymore } ## append style object for intersecting cells ## we are appending a new style keepStyle <- c(keepStyle, TRUE) ## keepStyle is used to remove styles that apply to 0 rows OR 0 columns ## Merge Style and append to styleObjects styleObjects <<- append(styleObjects, list( list( style = mergeStyle(styleObjects[[i]]$style, newStyle = style), sheet = sheet, rows = rows[mergeInds], cols = cols[mergeInds] ) )) } } ## if sheet == styleObjects[[i]]$sheet } ## End of loop through styles ## remove any styles that no longer have any affect if (!all(keepStyle)) { styleObjects <<- styleObjects[keepStyle] } ## append style object for non-intersecting cells if (length(newInds) > 0) { styleObjects <<- append(styleObjects, list(list( style = style, sheet = sheet, rows = rows[newInds], cols = cols[newInds] ))) } } else { ## else we are not stacking styleObjects <<- append(styleObjects, list(list( style = style, sheet = sheet, rows = rows, cols = cols ))) } ## End if(length(styleObjects) > 0) else if(stack) {} } ) Workbook$methods( createNamedRegion = function(ref1, ref2, name, sheet, localSheetId = NULL) { name <- replaceIllegalCharacters(name) if (is.null(localSheetId)) { workbook$definedNames <<- c( workbook$definedNames, sprintf( '\'%s\'!%s:%s', name, sheet, ref1, ref2 ) ) } else { workbook$definedNames <<- c( workbook$definedNames, sprintf( '\'%s\'!%s:%s', name, localSheetId, sheet, ref1, ref2 ) ) } } ) Workbook$methods( validate_table_name = function(tableName) { tableName <- tolower(tableName) ## Excel forces named regions to lowercase if (nchar(tableName) > 255) { stop("tableName must be less than 255 characters.") } if (grepl("$", tableName, fixed = TRUE)) { stop("'$' character cannot exist in a tableName") } if (grepl(" ", tableName, fixed = TRUE)) { stop("spaces cannot exist in a table name") } # if(!grepl("^[A-Za-z_]", tableName, perl = TRUE)) # stop("tableName must begin with a letter or an underscore") if (grepl("R[0-9]+C[0-9]+", tableName, perl = TRUE, ignore.case = TRUE )) { stop("tableName cannot be the same as a cell reference, such as R1C1") } if (grepl("^[A-Z]{1,3}[0-9]+$", tableName, ignore.case = TRUE)) { stop("tableName cannot be the same as a cell reference") } if (tableName %in% attr(tables, "tableName")) { stop(sprintf("Table with name '%s' already exists!", tableName)) } return(tableName) } ) Workbook$methods( check_overwrite_tables = function(sheet, new_rows, new_cols, error_msg = "Cannot overwrite existing table with another table.", check_table_header_only = FALSE) { ## check not overwriting another table if (length(tables) > 0) { tableSheets <- attr(tables, "sheet") sheetNo <- validateSheet(sheet) to_check <- which(tableSheets %in% sheetNo & !grepl("openxlsx_deleted", attr(tables, "tableName"), fixed = TRUE)) if (length(to_check) > 0) { ## only look at tables on this sheet exTable <- tables[to_check] rows <- lapply(names(exTable), function(rectCoords) { as.numeric(unlist(regmatches( rectCoords, gregexpr("[0-9]+", rectCoords) ))) }) cols <- lapply(names(exTable), function(rectCoords) { convertFromExcelRef(unlist(regmatches( rectCoords, gregexpr("[A-Z]+", rectCoords) ))) }) if (check_table_header_only) { rows <- lapply(rows, function(x) { c(x[1], x[1]) }) } ## loop through existing tables checking if any over lap with new table for (i in seq_along(exTable)) { existing_cols <- cols[[i]] existing_rows <- rows[[i]] if ((min(new_cols) <= max(existing_cols)) & (max(new_cols) >= min(existing_cols)) & (min(new_rows) <= max(existing_rows)) & (max(new_rows) >= min(existing_rows))) { stop(error_msg) } } } ## end if(sheet %in% tableSheets) } ## end (length(tables) > 0) invisible(0) } ) Workbook$methods( show = function() { exSheets <- sheet_names nSheets <- length(exSheets) nImages <- length(media) nCharts <- length(charts) nStyles <- length(styleObjects) aSheet <- ActiveSheet exSheets <- replaceXMLEntities(exSheets) showText <- "A Workbook object.\n" if (length(aSheet) == 0) { aSheet <- 1 } ## worksheets if (nSheets > 0) { showText <- c(showText, "\nWorksheets:\n") sheetTxt <- lapply(1:nSheets, function(i) { tmpTxt <- sprintf('Sheet %s: "%s"\n', i, exSheets[[i]]) if (length(rowHeights[[i]]) > 0) { tmpTxt <- append( tmpTxt, c( "\n\tCustom row heights (row: height)\n\t", stri_join( sprintf("%s: %s", names(rowHeights[[i]]), round(as.numeric( rowHeights[[i]] ), 2)), collapse = ", ", sep = " " ) ) ) } if (length(outlineLevels[[i]]) > 0) { tmpTxt <- append( tmpTxt, c( "\n\tGrouped rows:\n\t", stri_join( sprintf("%s", names(outlineLevels[[i]])), collapse = ", ", sep = " " ) ) ) } if (length(colOutlineLevels[[i]]) > 0) { tmpTxt <- append( tmpTxt, c( "\n\tGrouped columns:\n\t", stri_join( sprintf("%s", names(colOutlineLevels[[i]])), collapse = ", ", sep = " " ) ) ) } if (length(colWidths[[i]]) > 0) { cols <- names(colWidths[[i]]) widths <- unname(colWidths[[i]]) widths[widths != "auto"] <- as.numeric(widths[widths != "auto"]) tmpTxt <- append( tmpTxt, c( "\n\tCustom column widths (column: width)\n\t ", stri_join( sprintf("%s: %s", cols, substr(widths, 1, 5)), sep = " ", collapse = ", " ) ) ) tmpTxt <- c(tmpTxt, "\n") } c(tmpTxt, "\n\n") }) showText <- c(showText, sheetTxt, "\n") } else { showText <- c(showText, "\nWorksheets:\n", "No worksheets attached\n") } ## images if (nImages > 0) { showText <- c( showText, "\nImages:\n", sprintf('Image %s: "%s"\n', 1:nImages, media) ) } if (nCharts > 0) { showText <- c( showText, "\nCharts:\n", sprintf('Chart %s: "%s"\n', 1:nCharts, charts) ) } if (nSheets > 0) { showText <- c(showText, sprintf( "Worksheet write order: %s\n", stri_join(sheetOrder, sep = " ", collapse = ", ") )) } if (aSheet >= 1 & nSheets > 0) { showText <- c( showText, sprintf( 'Active Sheet %s: "%s" \n\tPosition: %s\n', sheetOrder[aSheet], exSheets[[sheetOrder[aSheet]]], aSheet ) ) } cat(unlist(showText)) cat("\n") } ) ## TO BE DEPRECATED Workbook$methods( conditionalFormatCell = function(sheet, startRow, endRow, startCol, endCol, dxfId, formula, type) { sheet <- validateSheet(sheet) sqref <- stri_join(getCellRefs(data.frame( "x" = c(startRow, endRow), "y" = c(startCol, endCol) )), collapse = ":") ## Increment priority of conditional formatting rule if (length((worksheets[[sheet]]$conditionalFormatting)) > 0) { for (i in rev(seq_along(worksheets[[sheet]]$conditionalFormatting))) { worksheets[[sheet]]$conditionalFormatting[[i]] <<- gsub('(?<=priority=")[0-9]+', i + 1L, worksheets[[sheet]]$conditionalFormatting[[i]], perl = TRUE ) } } nms <- c(names(worksheets[[sheet]]$conditionalFormatting), sqref) if (type == "expression") { cfRule <- sprintf( '%s', dxfId, formula ) } else if (type == "dataBar") { if (length(formula) == 2) { negColour <- formula[[1]] posColour <- formula[[2]] } else { posColour <- formula negColour <- "FFFF0000" } guid <- stri_join( "F7189283-14F7-4DE0-9601-54DE9DB", 40000L + length(worksheets[[sheet]]$extLst) ) cfRule <- sprintf( '{%s}', posColour, guid ) } else if (length(formula) == 2L) { cfRule <- sprintf( '', formula[[1]], formula[[2]] ) } else { cfRule <- sprintf( '', formula[[1]], formula[[2]], formula[[3]] ) } worksheets[[sheet]]$conditionalFormatting <<- append(worksheets[[sheet]]$conditionalFormatting, cfRule) names(worksheets[[sheet]]$conditionalFormatting) <<- nms invisible(0) } ) Workbook$methods( loadStyles = function(stylesXML) { ## Build style objects from the styles XML stylesTxt <- readUTF8(stylesXML) stylesTxt <- removeHeadTag(stylesTxt) ## Indexed colours vals <- getNodes(xml = stylesTxt, tagIn = "") if (length(vals) > 0) { styles$indexedColors <<- stri_join("", vals, "") } ## dxf (don't need these, I don't think) dxf <- getNodes(xml = stylesTxt, tagIn = " 0) { dxf <- getNodes(xml = dxf[[1]], tagIn = "") if (length(dxf) > 0) { styles$dxfs <<- dxf } } tableStyles <- getChildlessNode(stylesTxt, tag = "tableStyles") if (length(tableStyles) > 0) { styles$tableStyles <<- tableStyles } extLst <- getChildlessNode(stylesTxt, tag = "extLst") if (length(extLst) > 0) { styles$extLst <<- extLst } ## Number formats numFmts <- getChildlessNode(xml = stylesTxt, tag = "numFmt") numFmtFlag <- FALSE if (length(numFmts) > 0) { numFmtsIds <- sapply(numFmts, getAttr, tag = 'numFmtId="', USE.NAMES = FALSE) formatCodes <- sapply(numFmts, getAttr, tag = 'formatCode="', USE.NAMES = FALSE) numFmts <- lapply(seq_along(numFmts), function(i) { list("numFmtId" = numFmtsIds[[i]], "formatCode" = formatCodes[[i]]) }) numFmtFlag <- TRUE } ## fonts will maintain, sz, color, name, family scheme if (grepl("", stylesTxt, fixed = TRUE)) { ## empty font node fonts <- getNodes(xml = stylesTxt, tagIn = "") borders <- substr( borders, start = regexpr("", borders)[1], stop = regexpr("", borders) - 1L ) borders <- getNodes(xml = borders, tagIn = "", stri_join( names(attr), '="', attr, '"', collapse = " ", sep = "" ) ) if (!is.null(type) | !is.null(password)) workbook$apps <<- sprintf("%i", type) } else { workbook$workbookProtection <<- "" } } ) Workbook$methods( addCreator = function(Creator = NULL) { if (!is.null(Creator)) { current_creator <- stri_match(core, regex = "(.*?)")[1, 2] core <<- stri_replace_all_fixed( core, pattern = current_creator, replacement = stri_c(current_creator, Creator, sep = ";") ) } } ) Workbook$methods( getCreators = function() { current_creator <- stri_match(core, regex = "(.*?)")[1, 2] current_creator_vec <- as.character(stri_split_fixed( str = current_creator, pattern = ";", simplify = T )) return(current_creator_vec) } ) Workbook$methods( changeLastModifiedBy = function(LastModifiedBy = NULL) { if (!is.null(LastModifiedBy)) { current_LastModifiedBy <- stri_match(core, regex = "(.*?)")[1, 2] core <<- stri_replace_all_fixed( core, pattern = current_LastModifiedBy, replacement = LastModifiedBy ) } } ) Workbook$methods( setactiveSheet = function(activeSheet = NULL) { if (is.character(activeSheet)) { if (activeSheet %in% sheet_names) { ActiveSheet <<- which(sheet_names[sheetOrder] == activeSheet) } else { stop(paste(activeSheet, "doesn't exist as sheet name.")) } } if (is.integer(activeSheet)|is.numeric(activeSheet)) { if (activeSheet %in% seq_along(sheet_names)) { ActiveSheet <<- which(sheetOrder==activeSheet) }else { stop(paste(activeSheet, "doesn't exist as sheet index.")) } } for(i in seq_along(sheet_names)){ worksheets[[i]]$sheetViews <<- stri_replace_all_regex(worksheets[[i]]$sheetViews, "tabSelected=\"(1|true|false|0)\"", paste0("tabSelected=\"", ifelse(sheetOrder[ActiveSheet] == i,"true","false") ,"\"")) } } ) openxlsx/R/asserts.R0000644000176200001440000000456714155600363014161 0ustar liggesusers# Assertions for parameter validates # These should be used at the beginning of functions to stop execution early assert_class <- function(x, class, or_null = FALSE) { sx <- as.character(substitute(x)) ok <- inherits(x, class) if (or_null) { ok <- ok | is.null(x) class <- c(class, "null") } if (!ok) { msg <- sprintf("%s must be of class %s", sx, paste(class, collapse = " or ")) stop(msg, call. = FALSE) } } assert_length <- function(x, n) { stopifnot(is.integer(n)) if (length(x) != n) { msg <- sprintf("%s must be of length %iL", substitute(x), n) stop(msg, call. = FALSE) } } assert_true_false1 <- function(x) { if (!is_true_false(x)) { stop(substitute(x), " must be TRUE or FALSE", call. = FALSE) } } assert_true_false <- function(x) { ok <- is.logical(x) & !is.na(x) if (!ok) { stop(substitute(x), " must be a logical vector with NAs", call. = FALSE) } } assert_character1 <- function(x, scalar = FALSE) { ok <- is.character(x) && length(x) == 1L if (scalar) { ok <- ok & nchar(x) == 1L } if (!ok) { stop(substitute(x), " must be a character vector of length 1L", call. = FALSE) } } assert_unique <- function(x, case_sensitive = TRUE) { msg <- paste0(substitute(x), " must be a unique vector") if (!case_sensitive) { x <- tolower(x) msg <- paste0(msg, " (case sensitive)") } if (anyDuplicated(x) != 0L) { stop(msg, call. = FALSE) } } assert_numeric1 <- function(x, scalar = FALSE) { msg <- paste0(substitute(x), " must be a ") ok <- is.numeric(x) & length(x) == 1L if (scalar) { ok <- ok && nchar(x) == 1L msg <- paste0(msg, "single number") } else { msg <- paste0(msg, "numeric vector of length 1L") } if (!ok) { stop(msg, call. = FALSE) } } # validates --------------------------------------------------------------- validate_StyleName <- function(x) { m <- valid_StyleNames[match(tolower(x), valid_StyleNames_low)] if (anyNA(m)) { stop( "Invalid table style: ", paste0(sprintf("'%s'", x[is.na(m)]), collapse = ", "), call. = FALSE ) } m } valid_StyleNames <- c("none", paste0("TableStyleLight", 1:21), paste0("TableStyleMedium", 1:28), paste0("TableStyleDark", 1:11)) valid_StyleNames_low <- tolower(valid_StyleNames) openxlsx/R/wrappers.R0000644000176200001440000044462514155600363014343 0ustar liggesusers #' @name createWorkbook #' @title Create a new Workbook object #' @description Create a new Workbook object #' @param creator Creator of the workbook (your name). Defaults to login username #' @param title Workbook properties title #' @param subject Workbook properties subject #' @param category Workbook properties category #' @author Alexander Walker #' @return Workbook object #' @export #' @seealso [loadWorkbook()] #' @seealso [saveWorkbook()] #' @import methods #' @examples #' ## Create a new workbook #' wb <- createWorkbook() #' #' ## Save workbook to working directory #' \dontrun{ #' saveWorkbook(wb, file = "createWorkbookExample.xlsx", overwrite = TRUE) #' } #' #' ## Set Workbook properties #' wb <- createWorkbook( #' creator = "Me", #' title = "title here", #' subject = "this & that", #' category = "something" #' ) createWorkbook <- function(creator = ifelse(.Platform$OS.type == "windows", Sys.getenv("USERNAME"), Sys.getenv("USER")), title = NULL, subject = NULL, category = NULL) { op <- get_set_options() on.exit(options(op), add = TRUE) ## check all inputs are valid if (length(creator) > 1) creator <- creator[[1]] if (length(creator) == 0) creator <- "" if (!"character" %in% class(creator)) creator <- "" if (length(title) > 1) title <- title[[1]] if (length(subject) > 1) subject <- subject[[1]] if (length(category) > 1) category <- category[[1]] if (!is.null(title)) { if (!"character" %in% class(title)) { stop("title must be a string") } } if (!is.null(subject)) { if (!"character" %in% class(subject)) { stop("subject must be a string") } } if (!is.null(category)) { if (!"character" %in% class(category)) { stop("category must be a string") } } invisible(Workbook$new(creator = creator, title = title, subject = subject, category = category)) } #' @name saveWorkbook #' @title save Workbook to file #' @description save a Workbook object to file #' @author Alexander Walker, Philipp Schauberger #' @param wb A Workbook object to write to file #' @param file A character string naming an xlsx file #' @param overwrite If `TRUE`, overwrite any existing file. #' @param returnValue If `TRUE`, returns `TRUE` in case of a success, else `FALSE`. #' If flag is `FALSE`, then no return value is returned. #' @seealso [createWorkbook()] #' @seealso [addWorksheet()] #' @seealso [loadWorkbook()] #' @seealso [writeData()] #' @seealso [writeDataTable()] #' @export #' @examples #' ## Create a new workbook and add a worksheet #' wb <- createWorkbook("Creator of workbook") #' addWorksheet(wb, sheetName = "My first worksheet") #' #' ## Save workbook to working directory #' \dontrun{ #' saveWorkbook(wb, file = "saveWorkbookExample.xlsx", overwrite = TRUE) #' } saveWorkbook <- function(wb, file, overwrite = FALSE, returnValue = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (!is.logical(overwrite)) { overwrite <- FALSE } if (!is.logical(returnValue)) { returnValue <- FALSE } if (file.exists(file) & !overwrite) { stop("File already exists!") } xlsx_file <- wb$saveWorkbook() result <- file.copy(from = xlsx_file, to = file, overwrite = overwrite) ## delete temporary dir unlink(dirname(xlsx_file), force = TRUE, recursive = TRUE) if (returnValue == FALSE) { invisible(1) } else { return(result) } } #' @name mergeCells #' @title Merge cells within a worksheet #' @description Merge cells within a worksheet #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Columns to merge #' @param rows corresponding rows to merge #' @details As merged region must be rectangular, only min and max of cols and rows are used. #' @author Alexander Walker #' @seealso [removeCellMerge()] #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook() #' #' ## Add a worksheet #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' #' ## Merge cells: Row 2 column C to F (3:6) #' mergeCells(wb, "Sheet 1", cols = 2, rows = 3:6) #' #' ## Merge cells:Rows 10 to 20 columns A to J (1:10) #' mergeCells(wb, 1, cols = 1:10, rows = 10:20) #' #' ## Intersecting merges #' mergeCells(wb, 2, cols = 1:10, rows = 1) #' mergeCells(wb, 2, cols = 5:10, rows = 2) #' mergeCells(wb, 2, cols = c(1, 10), rows = 12) ## equivalent to 1:10 as only min/max are used #' # mergeCells(wb, 2, cols = 1, rows = c(1,10)) # Throws error because intersects existing merge #' #' ## remove merged cells #' removeCellMerge(wb, 2, cols = 1, rows = 1) # removes any intersecting merges #' mergeCells(wb, 2, cols = 1, rows = 1:10) # Now this works #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "mergeCellsExample.xlsx", overwrite = TRUE) #' } mergeCells <- function(wb, sheet, cols, rows) { od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } wb$mergeCells(sheet, startRow = min(rows), endRow = max(rows), startCol = min(cols), endCol = max(cols)) } #' @name int2col #' @title Convert integer to Excel column #' @description Converts an integer to an Excel column label. #' @param x A numeric vector #' @export #' @examples #' int2col(1:10) int2col <- function(x) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!is.numeric(x)) { stop("x must be numeric.") } convert_to_excel_ref(cols = x, LETTERS = LETTERS) } #' @name col2int #' @title Convert Excel column to integer #' @description Converts an Excel column label to an integer. #' @param x A character vector #' @export #' @examples #' col2int(LETTERS) col2int <- function(x) { if (!is.character(x)) { stop("x must be character") } as.integer(sapply(x, cell_ref_to_col)) } #' @name removeCellMerge #' @title Create a new Workbook object #' @description Unmerges any merged cells that intersect #' with the region specified by, min(cols):max(cols) X min(rows):max(rows) #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols vector of column indices #' @param rows vector of row indices #' @author Alexander Walker #' @export #' @seealso [mergeCells()] removeCellMerge <- function(wb, sheet, cols, rows) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } cols <- convertFromExcelRef(cols) rows <- as.integer(rows) wb$removeCellMerge(sheet, startRow = min(rows), endRow = max(rows), startCol = min(cols), endCol = max(cols)) } #' @name sheets #' @title Returns names of worksheets. #' @description DEPRECATED. Use names(). #' @param wb A workbook object #' @return Name of worksheet(s) for a given index #' @author Alexander Walker #' @seealso [names()] to rename a worksheet in a Workbook #' @details DEPRECATED. Use [names()] #' @export #' @examples #' #' ## Create a new workbook #' wb <- createWorkbook() #' #' ## Add some worksheets #' addWorksheet(wb, "Worksheet Name") #' addWorksheet(wb, "This is worksheet 2") #' addWorksheet(wb, "The third worksheet") #' #' ## Return names of sheets, can not be used for assignment. #' names(wb) #' # openXL(wb) #' #' names(wb) <- c("A", "B", "C") #' names(wb) #' # openXL(wb) sheets <- function(wb) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } nms <- wb$sheet_names nms <- replaceXMLEntities(nms) return(nms) } #' @name addWorksheet #' @title Add a worksheet to a workbook #' @description Add a worksheet to a Workbook object #' @author Alexander Walker #' @param wb A Workbook object to attach the new worksheet #' @param sheetName A name for the new worksheet #' @param gridLines A logical. If `FALSE`, the worksheet grid lines will be hidden. #' @param tabColour Colour of the worksheet tab. A valid colour (belonging to colours()) or a valid hex colour beginning with "#" #' @param zoom A numeric between 10 and 400. Worksheet zoom level as a percentage. #' @param header document header. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position. #' @param footer document footer. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position. #' @param evenHeader document header for even pages. #' @param evenFooter document footer for even pages. #' @param firstHeader document header for first page only. #' @param firstFooter document footer for first page only. #' @param visible If FALSE, sheet is hidden else visible. #' @param paperSize An integer corresponding to a paper size. See ?pageSetup for details. #' @param orientation One of "portrait" or "landscape" #' @param hdpi Horizontal DPI. Can be set with options("openxlsx.dpi" = X) or options("openxlsx.hdpi" = X) #' @param vdpi Vertical DPI. Can be set with options("openxlsx.dpi" = X) or options("openxlsx.vdpi" = X) #' @details Headers and footers can contain special tags #' \itemize{ #' \item{**&\[Page\]**}{ Page number} #' \item{**&\[Pages\]**}{ Number of pages} #' \item{**&\[Date\]**}{ Current date} #' \item{**&\[Time\]**}{ Current time} #' \item{**&\[Path\]**}{ File path} #' \item{**&\[File\]**}{ File name} #' \item{**&\[Tab\]**}{ Worksheet name} #' } #' @return XML tree #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook("Fred") #' #' ## Add 3 worksheets #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2", gridLines = FALSE) #' addWorksheet(wb, "Sheet 3", tabColour = "red") #' addWorksheet(wb, "Sheet 4", gridLines = FALSE, tabColour = "#4F81BD") #' #' ## Headers and Footers #' addWorksheet(wb, "Sheet 5", #' header = c("ODD HEAD LEFT", "ODD HEAD CENTER", "ODD HEAD RIGHT"), #' footer = c("ODD FOOT RIGHT", "ODD FOOT CENTER", "ODD FOOT RIGHT"), #' evenHeader = c("EVEN HEAD LEFT", "EVEN HEAD CENTER", "EVEN HEAD RIGHT"), #' evenFooter = c("EVEN FOOT RIGHT", "EVEN FOOT CENTER", "EVEN FOOT RIGHT"), #' firstHeader = c("TOP", "OF FIRST", "PAGE"), #' firstFooter = c("BOTTOM", "OF FIRST", "PAGE") #' ) #' #' addWorksheet(wb, "Sheet 6", #' header = c("&[Date]", "ALL HEAD CENTER 2", "&[Page] / &[Pages]"), #' footer = c("&[Path]&[File]", NA, "&[Tab]"), #' firstHeader = c(NA, "Center Header of First Page", NA), #' firstFooter = c(NA, "Center Footer of First Page", NA) #' ) #' #' addWorksheet(wb, "Sheet 7", #' header = c("ALL HEAD LEFT 2", "ALL HEAD CENTER 2", "ALL HEAD RIGHT 2"), #' footer = c("ALL FOOT RIGHT 2", "ALL FOOT CENTER 2", "ALL FOOT RIGHT 2") #' ) #' #' addWorksheet(wb, "Sheet 8", #' firstHeader = c("FIRST ONLY L", NA, "FIRST ONLY R"), #' firstFooter = c("FIRST ONLY L", NA, "FIRST ONLY R") #' ) #' #' ## Need data on worksheet to see all headers and footers #' writeData(wb, sheet = 5, 1:400) #' writeData(wb, sheet = 6, 1:400) #' writeData(wb, sheet = 7, 1:400) #' writeData(wb, sheet = 8, 1:400) #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "addWorksheetExample.xlsx", overwrite = TRUE) #' } addWorksheet <- function(wb, sheetName, gridLines = openxlsx_getOp("gridLines", TRUE), tabColour = NULL, zoom = 100, header = openxlsx_getOp("header"), footer = openxlsx_getOp("footer"), evenHeader = openxlsx_getOp("evenHeader"), evenFooter = openxlsx_getOp("evenFooter"), firstHeader = openxlsx_getOp("firstHeader"), firstFooter = openxlsx_getOp("firstFooter"), visible = TRUE, paperSize = openxlsx_getOp("paperSize", 9), orientation = openxlsx_getOp("orientation", "portrait"), vdpi = openxlsx_getOp("vdpi", 300), hdpi = openxlsx_getOp("hdpi", 300)) { op <- get_set_options() on.exit(options(op), add = TRUE) if (inherits(wb, "list")) { wb <- wb[[1]] } if (!inherits(wb, "Workbook")) { stop("wb must be a Workbok", call. = FALSE) } # Set NULL defaults gridLines <- gridLines %||% TRUE paperSize <- paperSize %||% 9 orientation <- orientation %||% "portrait" vdpi <- vdpi %||% 300 hdpi <- hdpi %||% 300 if (tolower(sheetName) %in% tolower(wb$sheet_names)) { stop(paste0("A worksheet by the name '", sheetName, "' already exists! Sheet names must be unique case-insensitive.")) } if (!is.logical(gridLines) | length(gridLines) > 1) { stop("gridLines must be a logical of length 1.") } if (nchar(sheetName) > 31) { stop(paste0("sheetName '", sheetName, "' too long! Max length is 31 characters.")) } if (!is.null(tabColour)) { tabColour <- validateColour(tabColour, "Invalid tabColour in addWorksheet.") } if (!is.numeric(zoom)) { stop("zoom must be numeric") } if (!is.character(sheetName)) { sheetName <- as.character(sheetName) } if (!is.null(header) & length(header) != 3) { stop("header must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(footer) & length(footer) != 3) { stop("footer must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(evenHeader) & length(evenHeader) != 3) { stop("evenHeader must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(evenFooter) & length(evenFooter) != 3) { stop("evenFooter must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(firstHeader) & length(firstHeader) != 3) { stop("firstHeader must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(firstFooter) & length(firstFooter) != 3) { stop("firstFooter must have length 3 where elements correspond to positions: left, center, right.") } visible <- tolower(visible[1]) if (!visible %in% c("true", "false", "hidden", "visible", "veryhidden")) { stop("visible must be one of: TRUE, FALSE, 'hidden', 'visible', 'veryHidden'") } orientation <- tolower(orientation) if (!orientation %in% c("portrait", "landscape")) { stop("orientation must be 'portrait' or 'landscape'.") } vdpi <- as.integer(vdpi) if (is.na(vdpi)) { stop("vdpi must be numeric") } hdpi <- as.integer(hdpi) if (is.na(hdpi)) { stop("hdpi must be numeric") } ## Invalid XML characters sheetName <- replaceIllegalCharacters(sheetName) invisible(wb$addWorksheet( sheetName = sheetName, showGridLines = gridLines, tabColour = tabColour, zoom = zoom[1], oddHeader = headerFooterSub(header), oddFooter = headerFooterSub(footer), evenHeader = headerFooterSub(evenHeader), evenFooter = headerFooterSub(evenFooter), firstHeader = headerFooterSub(firstHeader), firstFooter = headerFooterSub(firstFooter), visible = visible, paperSize = paperSize, orientation = orientation, vdpi = vdpi, hdpi = hdpi )) } #' @name cloneWorksheet #' @title Clone a worksheet to a workbook #' @description Clone a worksheet to a Workbook object #' @author Reinhold Kainhofer #' @param wb A Workbook object to attach the new worksheet #' @param sheetName A name for the new worksheet #' @param clonedSheet The name of the existing worksheet to be cloned. #' @return XML tree #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook("Fred") #' #' ## Add 3 worksheets #' addWorksheet(wb, "Sheet 1") #' cloneWorksheet(wb, "Sheet 2", clonedSheet = "Sheet 1") #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "cloneWorksheetExample.xlsx", overwrite = TRUE) #' } cloneWorksheet <- function(wb, sheetName, clonedSheet) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (tolower(sheetName) %in% tolower(wb$sheet_names)) { stop("A worksheet by that name already exists! Sheet names must be unique case-insensitive.") } if (nchar(sheetName) > 31) { stop("sheetName too long! Max length is 31 characters.") } if (!is.character(sheetName)) { sheetName <- as.character(sheetName) } ## Invalid XML characters sheetName <- replaceIllegalCharacters(sheetName) invisible(wb$cloneWorksheet(sheetName = sheetName, clonedSheet = clonedSheet)) } #' @name renameWorksheet #' @title Rename a worksheet #' @description Rename a worksheet #' @author Alexander Walker #' @param wb A Workbook object containing a worksheet #' @param sheet The name or index of the worksheet to rename #' @param newName The new name of the worksheet. No longer than 31 chars. #' @details DEPRECATED. Use [names()] #' @export #' @examples #' #' ## Create a new workbook #' wb <- createWorkbook("CREATOR") #' #' ## Add 3 worksheets #' addWorksheet(wb, "Worksheet Name") #' addWorksheet(wb, "This is worksheet 2") #' addWorksheet(wb, "Not the best name") #' #' #' ## rename all worksheets #' names(wb) <- c("A", "B", "C") #' #' #' ## Rename worksheet 1 & 3 #' renameWorksheet(wb, 1, "New name for sheet 1") #' names(wb)[[1]] <- "New name for sheet 1" #' names(wb)[[3]] <- "A better name" #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "renameWorksheetExample.xlsx", overwrite = TRUE) #' } renameWorksheet <- function(wb, sheet, newName) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } op <- get_set_options() on.exit(options(op), add = TRUE) invisible(wb$setSheetName(sheet, newName)) } #' @name convertFromExcelRef #' @title Convert excel column name to integer index #' @description Convert excel column name to integer index e.g. "J" to 10 #' @param col An excel column reference #' @export #' @examples #' convertFromExcelRef("DOG") #' convertFromExcelRef("COW") #' #' ## numbers will be removed #' convertFromExcelRef("R22") convertFromExcelRef <- function(col) { ## increase scipen to avoid writing in scientific op <- get_set_options() on.exit(options(op), add = TRUE) col <- toupper(col) charFlag <- grepl("[A-Z]", col) if (any(charFlag)) { col[charFlag] <- gsub("[0-9]", "", col[charFlag]) d <- lapply(strsplit(col[charFlag], split = ""), function(x) match(rev(x), LETTERS)) col[charFlag] <- unlist(lapply(seq_along(d), function(i) { sum(d[[i]] * (26^( seq_along(d[[i]]) - 1))) })) } col[!charFlag] <- as.integer(col[!charFlag]) return(as.integer(col)) } #' @name createStyle #' @title Create a cell style #' @description Create a new style to apply to worksheet cells #' @author Alexander Walker #' @seealso [addStyle()] #' @param fontName A name of a font. Note the font name is not validated. If fontName is NULL, #' the workbook base font is used. (Defaults to Calibri) #' @param fontColour Colour of text in cell. A valid hex colour beginning with "#" #' or one of colours(). If fontColour is NULL, the workbook base font colours is used. #' (Defaults to black) #' @param fontSize Font size. A numeric greater than 0. #' If fontSize is NULL, the workbook base font size is used. (Defaults to 11) #' @param numFmt Cell formatting #' \itemize{ #' \item{**GENERAL**} #' \item{**NUMBER**} #' \item{**CURRENCY**} #' \item{**ACCOUNTING**} #' \item{**DATE**} #' \item{**LONGDATE**} #' \item{**TIME**} #' \item{**PERCENTAGE**} #' \item{**FRACTION**} #' \item{**SCIENTIFIC**} #' \item{**TEXT**} #' \item{**COMMA**{ for comma separated thousands}} #' \item{For date/datetime styling a combination of d, m, y and punctuation marks} #' \item{For numeric rounding use "0.00" with the preferred number of decimal places} #' } #' #' @param border Cell border. A vector of "top", "bottom", "left", "right" or a single string). #' \itemize{ #' \item{**"top"**}{ Top border} #' \item{**bottom**}{ Bottom border} #' \item{**left**}{ Left border} #' \item{**right**}{ Right border} #' \item{**TopBottom** or **c("top", "bottom")**}{ Top and bottom border} #' \item{**LeftRight** or **c("left", "right")**}{ Left and right border} #' \item{**TopLeftRight** or **c("top", "left", "right")**}{ Top, Left and right border} #' \item{**TopBottomLeftRight** or **c("top", "bottom", "left", "right")**}{ All borders} #' } #' #' @param borderColour Colour of cell border vector the same length as the number of sides specified in "border" #' A valid colour (belonging to colours()) or a valid hex colour beginning with "#" #' #' @param borderStyle Border line style vector the same length as the number of sides specified in "border" #' \itemize{ #' \item{**none**}{ No Border} #' \item{**thin**}{ thin border} #' \item{**medium**}{ medium border} #' \item{**dashed**}{ dashed border} #' \item{**dotted**}{ dotted border} #' \item{**thick**}{ thick border} #' \item{**double**}{ double line border} #' \item{**hair**}{ Hairline border} #' \item{**mediumDashed**}{ medium weight dashed border} #' \item{**dashDot**}{ dash-dot border} #' \item{**mediumDashDot**}{ medium weight dash-dot border} #' \item{**dashDotDot**}{ dash-dot-dot border} #' \item{**mediumDashDotDot**}{ medium weight dash-dot-dot border} #' \item{**slantDashDot**}{ slanted dash-dot border} #' } #' #' @param bgFill Cell background fill colour. #' A valid colour (belonging to colours()) or a valid hex colour beginning with "#". #' -- **Use for conditional formatting styles only.** #' @param fgFill Cell foreground fill colour. #' A valid colour (belonging to colours()) or a valid hex colour beginning with "#" #' #' @param halign #' Horizontal alignment of cell contents #' \itemize{ #' \item{**left**}{ Left horizontal align cell contents} #' \item{**right**}{ Right horizontal align cell contents} #' \item{**center**}{ Center horizontal align cell contents} #' \item{**justify**}{ Justify horizontal align cell contents} #' } #' #' @param valign A name #' Vertical alignment of cell contents #' \itemize{ #' \item{**top**}{ Top vertical align cell contents} #' \item{**center**}{ Center vertical align cell contents} #' \item{**bottom**}{ Bottom vertical align cell contents} #' } #' #' @param textDecoration #' Text styling. #' \itemize{ #' \item{**bold**}{ Bold cell contents} #' \item{**strikeout**}{ Strikeout cell contents} #' \item{**italic**}{ Italicise cell contents} #' \item{**underline**}{ Underline cell contents} #' \item{**underline2**}{ Double underline cell contents} #' \item{**accounting**}{ Single accounting underline cell contents} #' \item{**accounting2**}{ Double accounting underline cell contents} #' } #' #' @param wrapText Logical. If `TRUE` cell contents will wrap to fit in column. #' @param textRotation Rotation of text in degrees. 255 for vertical text. #' @param indent Horizontal indentation of cell contents. #' @param hidden Whether the formula of the cell contents will be hidden (if worksheet protection is turned on) #' @param locked Whether cell contents are locked (if worksheet protection is turned on) #' @return A style object #' @export #' @examples #' ## See package vignettes for further examples #' #' ## Modify default values of border colour and border line style #' options("openxlsx.borderColour" = "#4F80BD") #' options("openxlsx.borderStyle" = "thin") #' #' ## Size 18 Arial, Bold, left horz. aligned, fill colour #1A33CC, all borders, #' style <- createStyle( #' fontSize = 18, fontName = "Arial", #' textDecoration = "bold", halign = "left", fgFill = "#1A33CC", border = "TopBottomLeftRight" #' ) #' #' ## Red, size 24, Bold, italic, underline, center aligned Font, bottom border #' style <- createStyle( #' fontSize = 24, fontColour = rgb(1, 0, 0), #' textDecoration = c("bold", "italic", "underline"), #' halign = "center", valign = "center", border = "Bottom" #' ) #' #' # borderColour is recycled for each border or all colours can be supplied #' #' # colour is recycled 3 times for "Top", "Bottom" & "Right" sides. #' createStyle(border = "TopBottomRight", borderColour = "red") #' #' # supply all colours #' createStyle(border = "TopBottomLeft", borderColour = c("red", "yellow", "green")) createStyle <- function(fontName = NULL, fontSize = NULL, fontColour = NULL, numFmt = openxlsx_getOp("numFmt", "GENERAL"), border = NULL, borderColour = openxlsx_getOp("borderColour", "black"), borderStyle = openxlsx_getOp("borderStyle", "thin"), bgFill = NULL, fgFill = NULL, halign = NULL, valign = NULL, textDecoration = NULL, wrapText = FALSE, textRotation = NULL, indent = NULL, locked = NULL, hidden = NULL) { ### Error checking op <- get_set_options() on.exit(options(op), add = TRUE) ## if num fmt is made up of dd, mm, yy numFmt_original <- numFmt[[1]] numFmt <- tolower(numFmt_original) validNumFmt <- c("general", "number", "currency", "accounting", "date", "longdate", "time", "percentage", "scientific", "text", "3", "4", "comma") if (numFmt == "date") { numFmt <- openxlsx_getOp("dateFormat", "date") } else if (numFmt == "longdate") { numFmt <- openxlsx_getOp("datetimeFormat", "longdate") } else if (!numFmt %in% validNumFmt) { numFmt <- replaceIllegalCharacters(numFmt_original) } numFmtMapping <- list( list(numFmtId = 0), # GENERAL list(numFmtId = 2), # NUMBER list(numFmtId = 164, formatCode = ""$"#,##0.00"), ## CURRENCY list(numFmtId = 44), # ACCOUNTING list(numFmtId = 14), # DATE list(numFmtId = 166, formatCode = "yyyy/mm/dd hh:mm:ss"), # LONGDATE list(numFmtId = 167), # TIME list(numFmtId = 10), # PERCENTAGE list(numFmtId = 11), # SCIENTIFIC list(numFmtId = 49), # TEXT list(numFmtId = 3), list(numFmtId = 4), list(numFmtId = 3) ) names(numFmtMapping) <- validNumFmt ## Validate border line style if (!is.null(borderStyle)) { borderStyle <- validateBorderStyle(borderStyle) } if (!is.null(halign)) { halign <- tolower(halign[[1]]) if (!halign %in% c("left", "right", "center", "justify")) { stop("Invalid halign argument!") } } if (!is.null(valign)) { valign <- tolower(valign[[1]]) if (!valign %in% c("top", "bottom", "center")) { stop("Invalid valign argument!") } } if (!is.logical(wrapText)) { stop("Invalid wrapText") } if (!is.null(indent)) { if (!is.numeric(indent) & !is.integer(indent)) { stop("indent must be numeric") } } textDecoration <- tolower(textDecoration) if (!is.null(textDecoration)) { if (!all(textDecoration %in% c("bold", "strikeout", "italic", "underline", "underline2", "accounting", "accounting2", ""))) { stop("Invalid textDecoration!") } } borderColour <- validateColour(borderColour, "Invalid border colour!") if (!is.null(fontColour)) { fontColour <- validateColour(fontColour, "Invalid font colour!") } if (!is.null(fontSize)) { if (fontSize < 1) stop("Font size must be greater than 0!") } if (!is.null(locked)) { if (!is.logical(locked)) stop("Cell attribute locked must be TRUE or FALSE") } if (!is.null(hidden)) { if (!is.logical(hidden)) stop("Cell attribute hidden must be TRUE or FALSE") } ######################### error checking complete ############################# style <- Style$new() if (!is.null(fontName)) { style$fontName <- list("val" = fontName) } if (!is.null(fontSize)) { style$fontSize <- list("val" = fontSize) } if (!is.null(fontColour)) { style$fontColour <- list("rgb" = fontColour) } style$fontDecoration <- toupper(textDecoration) ## background fill if (is.null(bgFill)) { # bgFillList <- NULL variable not used } else { bgFill <- validateColour(bgFill, "Invalid bgFill colour") style$fill <- append(style$fill, list(fillBg = list("rgb" = bgFill))) } ## foreground fill if (is.null(fgFill)) { # fgFillList <- NULL variable not used } else { fgFill <- validateColour(fgFill, "Invalid fgFill colour") style$fill <- append(style$fill, list(fillFg = list(rgb = fgFill))) } ## border if (!is.null(border)) { border <- toupper(border) border <- paste(border, collapse = "") ## find position of each side in string sides <- c("LEFT", "RIGHT", "TOP", "BOTTOM") pos <- sapply(sides, function(x) regexpr(x, border)) pos <- pos[order(pos, decreasing = FALSE)] nSides <- sum(pos > 0) borderColour <- rep(borderColour, length.out = nSides) borderStyle <- rep(borderStyle, length.out = nSides) pos <- pos[pos > 0] if (length(pos) == 0) { stop("Unknown border argument") } names(borderColour) <- names(pos) names(borderStyle) <- names(pos) if ("LEFT" %in% names(pos)) { style$borderLeft <- borderStyle[["LEFT"]] style$borderLeftColour <- list("rgb" = borderColour[["LEFT"]]) } if ("RIGHT" %in% names(pos)) { style$borderRight <- borderStyle[["RIGHT"]] style$borderRightColour <- list("rgb" = borderColour[["RIGHT"]]) } if ("TOP" %in% names(pos)) { style$borderTop <- borderStyle[["TOP"]] style$borderTopColour <- list("rgb" = borderColour[["TOP"]]) } if ("BOTTOM" %in% names(pos)) { style$borderBottom <- borderStyle[["BOTTOM"]] style$borderBottomColour <- list("rgb" = borderColour[["BOTTOM"]]) } } ## other fields if (!is.null(halign)) { style$halign <- halign } if (!is.null(valign)) { style$valign <- valign } if (!is.null(indent)) { style$indent <- indent } if (wrapText) { style$wrapText <- TRUE } if (!is.null(textRotation)) { if (!is.numeric(textRotation)) { stop("textRotation must be numeric.") } if (textRotation < 0 & textRotation >= -90) { textRotation <- (textRotation * -1) + 90 } style$textRotation <- round(textRotation[[1]], 0) } if (numFmt != "general") { if (numFmt %in% validNumFmt) { style$numFmt <- numFmtMapping[[numFmt[[1]]]] } else { style$numFmt <- list("numFmtId" = 165, formatCode = numFmt) ## Custom numFmt } } if (!is.null(locked)) { style$locked <- locked } if (!is.null(hidden)) { style$hidden <- hidden } return(style) } #' @name addStyle #' @title Add a style to a set of cells #' @description Function adds a style to a specified set of cells. #' @author Alexander Walker #' @param wb A Workbook object containing a worksheet. #' @param sheet A worksheet to apply the style to. #' @param style A style object returned from createStyle() #' @param rows Rows to apply style to. #' @param cols columns to apply style to. #' @param gridExpand If `TRUE`, style will be applied to all combinations of rows and cols. #' @param stack If `TRUE` the new style is merged with any existing cell styles. If FALSE, any #' existing style is replaced by the new style. #' @seealso [createStyle()] #' @seealso expand.grid #' @export #' @examples #' ## See package vignette for more examples. #' #' ## Create a new workbook #' wb <- createWorkbook("My name here") #' #' ## Add a worksheets #' addWorksheet(wb, "Expenditure", gridLines = FALSE) #' #' ## write data to worksheet 1 #' writeData(wb, sheet = 1, USPersonalExpenditure, rowNames = TRUE) #' #' ## create and add a style to the column headers #' headerStyle <- createStyle( #' fontSize = 14, fontColour = "#FFFFFF", halign = "center", #' fgFill = "#4F81BD", border = "TopBottom", borderColour = "#4F81BD" #' ) #' #' ## style for body #' bodyStyle <- createStyle(border = "TopBottom", borderColour = "#4F81BD") #' addStyle(wb, sheet = 1, bodyStyle, rows = 2:6, cols = 1:6, gridExpand = TRUE) #' setColWidths(wb, 1, cols = 1, widths = 21) ## set column width for row names column #' \dontrun{ #' saveWorkbook(wb, "addStyleExample.xlsx", overwrite = TRUE) #' } addStyle <- function(wb, sheet, style, rows, cols, gridExpand = FALSE, stack = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!is.null(style$numFmt) & length(wb$styleObjects) > 0) { if (style$numFmt$numFmtId == 165) { maxnumFmtId <- max(c(sapply(wb$styleObjects, function(i) { as.integer( max(c(i$style$numFmt$numFmtId, 0)) ) }), 165)) style$numFmt$numFmtId <- maxnumFmtId + 1 } } sheet <- wb$validateSheet(sheet) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (!"Style" %in% class(style)) { stop("style argument must be a Style object.") } if (!is.logical(stack)) { stop("stack parameter must be a logical!") } if (length(cols) == 0 | length(rows) == 0) { return(invisible(0)) } cols <- convertFromExcelRef(cols) rows <- as.integer(rows) ## rows and cols need to be the same length if (gridExpand) { n <- length(cols) cols <- rep.int(cols, times = length(rows)) rows <- rep(rows, each = n) } else if (length(rows) == 1 & length(cols) > 1) { rows <- rep.int(rows, times = length(cols)) } else if (length(cols) == 1 & length(rows) > 1) { cols <- rep.int(cols, times = length(rows)) } else if (length(rows) != length(cols)) { stop("Length of rows and cols must be equal.") } wb$addStyle(sheet = sheet, style = style, rows = rows, cols = cols, stack = stack) } #' @name getCellRefs #' @title Return excel cell coordinates from (x,y) coordinates #' @description Return excel cell coordinates from (x,y) coordinates #' @author Philipp Schauberger, Alexander Walker #' @param cellCoords A data.frame with two columns coordinate pairs. #' @return Excel alphanumeric cell reference #' @examples #' getCellRefs(data.frame(1, 2)) #' # "B1" #' getCellRefs(data.frame(1:3, 2:4)) #' # "B1" "C2" "D3" #' @export getCellRefs <- function(cellCoords) { if (!"data.frame" %in% class(cellCoords)) { stop("Provide a data.frame!") } if (!("numeric" %in% sapply(cellCoords[, 1], class) | "integer" %in% sapply(cellCoords[, 1], class)) & ("numeric" %in% sapply(cellCoords[, 2], class) | "integer" %in% sapply(cellCoords[, 2], class)) ) { stop("Provide a data.frame containing integers!") } op <- get_set_options() on.exit(options(op), add = TRUE) l <- convert_to_excel_ref(cols = unlist(cellCoords[, 2]), LETTERS = LETTERS) paste0(l, cellCoords[, 1]) } #' @name freezePane #' @title Freeze a worksheet pane #' @description Freeze a worksheet pane #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param firstActiveRow Top row of active region #' @param firstActiveCol Furthest left column of active region #' @param firstRow If `TRUE`, freezes the first row (equivalent to firstActiveRow = 2) #' @param firstCol If `TRUE`, freezes the first column (equivalent to firstActiveCol = 2) #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook("Kenshin") #' #' ## Add some worksheets #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' addWorksheet(wb, "Sheet 3") #' addWorksheet(wb, "Sheet 4") #' #' ## Freeze Panes #' freezePane(wb, "Sheet 1", firstActiveRow = 5, firstActiveCol = 3) #' freezePane(wb, "Sheet 2", firstCol = TRUE) ## shortcut to firstActiveCol = 2 #' freezePane(wb, 3, firstRow = TRUE) ## shortcut to firstActiveRow = 2 #' freezePane(wb, 4, firstActiveRow = 1, firstActiveCol = "D") #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "freezePaneExample.xlsx", overwrite = TRUE) #' } freezePane <- function(wb, sheet, firstActiveRow = NULL, firstActiveCol = NULL, firstRow = FALSE, firstCol = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) if (is.null(firstActiveRow) & is.null(firstActiveCol) & !firstRow & !firstCol) { return(invisible(0)) } if (!is.logical(firstRow)) { stop("firstRow must be TRUE/FALSE") } if (!is.logical(firstCol)) { stop("firstCol must be TRUE/FALSE") } if (firstRow & !firstCol) { invisible(wb$freezePanes(sheet, firstRow = firstRow)) } else if (firstCol & !firstRow) { invisible(wb$freezePanes(sheet, firstCol = firstCol)) } else if (firstRow & firstCol) { invisible(wb$freezePanes(sheet, firstActiveRow = 2L, firstActiveCol = 2L)) } else { ## else both firstRow and firstCol are FALSE ## Convert to numeric if column letter given if (!is.null(firstActiveRow)) { firstActiveRow <- convertFromExcelRef(firstActiveRow) } else { firstActiveRow <- 1L } if (!is.null(firstActiveCol)) { firstActiveCol <- convertFromExcelRef(firstActiveCol) } else { firstActiveCol <- 1L } invisible(wb$freezePanes(sheet, firstActiveRow = firstActiveRow, firstActiveCol = firstActiveCol, firstRow = firstRow, firstCol = firstCol)) } } convert2EMU <- function(d, units) { if (grepl("in", units)) { d <- d * 2.54 } if (grepl("mm|milli", units)) { d <- d / 10 } return(d * 360000) } #' @name insertImage #' @title Insert an image into a worksheet #' @description Insert an image into a worksheet #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param file An image file. Valid file types are: jpeg, png, bmp #' @param width Width of figure. #' @param height Height of figure. #' @param startRow Row coordinate of upper left corner of the image #' @param startCol Column coordinate of upper left corner of the image #' @param units Units of width and height. Can be "in", "cm" or "px" #' @param dpi Image resolution used for conversion between units. #' @importFrom grDevices bmp png jpeg #' @seealso [insertPlot()] #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook("Ayanami") #' #' ## Add some worksheets #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' addWorksheet(wb, "Sheet 3") #' #' ## Insert images #' img <- system.file("extdata", "einstein.jpg", package = "openxlsx") #' insertImage(wb, "Sheet 1", img, startRow = 5, startCol = 3, width = 6, height = 5) #' insertImage(wb, 2, img, startRow = 2, startCol = 2) #' insertImage(wb, 3, img, width = 15, height = 12, startRow = 3, startCol = "G", units = "cm") #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "insertImageExample.xlsx", overwrite = TRUE) #' } insertImage <- function(wb, sheet, file, width = 6, height = 3, startRow = 1, startCol = 1, units = "in", dpi = 300) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!file.exists(file)) { stop("File does not exist.") } if (!grepl("\\\\|\\/", file)) { file <- file.path(getwd(), file, fsep = .Platform$file.sep) } units <- tolower(units) if (!units %in% c("cm", "in", "px")) { stop("Invalid units.\nunits must be one of: cm, in, px") } startCol <- convertFromExcelRef(startCol) startRow <- as.integer(startRow) ## convert to inches if (units == "px") { width <- width / dpi height <- height / dpi } else if (units == "cm") { width <- width / 2.54 height <- height / 2.54 } ## Convert to EMUs widthEMU <- as.integer(round(width * 914400L, 0)) # (EMUs per inch) heightEMU <- as.integer(round(height * 914400L, 0)) # (EMUs per inch) wb$insertImage(sheet, file = file, startRow = startRow, startCol = startCol, width = widthEMU, height = heightEMU) } pixels2ExcelColWidth <- function(pixels) { if (any(!is.numeric(pixels))) { stop("All elements of pixels must be numeric") } pixels[pixels == 0] <- 8.43 pixels[pixels != 0] <- (pixels[pixels != 0] - 12) / 7 + 1 pixels } #' @name setRowHeights #' @title Set worksheet row heights #' @description Set worksheet row heights #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param rows Indices of rows to set height #' @param heights Heights to set rows to specified in Excel column height units. #' @seealso [removeRowHeights()] #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook() #' #' ## Add a worksheet #' addWorksheet(wb, "Sheet 1") #' #' ## set row heights #' setRowHeights(wb, 1, rows = c(1, 4, 22, 2, 19), heights = c(24, 28, 32, 42, 33)) #' #' ## overwrite row 1 height #' setRowHeights(wb, 1, rows = 1, heights = 40) #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "setRowHeightsExample.xlsx", overwrite = TRUE) #' } setRowHeights <- function(wb, sheet, rows, heights) { sheet <- wb$validateSheet(sheet) if (length(rows) > length(heights)) { heights <- rep(heights, length.out = length(rows)) } if (length(heights) > length(rows)) { stop("Greater number of height values than rows.") } op <- get_set_options() on.exit(options(op), add = TRUE) ## Remove duplicates heights <- heights[!duplicated(rows)] rows <- rows[!duplicated(rows)] heights <- as.character(as.numeric(heights)) names(heights) <- rows wb$setRowHeights(sheet, rows, heights) } #' @name setColWidths #' @title Set worksheet column widths #' @description Set worksheet column widths to specific width or "auto". #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Indices of cols to set width #' @param widths widths to set cols to specified in Excel column width units or "auto" for automatic sizing. The widths argument is #' recycled to the length of cols. #' @param hidden Logical vector. If TRUE the column is hidden. #' @param ignoreMergedCells Ignore any cells that have been merged with other cells in the calculation of "auto" column widths. #' @details The global min and max column width for "auto" columns is set by (default values show): #' \itemize{ #' \item{options("openxlsx.minWidth" = 3)} #' \item{options("openxlsx.maxWidth" = 250)} ## This is the maximum width allowed in Excel #' } #' #' NOTE: The calculation of column widths can be slow for large worksheets. #' #' NOTE: The `hidden` parameter may conflict with the one set in `groupColumns`; changing one will update the other. #' #' @seealso [removeColWidths()] #' @export #' @examples #' ## Create a new workbook #' wb <- createWorkbook() #' #' ## Add a worksheet #' addWorksheet(wb, "Sheet 1") #' #' #' ## set col widths #' setColWidths(wb, 1, cols = c(1, 4, 6, 7, 9), widths = c(16, 15, 12, 18, 33)) #' #' ## auto columns #' addWorksheet(wb, "Sheet 2") #' writeData(wb, sheet = 2, x = iris) #' setColWidths(wb, sheet = 2, cols = 1:5, widths = "auto") #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "setColWidthsExample.xlsx", overwrite = TRUE) #' } #' setColWidths <- function(wb, sheet, cols, widths = 8.43, hidden = rep(FALSE, length(cols)), ignoreMergedCells = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) sheet <- wb$validateSheet(sheet) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } widths <- tolower(widths) ## possibly "auto" if (ignoreMergedCells) { widths[widths == "auto"] <- "auto2" } # should do nothing if the cols' length is zero if (length(cols) == 0L) { return(invisible(0)) } if (length(widths) > length(cols)) { stop("More widths than columns supplied.") } if (length(hidden) > length(cols)) { stop("hidden argument is longer than cols.") } if (length(widths) < length(cols)) { widths <- rep(widths, length.out = length(cols)) } if (length(hidden) < length(cols)) { hidden <- rep(hidden, length.out = length(cols)) } ## Remove duplicates widths <- widths[!duplicated(cols)] hidden <- hidden[!duplicated(cols)] cols <- cols[!duplicated(cols)] cols <- convertFromExcelRef(cols) if (length(wb$colWidths[[sheet]]) > 0) { existing_cols <- names(wb$colWidths[[sheet]]) existing_widths <- unname(wb$colWidths[[sheet]]) existing_hidden <- attr(wb$colWidths[[sheet]], "hidden") ## check for existing custom widths flag <- existing_cols %in% cols if (any(flag)) { existing_cols <- existing_cols[!flag] existing_widths <- existing_widths[!flag] existing_hidden <- existing_hidden[!flag] } all_names <- c(existing_cols, cols) all_widths <- c(existing_widths, widths) all_hidden <- c(existing_hidden, as.character(as.integer(hidden))) ord <- order(as.integer(all_names)) all_names <- all_names[ord] all_widths <- all_widths[ord] all_hidden <- all_hidden[ord] names(all_widths) <- all_names wb$colWidths[[sheet]] <- all_widths attr(wb$colWidths[[sheet]], "hidden") <- all_hidden } else { names(widths) <- cols wb$colWidths[[sheet]] <- widths attr(wb$colWidths[[sheet]], "hidden") <- as.character(as.integer(hidden)) } # Check if any conflicting column outline levels if (length(wb$colOutlineLevels[[sheet]]) > 0) { existing_cols <- names(wb$colOutlineLevels[[sheet]]) if (any(existing_cols %in% cols)) { for (i in intersect(existing_cols, cols)) { width_hidden <- attr(wb$colWidths[[sheet]], "hidden")[attr(wb$colWidths[[sheet]], "names") == i] outline_hidden <- attr(wb$colOutlineLevels[[sheet]], "hidden")[attr(wb$colOutlineLevels[[sheet]], "names") == i] if (outline_hidden != width_hidden) { attr(wb$colOutlineLevels[[sheet]], "hidden")[attr(wb$colOutlineLevels[[sheet]], "names") == i] <- width_hidden } } cols <- cols[!cols %in% existing_cols] hidden <- attr(wb$colWidths[[sheet]], "hidden")[attr(wb$colWidths[[sheet]], "names") %in% cols] } } invisible(0) } #' @name removeColWidths #' @title Remove column widths from a worksheet #' @description Remove column widths from a worksheet #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Indices of columns to remove custom width (if any) from. #' @seealso [setColWidths()] #' @export #' @examples #' ## Create a new workbook #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' #' ## remove column widths in columns 1 to 20 #' removeColWidths(wb, 1, cols = 1:20) #' \dontrun{ #' saveWorkbook(wb, "removeColWidthsExample.xlsx", overwrite = TRUE) #' } removeColWidths <- function(wb, sheet, cols) { sheet <- wb$validateSheet(sheet) if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } op <- get_set_options() on.exit(options(op), add = TRUE) customCols <- as.integer(names(wb$colWidths[[sheet]])) removeInds <- which(customCols %in% cols) if (length(removeInds) > 0) { remainingCols <- customCols[-removeInds] if (length(remainingCols) == 0) { wb$colWidths[[sheet]] <- list() } else { rem_widths <- wb$colWidths[[sheet]][-removeInds] names(rem_widths) <- as.character(remainingCols) wb$colWidths[[sheet]] <- rem_widths } } } #' @name removeRowHeights #' @title Remove custom row heights from a worksheet #' @description Remove row heights from a worksheet #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param rows Indices of rows to remove custom height (if any) from. #' @seealso [setRowHeights()] #' @export #' @examples #' ## Create a new workbook #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' #' ## remove any custom row heights in rows 1 to 10 #' removeRowHeights(wb, 1, rows = 1:10) #' \dontrun{ #' saveWorkbook(wb, "removeRowHeightsExample.xlsx", overwrite = TRUE) #' } removeRowHeights <- function(wb, sheet, rows) { op <- get_set_options() on.exit(options(op), add = TRUE) sheet <- wb$validateSheet(sheet) customRows <- as.integer(names(wb$rowHeights[[sheet]])) removeInds <- which(customRows %in% rows) if (length(removeInds) > 0) { wb$rowHeights[[sheet]] <- wb$rowHeights[[sheet]][-removeInds] } } #' @name insertPlot #' @title Insert the current plot into a worksheet #' @author Alexander Walker #' @description The current plot is saved to a temporary image file using dev.copy. #' This file is then written to the workbook using insertImage. #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param startRow Row coordinate of upper left corner of figure.` xy[[2]]` when xy is given. #' @param startCol Column coordinate of upper left corner of figure. `xy[[1]]` when xy is given. #' @param xy Alternate way to specify startRow and startCol. A vector of length 2 of form (startcol, startRow) #' @param width Width of figure. Defaults to 6in. #' @param height Height of figure . Defaults to 4in. #' @param fileType File type of image #' @param units Units of width and height. Can be "in", "cm" or "px" #' @param dpi Image resolution #' @seealso [insertImage()] #' @export #' @importFrom grDevices bmp png jpeg tiff dev.copy dev.list dev.off #' @examples #' \dontrun{ #' ## Create a new workbook #' wb <- createWorkbook() #' #' ## Add a worksheet #' addWorksheet(wb, "Sheet 1", gridLines = FALSE) #' #' ## create plot objects #' require(ggplot2) #' p1 <- qplot(mpg, #' data = mtcars, geom = "density", #' fill = as.factor(gear), alpha = I(.5), main = "Distribution of Gas Mileage" #' ) #' p2 <- qplot(age, circumference, #' data = Orange, geom = c("point", "line"), colour = Tree #' ) #' #' ## Insert currently displayed plot to sheet 1, row 1, column 1 #' print(p1) # plot needs to be showing #' insertPlot(wb, 1, width = 5, height = 3.5, fileType = "png", units = "in") #' #' ## Insert plot 2 #' print(p2) #' insertPlot(wb, 1, xy = c("J", 2), width = 16, height = 10, fileType = "png", units = "cm") #' #' ## Save workbook #' saveWorkbook(wb, "insertPlotExample.xlsx", overwrite = TRUE) #' } insertPlot <- function(wb, sheet, width = 6, height = 4, xy = NULL, startRow = 1, startCol = 1, fileType = "png", units = "in", dpi = 300) { op <- get_set_options() on.exit(options(op), add = TRUE) if (is.null(dev.list()[[1]])) { warning("No plot to insert.") return() } if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (!is.null(xy)) { startCol <- xy[[1]] startRow <- xy[[2]] } fileType <- tolower(fileType) units <- tolower(units) if (fileType == "jpg") { fileType <- "jpeg" } if (!fileType %in% c("png", "jpeg", "tiff", "bmp")) { stop("Invalid file type.\nfileType must be one of: png, jpeg, tiff, bmp") } if (!units %in% c("cm", "in", "px")) { stop("Invalid units.\nunits must be one of: cm, in, px") } fileName <- tempfile(pattern = "figureImage", fileext = paste0(".", fileType)) if (fileType == "bmp") { dev.copy(bmp, filename = fileName, width = width, height = height, units = units, res = dpi) } else if (fileType == "jpeg") { dev.copy(jpeg, filename = fileName, width = width, height = height, units = units, quality = 100, res = dpi) } else if (fileType == "png") { dev.copy(png, filename = fileName, width = width, height = height, units = units, res = dpi) } else if (fileType == "tiff") { dev.copy(tiff, filename = fileName, width = width, height = height, units = units, compression = "none", res = dpi) } ## write image invisible(dev.off()) insertImage(wb = wb, sheet = sheet, file = fileName, width = width, height = height, startRow = startRow, startCol = startCol, units = units, dpi = dpi) } #' @name replaceStyle #' @title Replace an existing cell style #' @description Replace an existing cell style #' @author Alexander Walker #' @param wb A workbook object #' @param index Index of style object to replace #' @param newStyle A style to replace the existing style as position index #' @description Replace a style object #' @export #' @seealso [getStyles()] #' @examples #' #' ## load a workbook #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' #' ## create a new style and replace style 2 #' #' newStyle <- createStyle(fgFill = "#00FF00") #' #' ## replace style 2 #' getStyles(wb)[1:3] ## prints styles #' replaceStyle(wb, 2, newStyle = newStyle) #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "replaceStyleExample.xlsx", overwrite = TRUE) #' } replaceStyle <- function(wb, index, newStyle) { nStyles <- length(wb$styleObjects) if (nStyles == 0) { stop("Workbook has no existing styles.") } if (index > nStyles) { stop(sprintf("Invalid index. Workbook only has %s styles.", nStyles)) } if (!all("Style" %in% class(newStyle))) { stop("Invalid style object.") } wb$styleObjects[[index]]$style <- newStyle } #' @name getStyles #' @title Returns a list of all styles in the workbook #' @description Returns list of style objects in the workbook #' @param wb A workbook object #' @export #' @seealso [replaceStyle()] #' @examples #' ## load a workbook #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' getStyles(wb)[1:3] getStyles <- function(wb) { nStyles <- length(wb$styleObjects) if (nStyles == 0) { stop("Workbook has no existing styles.") } styles <- lapply(wb$styleObjects, "[[", "style") return(styles) } #' @name removeWorksheet #' @title Remove a worksheet from a workbook #' @description Remove a worksheet from a Workbook object #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @description Remove a worksheet from a workbook #' @export #' @examples #' ## load a workbook #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' #' ## Remove sheet 2 #' removeWorksheet(wb, 2) #' #' ## save the modified workbook #' \dontrun{ #' saveWorkbook(wb, "removeWorksheetExample.xlsx", overwrite = TRUE) #' } removeWorksheet <- function(wb, sheet) { if (class(wb) != "Workbook") { stop("wb must be a Workbook object!") } if (length(sheet) != 1) { stop("sheet must have length 1.") } wb$deleteWorksheet(sheet) invisible(0) } #' @name deleteData #' @title Delete cell data #' @description Delete contents and styling from a cell. #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param rows Rows to delete data from. #' @param cols columns to delete data from. #' @param gridExpand If `TRUE`, all data in rectangle min(rows):max(rows) X min(cols):max(cols) #' will be removed. #' @export #' @examples #' ## write some data #' wb <- createWorkbook() #' addWorksheet(wb, "Worksheet 1") #' x <- data.frame(matrix(runif(200), ncol = 10)) #' writeData(wb, sheet = 1, x = x, startCol = 2, startRow = 3, colNames = FALSE) #' #' ## delete some data #' deleteData(wb, sheet = 1, cols = 3:5, rows = 5:7, gridExpand = TRUE) #' deleteData(wb, sheet = 1, cols = 7:9, rows = 5:7, gridExpand = TRUE) #' deleteData(wb, sheet = 1, cols = LETTERS, rows = 18, gridExpand = TRUE) #' \dontrun{ #' saveWorkbook(wb, "deleteDataExample.xlsx", overwrite = TRUE) #' } deleteData <- function(wb, sheet, cols, rows, gridExpand = FALSE) { sheet <- wb$validateSheet(sheet) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } wb$worksheets[[sheet]]$sheet_data$delete(rows_in = rows, cols_in = cols, grid_expand = gridExpand) invisible(0) } #' @name modifyBaseFont #' @title Modify the default font #' @description Modify the default font for this workbook #' @author Alexander Walker #' @param wb A workbook object #' @param fontSize font size #' @param fontColour font colour #' @param fontName Name of a font #' @details The font name is not validated in anyway. Excel replaces unknown font names #' with Arial. Base font is black, size 11, Calibri. #' @export #' @examples #' ## create a workbook #' wb <- createWorkbook() #' addWorksheet(wb, "S1") #' ## modify base font to size 10 Arial Narrow in red #' modifyBaseFont(wb, fontSize = 10, fontColour = "#FF0000", fontName = "Arial Narrow") #' #' writeData(wb, "S1", iris) #' writeDataTable(wb, "S1", x = iris, startCol = 10) ## font colour does not affect tables #' \dontrun{ #' saveWorkbook(wb, "modifyBaseFontExample.xlsx", overwrite = TRUE) #' } modifyBaseFont <- function(wb, fontSize = 11, fontColour = "black", fontName = "Calibri") { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } op <- get_set_options() on.exit(options(op), add = TRUE) if (fontSize < 0) stop("Invalid fontSize") fontColour <- validateColour(fontColour) wb$styles$fonts[[1]] <- sprintf('', fontSize, fontColour, fontName) } #' @name getBaseFont #' @title Return the workbook default font #' @description Return the workbook default font #' @author Alexander Walker #' @param wb A workbook object #' @description Returns the base font used in the workbook. #' @export #' @examples #' ## create a workbook #' wb <- createWorkbook() #' getBaseFont(wb) #' #' ## modify base font to size 10 Arial Narrow in red #' modifyBaseFont(wb, fontSize = 10, fontColour = "#FF0000", fontName = "Arial Narrow") #' #' getBaseFont(wb) getBaseFont <- function(wb) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } wb$getBaseFont() } #' @name setHeaderFooter #' @title Set document headers and footers #' @description Set document headers and footers #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param header document header. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position. #' @param footer document footer. Character vector of length 3 corresponding to positions left, center, right. Use NA to skip a position. #' @param evenHeader document header for even pages. #' @param evenFooter document footer for even pages. #' @param firstHeader document header for first page only. #' @param firstFooter document footer for first page only. #' @details Headers and footers can contain special tags #' \itemize{ #' \item{**&\[Page\]**}{ Page number} #' \item{**&\[Pages\]**}{ Number of pages} #' \item{**&\[Date\]**}{ Current date} #' \item{**&\[Time\]**}{ Current time} #' \item{**&\[Path\]**}{ File path} #' \item{**&\[File\]**}{ File name} #' \item{**&\[Tab\]**}{ Worksheet name} #' } #' @export #' @seealso [addWorksheet()] to set headers and footers when adding a worksheet #' @examples #' wb <- createWorkbook() #' #' addWorksheet(wb, "S1") #' addWorksheet(wb, "S2") #' addWorksheet(wb, "S3") #' addWorksheet(wb, "S4") #' #' writeData(wb, 1, 1:400) #' writeData(wb, 2, 1:400) #' writeData(wb, 3, 3:400) #' writeData(wb, 4, 3:400) #' #' setHeaderFooter(wb, #' sheet = "S1", #' header = c("ODD HEAD LEFT", "ODD HEAD CENTER", "ODD HEAD RIGHT"), #' footer = c("ODD FOOT RIGHT", "ODD FOOT CENTER", "ODD FOOT RIGHT"), #' evenHeader = c("EVEN HEAD LEFT", "EVEN HEAD CENTER", "EVEN HEAD RIGHT"), #' evenFooter = c("EVEN FOOT RIGHT", "EVEN FOOT CENTER", "EVEN FOOT RIGHT"), #' firstHeader = c("TOP", "OF FIRST", "PAGE"), #' firstFooter = c("BOTTOM", "OF FIRST", "PAGE") #' ) #' #' setHeaderFooter(wb, #' sheet = 2, #' header = c("&[Date]", "ALL HEAD CENTER 2", "&[Page] / &[Pages]"), #' footer = c("&[Path]&[File]", NA, "&[Tab]"), #' firstHeader = c(NA, "Center Header of First Page", NA), #' firstFooter = c(NA, "Center Footer of First Page", NA) #' ) #' #' setHeaderFooter(wb, #' sheet = 3, #' header = c("ALL HEAD LEFT 2", "ALL HEAD CENTER 2", "ALL HEAD RIGHT 2"), #' footer = c("ALL FOOT RIGHT 2", "ALL FOOT CENTER 2", "ALL FOOT RIGHT 2") #' ) #' #' setHeaderFooter(wb, #' sheet = 4, #' firstHeader = c("FIRST ONLY L", NA, "FIRST ONLY R"), #' firstFooter = c("FIRST ONLY L", NA, "FIRST ONLY R") #' ) #' \dontrun{ #' saveWorkbook(wb, "setHeaderFooterExample.xlsx", overwrite = TRUE) #' } setHeaderFooter <- function(wb, sheet, header = NULL, footer = NULL, evenHeader = NULL, evenFooter = NULL, firstHeader = NULL, firstFooter = NULL) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) if (!is.null(header) & length(header) != 3) { stop("header must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(footer) & length(footer) != 3) { stop("footer must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(evenHeader) & length(evenHeader) != 3) { stop("evenHeader must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(evenFooter) & length(evenFooter) != 3) { stop("evenFooter must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(firstHeader) & length(firstHeader) != 3) { stop("firstHeader must have length 3 where elements correspond to positions: left, center, right.") } if (!is.null(firstFooter) & length(firstFooter) != 3) { stop("firstFooter must have length 3 where elements correspond to positions: left, center, right.") } op <- get_set_options() on.exit(options(op), add = TRUE) oddHeader <- headerFooterSub(header) oddFooter <- headerFooterSub(footer) evenHeader <- headerFooterSub(evenHeader) evenFooter <- headerFooterSub(evenFooter) firstHeader <- headerFooterSub(firstHeader) firstFooter <- headerFooterSub(firstFooter) naToNULLList <- function(x) { lapply(x, function(x) { if (is.na(x)) { return(NULL) } x }) } hf <- list( oddHeader = naToNULLList(oddHeader), oddFooter = naToNULLList(oddFooter), evenHeader = naToNULLList(evenHeader), evenFooter = naToNULLList(evenFooter), firstHeader = naToNULLList(firstHeader), firstFooter = naToNULLList(firstFooter) ) if (all(sapply(hf, length) == 0)) { hf <- NULL } wb$worksheets[[sheet]]$headerFooter <- hf } #' @name pageSetup #' @title Set page margins, orientation and print scaling #' @description Set page margins, orientation and print scaling #' @author Alexander Walker, Joshua Sturm #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param orientation Page orientation. One of "portrait" or "landscape" #' @param scale Print scaling. Numeric value between 10 and 400 #' @param left left page margin in inches #' @param right right page margin in inches #' @param top top page margin in inches #' @param bottom bottom page margin in inches #' @param header header margin in inches #' @param footer footer margin in inches #' @param fitToWidth If `TRUE`, worksheet is scaled to fit to page width on printing. #' @param fitToHeight If `TRUE`, worksheet is scaled to fit to page height on printing. #' @param paperSize See details. Default value is 9 (A4 paper). #' @param printTitleRows Rows to repeat at top of page when printing. Integer vector. #' @param printTitleCols Columns to repeat at left when printing. Integer vector. #' @param summaryRow Location of summary rows in groupings. One of "Above" or "Below". #' @param summaryCol Location of summary columns in groupings. One of "Right" or "Left". #' @export #' @details #' paperSize is an integer corresponding to: #' \itemize{ #' \item{**1**}{ Letter paper (8.5 in. by 11 in.)} #' \item{**2**}{ Letter small paper (8.5 in. by 11 in.)} #' \item{**3**}{ Tabloid paper (11 in. by 17 in.)} #' \item{**4**}{ Ledger paper (17 in. by 11 in.)} #' \item{**5**}{ Legal paper (8.5 in. by 14 in.)} #' \item{**6**}{ Statement paper (5.5 in. by 8.5 in.)} #' \item{**7**}{ Executive paper (7.25 in. by 10.5 in.)} #' \item{**8**}{ A3 paper (297 mm by 420 mm)} #' \item{**9**}{ A4 paper (210 mm by 297 mm)} #' \item{**10**}{ A4 small paper (210 mm by 297 mm)} #' \item{**11**}{ A5 paper (148 mm by 210 mm)} #' \item{**12**}{ B4 paper (250 mm by 353 mm)} #' \item{**13**}{ B5 paper (176 mm by 250 mm)} #' \item{**14**}{ Folio paper (8.5 in. by 13 in.)} #' \item{**15**}{ Quarto paper (215 mm by 275 mm)} #' \item{**16**}{ Standard paper (10 in. by 14 in.)} #' \item{**17**}{ Standard paper (11 in. by 17 in.)} #' \item{**18**}{ Note paper (8.5 in. by 11 in.)} #' \item{**19**}{ #9 envelope (3.875 in. by 8.875 in.)} #' \item{**20**}{ #10 envelope (4.125 in. by 9.5 in.)} #' \item{**21**}{ #11 envelope (4.5 in. by 10.375 in.)} #' \item{**22**}{ #12 envelope (4.75 in. by 11 in.)} #' \item{**23**}{ #14 envelope (5 in. by 11.5 in.)} #' \item{**24**}{ C paper (17 in. by 22 in.)} #' \item{**25**}{ D paper (22 in. by 34 in.)} #' \item{**26**}{ E paper (34 in. by 44 in.)} #' \item{**27**}{ DL envelope (110 mm by 220 mm)} #' \item{**28**}{ C5 envelope (162 mm by 229 mm)} #' \item{**29**}{ C3 envelope (324 mm by 458 mm)} #' \item{**30**}{ C4 envelope (229 mm by 324 mm)} #' \item{**31**}{ C6 envelope (114 mm by 162 mm)} #' \item{**32**}{ C65 envelope (114 mm by 229 mm)} #' \item{**33**}{ B4 envelope (250 mm by 353 mm)} #' \item{**34**}{ B5 envelope (176 mm by 250 mm)} #' \item{**35**}{ B6 envelope (176 mm by 125 mm)} #' \item{**36**}{ Italy envelope (110 mm by 230 mm)} #' \item{**37**}{ Monarch envelope (3.875 in. by 7.5 in.).} #' \item{**38**}{ 6 3/4 envelope (3.625 in. by 6.5 in.)} #' \item{**39**}{ US standard fanfold (14.875 in. by 11 in.)} #' \item{**40**}{ German standard fanfold (8.5 in. by 12 in.)} #' \item{**41**}{ German legal fanfold (8.5 in. by 13 in.)} #' \item{**42**}{ ISO B4 (250 mm by 353 mm)} #' \item{**43**}{ Japanese double postcard (200 mm by 148 mm)} #' \item{**44**}{ Standard paper (9 in. by 11 in.)} #' \item{**45**}{ Standard paper (10 in. by 11 in.)} #' \item{**46**}{ Standard paper (15 in. by 11 in.)} #' \item{**47**}{ Invite envelope (220 mm by 220 mm)} #' \item{**50**}{ Letter extra paper (9.275 in. by 12 in.)} #' \item{**51**}{ Legal extra paper (9.275 in. by 15 in.)} #' \item{**52**}{ Tabloid extra paper (11.69 in. by 18 in.)} #' \item{**53**}{ A4 extra paper (236 mm by 322 mm)} #' \item{**54**}{ Letter transverse paper (8.275 in. by 11 in.)} #' \item{**55**}{ A4 transverse paper (210 mm by 297 mm)} #' \item{**56**}{ Letter extra transverse paper (9.275 in. by 12 in.)} #' \item{**57**}{ SuperA/SuperA/A4 paper (227 mm by 356 mm)} #' \item{**58**}{ SuperB/SuperB/A3 paper (305 mm by 487 mm)} #' \item{**59**}{ Letter plus paper (8.5 in. by 12.69 in.)} #' \item{**60**}{ A4 plus paper (210 mm by 330 mm)} #' \item{**61**}{ A5 transverse paper (148 mm by 210 mm)} #' \item{**62**}{ JIS B5 transverse paper (182 mm by 257 mm)} #' \item{**63**}{ A3 extra paper (322 mm by 445 mm)} #' \item{**64**}{ A5 extra paper (174 mm by 235 mm)} #' \item{**65**}{ ISO B5 extra paper (201 mm by 276 mm)} #' \item{**66**}{ A2 paper (420 mm by 594 mm)} #' \item{**67**}{ A3 transverse paper (297 mm by 420 mm)} #' \item{**68**}{ A3 extra transverse paper (322 mm by 445 mm)} #' } #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "S1") #' addWorksheet(wb, "S2") #' writeDataTable(wb, 1, x = iris[1:30, ]) #' writeDataTable(wb, 2, x = iris[1:30, ], xy = c("C", 5)) #' #' ## landscape page scaled to 50% #' pageSetup(wb, sheet = 1, orientation = "landscape", scale = 50) #' #' ## portrait page scales to 300% with 0.5in left and right margins #' pageSetup(wb, sheet = 2, orientation = "portrait", scale = 300, left = 0.5, right = 0.5) #' #' #' ## print titles #' addWorksheet(wb, "print_title_rows") #' addWorksheet(wb, "print_title_cols") #' #' writeData(wb, "print_title_rows", rbind(iris, iris, iris, iris)) #' writeData(wb, "print_title_cols", x = rbind(mtcars, mtcars, mtcars), rowNames = TRUE) #' #' pageSetup(wb, sheet = "print_title_rows", printTitleRows = 1) ## first row #' pageSetup(wb, sheet = "print_title_cols", printTitleCols = 1, printTitleRows = 1) #' \dontrun{ #' saveWorkbook(wb, "pageSetupExample.xlsx", overwrite = TRUE) #' } pageSetup <- function(wb, sheet, orientation = NULL, scale = 100, left = 0.7, right = 0.7, top = 0.75, bottom = 0.75, header = 0.3, footer = 0.3, fitToWidth = FALSE, fitToHeight = FALSE, paperSize = NULL, printTitleRows = NULL, printTitleCols = NULL, summaryRow = NULL, summaryCol = NULL) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) xml <- wb$worksheets[[sheet]]$pageSetup if (!is.null(orientation)) { orientation <- tolower(orientation) if (!orientation %in% c("portrait", "landscape")) stop("Invalid page orientation.") } else { orientation <- ifelse(grepl("landscape", xml), "landscape", "portrait") ## get existing } if (scale < 10 | scale > 400) { stop("Scale must be between 10 and 400.") } if (!is.null(paperSize)) { paperSizes <- 1:68 paperSizes <- paperSizes[!paperSizes %in% 48:49] if (!paperSize %in% paperSizes) { stop("paperSize must be an integer in range [1, 68]. See ?pageSetup details.") } paperSize <- as.integer(paperSize) } else { paperSize <- regmatches(xml, regexpr('(?<=paperSize=")[0-9]+', xml, perl = TRUE)) ## get existing } ############################## ## Keep defaults on orientation, hdpi, vdpi, paperSize hdpi <- regmatches(xml, regexpr('(?<=horizontalDpi=")[0-9]+', xml, perl = TRUE)) vdpi <- regmatches(xml, regexpr('(?<=verticalDpi=")[0-9]+', xml, perl = TRUE)) ############################## ## Update wb$worksheets[[sheet]]$pageSetup <- sprintf( '', paperSize, orientation, scale, as.integer(fitToWidth), as.integer(fitToHeight), hdpi, vdpi ) if (fitToHeight | fitToWidth) { wb$worksheets[[sheet]]$sheetPr <- unique(c(wb$worksheets[[sheet]]$sheetPr, '')) } wb$worksheets[[sheet]]$pageMargins <- sprintf('', left, right, top, bottom, header, footer) validRow <- function(summaryRow) { return(tolower(summaryRow) %in% c("above", "below")) } validCol <- function(summaryCol) { return(tolower(summaryCol) %in% c("left", "right")) } outlinepr <- "" if (!is.null(summaryRow)) { if (!validRow(summaryRow)) { stop("Invalid \`summaryRow\` option. Must be one of \"Above\" or \"Below\".") } else if (tolower(summaryRow) == "above") { outlinepr <- ' summaryBelow=\"0\"' } else { outlinepr <- ' summaryBelow=\"1\"' } } if (!is.null(summaryCol)) { if (!validCol(summaryCol)) { stop("Invalid \`summaryCol\` option. Must be one of \"Left\" or \"Right\".") } else if (tolower(summaryCol) == "left") { outlinepr <- paste0(outlinepr, ' summaryRight=\"0\"') } else { outlinepr <- paste0(outlinepr, ' summaryRight=\"1\"') } } if (!stri_isempty(outlinepr)) { wb$worksheets[[sheet]]$sheetPr <- unique(c(wb$worksheets[[sheet]]$sheetPr, paste0(""))) } ## print Titles if (!is.null(printTitleRows) & is.null(printTitleCols)) { if (!is.numeric(printTitleRows)) { stop("printTitleRows must be numeric.") } wb$createNamedRegion( ref1 = paste0("$", min(printTitleRows)), ref2 = paste0("$", max(printTitleRows)), name = "_xlnm.Print_Titles", sheet = names(wb)[[sheet]], localSheetId = sheet - 1L ) } else if (!is.null(printTitleCols) & is.null(printTitleRows)) { if (!is.numeric(printTitleCols)) { stop("printTitleCols must be numeric.") } cols <- convert_to_excel_ref(cols = range(printTitleCols), LETTERS = LETTERS) wb$createNamedRegion( ref1 = paste0("$", cols[1]), ref2 = paste0("$", cols[2]), name = "_xlnm.Print_Titles", sheet = names(wb)[[sheet]], localSheetId = sheet - 1L ) } else if (!is.null(printTitleCols) & !is.null(printTitleRows)) { if (!is.numeric(printTitleRows)) { stop("printTitleRows must be numeric.") } if (!is.numeric(printTitleCols)) { stop("printTitleCols must be numeric.") } cols <- convert_to_excel_ref(cols = range(printTitleCols), LETTERS = LETTERS) rows <- range(printTitleRows) cols <- paste(paste0("$", cols[1]), paste0("$", cols[2]), sep = ":") rows <- paste(paste0("$", rows[1]), paste0("$", rows[2]), sep = ":") localSheetId <- sheet - 1L sheet <- names(wb)[[sheet]] wb$workbook$definedNames <- c( wb$workbook$definedNames, sprintf('\'%s\'!%s,\'%s\'!%s', localSheetId, sheet, cols, sheet, rows) ) } } #' @name protectWorksheet #' @title Protect a worksheet from modifications #' @description Protect or unprotect a worksheet from modifications by the user in the graphical user interface. Replaces an existing protection. #' @author Reinhold Kainhofer #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param protect Whether to protect or unprotect the sheet (default=TRUE) #' @param password (optional) password required to unprotect the worksheet #' @param lockSelectingLockedCells Whether selecting locked cells is locked #' @param lockSelectingUnlockedCells Whether selecting unlocked cells is locked #' @param lockFormattingCells Whether formatting cells is locked #' @param lockFormattingColumns Whether formatting columns is locked #' @param lockFormattingRows Whether formatting rows is locked #' @param lockInsertingColumns Whether inserting columns is locked #' @param lockInsertingRows Whether inserting rows is locked #' @param lockInsertingHyperlinks Whether inserting hyperlinks is locked #' @param lockDeletingColumns Whether deleting columns is locked #' @param lockDeletingRows Whether deleting rows is locked #' @param lockSorting Whether sorting is locked #' @param lockAutoFilter Whether auto-filter is locked #' @param lockPivotTables Whether pivot tables are locked #' @param lockObjects Whether objects are locked #' @param lockScenarios Whether scenarios are locked #' @export #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "S1") #' writeDataTable(wb, 1, x = iris[1:30, ]) #' # Formatting cells / columns is allowed , but inserting / deleting columns is protected: #' protectWorksheet(wb, "S1", #' protect = TRUE, #' lockFormattingCells = FALSE, lockFormattingColumns = FALSE, #' lockInsertingColumns = TRUE, lockDeletingColumns = TRUE #' ) #' #' # Remove the protection #' protectWorksheet(wb, "S1", protect = FALSE) #' \dontrun{ #' saveWorkbook(wb, "pageSetupExample.xlsx", overwrite = TRUE) #' } protectWorksheet <- function(wb, sheet, protect = TRUE, password = NULL, lockSelectingLockedCells = NULL, lockSelectingUnlockedCells = NULL, lockFormattingCells = NULL, lockFormattingColumns = NULL, lockFormattingRows = NULL, lockInsertingColumns = NULL, lockInsertingRows = NULL, lockInsertingHyperlinks = NULL, lockDeletingColumns = NULL, lockDeletingRows = NULL, lockSorting = NULL, lockAutoFilter = NULL, lockPivotTables = NULL, lockObjects = NULL, lockScenarios = NULL) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) # xml <- wb$worksheets[[sheet]]$sheetProtection variable not used props <- c() if (!missing(password) && !is.null(password)) { props["password"] <- hashPassword(password) } if (!missing(lockSelectingLockedCells) && !is.null(lockSelectingLockedCells)) { props["selectLockedCells"] <- toString(as.numeric(lockSelectingLockedCells)) } if (!missing(lockSelectingUnlockedCells) && !is.null(lockSelectingUnlockedCells)) { props["selectUnlockedCells"] <- toString(as.numeric(lockSelectingUnlockedCells)) } if (!missing(lockFormattingCells) && !is.null(lockFormattingCells)) { props["formatCells"] <- toString(as.numeric(lockFormattingCells)) } if (!missing(lockFormattingColumns) && !is.null(lockFormattingColumns)) { props["formatColumns"] <- toString(as.numeric(lockFormattingColumns)) } if (!missing(lockFormattingRows) && !is.null(lockFormattingRows)) { props["formatRows"] <- toString(as.numeric(lockFormattingRows)) } if (!missing(lockInsertingColumns) && !is.null(lockInsertingColumns)) { props["insertColumns"] <- toString(as.numeric(lockInsertingColumns)) } if (!missing(lockInsertingRows) && !is.null(lockInsertingRows)) { props["insertRows"] <- toString(as.numeric(lockInsertingRows)) } if (!missing(lockInsertingHyperlinks) && !is.null(lockInsertingHyperlinks)) { props["insertHyperlinks"] <- toString(as.numeric(lockInsertingHyperlinks)) } if (!missing(lockDeletingColumns) && !is.null(lockDeletingColumns)) { props["deleteColumns"] <- toString(as.numeric(lockDeletingColumns)) } if (!missing(lockDeletingRows) && !is.null(lockDeletingRows)) { props["deleteRows"] <- toString(as.numeric(lockDeletingRows)) } if (!missing(lockSorting) && !is.null(lockSorting)) { props["sort"] <- toString(as.numeric(lockSorting)) } if (!missing(lockAutoFilter) && !is.null(lockAutoFilter)) { props["autoFilter"] <- toString(as.numeric(lockAutoFilter)) } if (!missing(lockPivotTables) && !is.null(lockPivotTables)) { props["pivotTables"] <- toString(as.numeric(lockPivotTables)) } if (!missing(lockObjects) && !is.null(lockObjects)) { props["objects"] <- toString(as.numeric(lockObjects)) } if (!missing(lockScenarios) && !is.null(lockScenarios)) { props["scenarios"] <- toString(as.numeric(lockScenarios)) } if (protect) { props["sheet"] <- "1" wb$worksheets[[sheet]]$sheetProtection <- sprintf("", paste(names(props), '="', props, '"', collapse = " ", sep = "")) } else { wb$worksheets[[sheet]]$sheetProtection <- "" } } #' @name protectWorkbook #' @title Protect a workbook from modifications #' @description Protect or unprotect a workbook from modifications by the user in the graphical user interface. Replaces an existing protection. #' @author Reinhold Kainhofer #' @param wb A workbook object #' @param protect Whether to protect or unprotect the sheet (default=TRUE) #' @param password (optional) password required to unprotect the workbook #' @param lockStructure Whether the workbook structure should be locked #' @param lockWindows Whether the window position of the spreadsheet should be locked #' @param type Lock type, default 1. From the xml documentation: 1 - Document is password protected. 2 - Document is recommended to be opened as read-only. 4 - Document is enforced to be opened as read-only. 8 - Document is locked for annotation. #' @export #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "S1") #' protectWorkbook(wb, protect = TRUE, password = "Password", lockStructure = TRUE) #' \dontrun{ #' saveWorkbook(wb, "WorkBook_Protection.xlsx", overwrite = TRUE) #' } #' # Remove the protection #' protectWorkbook(wb, protect = FALSE) #' \dontrun{ #' saveWorkbook(wb, "WorkBook_Protection_unprotected.xlsx", overwrite = TRUE) #' } protectWorkbook <- function(wb, protect = TRUE, password = NULL, lockStructure = FALSE, lockWindows = FALSE, type = 1L) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } invisible(wb$protectWorkbook(protect = protect, password = password, lockStructure = lockStructure, lockWindows = lockWindows, type = type)) } #' @name showGridLines #' @title Set worksheet gridlines to show or hide. #' @description Set worksheet gridlines to show or hide. #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param showGridLines A logical. If `FALSE`, grid lines are hidden. #' @export #' @examples #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' names(wb) ## list worksheets in workbook #' showGridLines(wb, 1, showGridLines = FALSE) #' showGridLines(wb, "testing", showGridLines = FALSE) #' \dontrun{ #' saveWorkbook(wb, "showGridLinesExample.xlsx", overwrite = TRUE) #' } showGridLines <- function(wb, sheet, showGridLines = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) if (!is.logical(showGridLines)) stop("showGridLines must be a logical") sv <- wb$worksheets[[sheet]]$sheetViews showGridLines <- as.integer(showGridLines) ## If attribute exists gsub if (grepl("showGridLines", sv)) { sv <- gsub('showGridLines=".?[^"]', sprintf('showGridLines="%s', showGridLines), sv, perl = TRUE) } else { sv <- gsub(" length(wb$worksheets))) { stop("Elements of order are greater than the number of worksheets") } old_ActiveSheet <- wb$ActiveSheet wb$sheetOrder <- value wb$setactiveSheet(old_ActiveSheet) invisible(wb) } #' @name convertToDate #' @title Convert from excel date number to R Date type #' @description Convert from excel date number to R Date type #' @param x A vector of integers #' @param origin date. Default value is for Windows Excel 2010 #' @param ... additional parameters passed to as.Date() #' @details Excel stores dates as number of days from some origin day #' @seealso [writeData()] #' @export #' @examples #' ## 2014 April 21st to 25th #' convertToDate(c(41750, 41751, 41752, 41753, 41754, NA)) #' convertToDate(c(41750.2, 41751.99, NA, 41753)) convertToDate <- function(x, origin = "1900-01-01", ...) { x <- as.numeric(x) notNa <- !is.na(x) earlyDate <- x < 60 if (origin == "1900-01-01") { x[notNa] <- x[notNa] - 2 x[earlyDate & notNa] <- x[earlyDate & notNa] + 1 } return(as.Date(x, origin = origin, ...)) } #' @name convertToDateTime #' @title Convert from excel time number to R POSIXct type. #' @description Convert from excel time number to R POSIXct type. #' @param x A numeric vector #' @param origin date. Default value is for Windows Excel 2010 #' @param ... Additional parameters passed to as.POSIXct #' @details Excel stores dates as number of days from some origin date #' @export #' @examples #' ## 2014-07-01, 2014-06-30, 2014-06-29 #' x <- c(41821.8127314815, 41820.8127314815, NA, 41819, NaN) #' convertToDateTime(x) #' convertToDateTime(x, tz = "Australia/Perth") #' convertToDateTime(x, tz = "UTC") convertToDateTime <- function(x, origin = "1900-01-01", ...) { op <- get_set_options() on.exit(options(op), add = TRUE) x <- as.numeric(x) date <- convertToDate(x, origin) x <- x * 86400 rem <- x %% 86400 hours <- as.integer(floor(rem / 3600)) minutes_fraction <- rem %% 3600 minutes_whole <- as.integer(floor(minutes_fraction / 60)) secs <- minutes_fraction %% 60 y <- sprintf("%02d:%02d:%06.3f", hours, minutes_whole, secs) notNA <- !is.na(x) date_time <- rep(NA, length(x)) date_time[notNA] <- as.POSIXct(paste(date[notNA], y[notNA]), ...) date_time <- .POSIXct(date_time) return(date_time) } #' @name names #' @title get or set worksheet names #' @description get or set worksheet names #' @aliases names.Workbook #' @export #' @method names Workbook #' @param x A `Workbook` object #' @examples #' #' wb <- createWorkbook() #' addWorksheet(wb, "S1") #' addWorksheet(wb, "S2") #' addWorksheet(wb, "S3") #' #' names(wb) #' names(wb)[[2]] <- "S2a" #' names(wb) #' names(wb) <- paste("Sheet", 1:3) names.Workbook <- function(x) { nms <- x$sheet_names nms <- replaceXMLEntities(nms) } #' @rdname names #' @param value a character vector the same length as wb #' @export `names<-.Workbook` <- function(x, value) { op <- get_set_options() on.exit(options(op), add = TRUE) if (any(duplicated(tolower(value)))) { stop("Worksheet names must be unique.") } existing_sheets <- x$sheet_names inds <- which(value != existing_sheets) if (length(inds) == 0) { return(invisible(x)) } if (length(value) != length(x$worksheets)) { stop(sprintf("names vector must have length equal to number of worksheets in Workbook [%s]", length(existing_sheets))) } if (any(nchar(value) > 31)) { warning("Worksheet names must less than 32 characters. Truncating names...") value[nchar(value) > 31] <- sapply(value[nchar(value) > 31], substr, start = 1, stop = 31) } for (i in inds) { invisible(x$setSheetName(i, value[[i]])) } invisible(x) } #' @name createNamedRegion #' @title Create / delete a named region. #' @description Create / delete a named region #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param rows Numeric vector specifying rows to include in region #' @param cols Numeric vector specifying columns to include in region #' @param name Name for region. A character vector of length 1. Note region names musts be case-insensitive unique. #' @param overwrite Boolean. Overwrite if exists ? Default to FALSE #' #' @details Region is given by: min(cols):max(cols) X min(rows):max(rows) #' @export #' @seealso [getNamedRegions()] #' @examples #' ## create named regions #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' #' ## specify region #' writeData(wb, sheet = 1, x = iris, startCol = 1, startRow = 1) #' createNamedRegion( #' wb = wb, #' sheet = 1, #' name = "iris", #' rows = 1:(nrow(iris) + 1), #' cols = 1:ncol(iris) #' ) #' #' #' ## using writeData 'name' argument #' writeData(wb, sheet = 1, x = iris, name = "iris2", startCol = 10) #' #' out_file <- tempfile(fileext = ".xlsx") #' \dontrun{ #' saveWorkbook(wb, out_file, overwrite = TRUE) #' #' ## see named regions #' getNamedRegions(wb) ## From Workbook object #' getNamedRegions(out_file) ## From xlsx file #' #' ## delete one #' deleteNamedRegion(wb = wb, name = "iris2") #' getNamedRegions(wb) #' #' ## read named regions #' df <- read.xlsx(wb, namedRegion = "iris") #' head(df) #' #' df <- read.xlsx(out_file, namedRegion = "iris2") #' head(df) #' } #' #' @rdname NamedRegion createNamedRegion <- function(wb, sheet, cols, rows, name, overwrite = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) sheet <- wb$validateSheet(sheet) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (!is.numeric(rows)) { stop("rows argument must be a numeric/integer vector") } if (!is.numeric(cols)) { stop("cols argument must be a numeric/integer vector") } ## check name doesn't already exist ## named region ex_names <- regmatches(wb$workbook$definedNames, regexpr('(?<=name=")[^"]+', wb$workbook$definedNames, perl = TRUE)) ex_names <- tolower(replaceXMLEntities(ex_names)) if (tolower(name) %in% ex_names & !overwrite) { stop(sprintf("Named region with name '%s' already exists! Use overwrite = TRUE if you want to replace it", name)) } else if (tolower(name) %in% ex_names & overwrite) { wb$workbook$definedNames <- wb$workbook$definedNames[!ex_names %in% tolower(name)] } if (grepl("^[A-Z]{1,3}[0-9]+$", name)) { stop("name cannot look like a cell reference.") } cols <- round(cols) rows <- round(rows) startCol <- min(cols) endCol <- max(cols) startRow <- min(rows) endRow <- max(rows) ref1 <- paste0("$", convert_to_excel_ref(cols = startCol, LETTERS = LETTERS), "$", startRow) ref2 <- paste0("$", convert_to_excel_ref(cols = endCol, LETTERS = LETTERS), "$", endRow) invisible( wb$createNamedRegion(ref1 = ref1, ref2 = ref2, name = name, sheet = wb$sheet_names[sheet]) ) } #' @export #' @rdname NamedRegion deleteNamedRegion <- function(wb, name) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } ex_names <- regmatches(wb$workbook$definedNames, regexpr('(?<=name=")[^"]+', wb$workbook$definedNames, perl = TRUE)) ex_names <- tolower(replaceXMLEntities(ex_names)) if (tolower(name) %in% ex_names) { wb$workbook$definedNames <- wb$workbook$definedNames[!ex_names %in% tolower(name)] } else { warning(sprintf("Cannot find Named region with name '%s'", name)) } invisible(0) } #' @name getNamedRegions #' @title Get named regions #' @description Return a vector of named regions in a xlsx file or #' Workbook object #' @param x An xlsx file or Workbook object #' @export #' @seealso [createNamedRegion()] #' @examples #' ## create named regions #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' #' ## specify region #' writeData(wb, sheet = 1, x = iris, startCol = 1, startRow = 1) #' createNamedRegion( #' wb = wb, #' sheet = 1, #' name = "iris", #' rows = 1:(nrow(iris) + 1), #' cols = 1:ncol(iris) #' ) #' #' #' ## using writeData 'name' argument to create a named region #' writeData(wb, sheet = 1, x = iris, name = "iris2", startCol = 10) #' \dontrun{ #' out_file <- tempfile(fileext = ".xlsx") #' saveWorkbook(wb, out_file, overwrite = TRUE) #' #' ## see named regions #' getNamedRegions(wb) ## From Workbook object #' getNamedRegions(out_file) ## From xlsx file #' #' ## read named regions #' df <- read.xlsx(wb, namedRegion = "iris") #' head(df) #' #' df <- read.xlsx(out_file, namedRegion = "iris2") #' head(df) #' } #' getNamedRegions <- function(x) { UseMethod("getNamedRegions", x) } #' @export getNamedRegions.default <- function(x) { if (!file.exists(x)) { stop(sprintf("File '%s' does not exist.", x)) } xmlDir <- tempfile() xmlFiles <- unzip(x, exdir = xmlDir) workbook <- grep("workbook.xml$", xmlFiles, perl = TRUE, value = TRUE) workbook <- unlist(readUTF8(workbook)) dn <- getChildlessNode(xml = removeHeadTag(workbook), tag = "definedName") if (length(dn) == 0) { return(NULL) } dn_names <- get_named_regions_from_string(dn = dn) unlink(xmlDir, recursive = TRUE, force = TRUE) return(dn_names) } #' @export getNamedRegions.Workbook <- function(x) { dn <- x$workbook$definedNames if (length(dn) == 0) { return(NULL) } dn_names <- get_named_regions_from_string(dn = dn) return(dn_names) } #' @name addFilter #' @title Add column filters #' @description Add excel column filters to a worksheet #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols columns to add filter to. #' @param rows A row number. #' @seealso [writeData()] #' @details adds filters to worksheet columns, same as filter parameters in writeData. #' writeDataTable automatically adds filters to first row of a table. #' NOTE Can only have a single filter per worksheet unless using tables. #' @export #' @seealso [addFilter()] #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' addWorksheet(wb, "Sheet 3") #' #' writeData(wb, 1, iris) #' addFilter(wb, 1, row = 1, cols = 1:ncol(iris)) #' #' ## Equivalently #' writeData(wb, 2, x = iris, withFilter = TRUE) #' #' ## Similarly #' writeDataTable(wb, 3, iris) #' \dontrun{ #' saveWorkbook(wb, file = "addFilterExample.xlsx", overwrite = TRUE) #' } addFilter <- function(wb, sheet, rows, cols) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) if (length(rows) != 1) { stop("row must be a numeric of length 1.") } if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } wb$worksheets[[sheet]]$autoFilter <- sprintf('', paste(getCellRefs(data.frame("x" = c(rows, rows), "y" = c(min(cols), max(cols)))), collapse = ":")) invisible(wb) } #' @name removeFilter #' @title Remove a worksheet filter #' @description Removes filters from addFilter() and writeData() #' @param wb A workbook object #' @param sheet A vector of names or indices of worksheets #' @export #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' addWorksheet(wb, "Sheet 3") #' #' writeData(wb, 1, iris) #' addFilter(wb, 1, row = 1, cols = 1:ncol(iris)) #' #' ## Equivalently #' writeData(wb, 2, x = iris, withFilter = TRUE) #' #' ## Similarly #' writeDataTable(wb, 3, iris) #' #' ## remove filters #' removeFilter(wb, 1:2) ## remove filters #' removeFilter(wb, 3) ## Does not affect tables! #' \dontrun{ #' saveWorkbook(wb, file = "removeFilterExample.xlsx", overwrite = TRUE) #' } removeFilter <- function(wb, sheet) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } for (s in sheet) { s <- wb$validateSheet(s) wb$worksheets[[s]]$autoFilter <- character(0) } invisible(wb) } #' @name setHeader #' @title Set header for all worksheets #' @description DEPRECATED #' @author Alexander Walker #' @param wb A workbook object #' @param text header text. A character vector of length 1. #' @param position Position of text in header. One of "left", "center" or "right" #' @export #' @examples #' \dontrun{ #' wb <- createWorkbook("Edgar Anderson") #' addWorksheet(wb, "S1") #' writeDataTable(wb, "S1", x = iris[1:30, ], xy = c("C", 5)) #' #' ## set all headers #' setHeader(wb, "This is a header", position = "center") #' setHeader(wb, "To the left", position = "left") #' setHeader(wb, "On the right", position = "right") #' #' ## set all footers #' setFooter(wb, "Center Footer Here", position = "center") #' setFooter(wb, "Bottom left", position = "left") #' setFooter(wb, Sys.Date(), position = "right") #' #' saveWorkbook(wb, "headerHeaderExample.xlsx", overwrite = TRUE) #' } setHeader <- function(wb, text, position = "center") { warning("This function is deprecated. Use function 'setHeaderFooter()'") if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } position <- tolower(position) if (!position %in% c("left", "center", "right")) { stop("Invalid position.") } if (length(text) != 1) { stop("Text argument must be a character vector of length 1") } # sheet <- wb$validateSheet(1) variable not used wb$headFoot$text[wb$headFoot$pos == position & wb$headFoot$head == "head"] <- as.character(text) } #' @name setFooter #' @title Set footer for all worksheets #' @description DEPRECATED #' @author Alexander Walker #' @param wb A workbook object #' @param text footer text. A character vector of length 1. #' @param position Position of text in footer. One of "left", "center" or "right" #' @export #' @examples #' \dontrun{ #' wb <- createWorkbook("Edgar Anderson") #' addWorksheet(wb, "S1") #' writeDataTable(wb, "S1", x = iris[1:30, ], xy = c("C", 5)) #' #' ## set all headers #' setHeader(wb, "This is a header", position = "center") #' setHeader(wb, "To the left", position = "left") #' setHeader(wb, "On the right", position = "right") #' #' ## set all footers #' setFooter(wb, "Center Footer Here", position = "center") #' setFooter(wb, "Bottom left", position = "left") #' setFooter(wb, Sys.Date(), position = "right") #' #' saveWorkbook(wb, "headerFooterExample.xlsx", overwrite = TRUE) #' } setFooter <- function(wb, text, position = "center") { warning("This function is deprecated. Use function 'setHeaderFooter()'") if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } position <- tolower(position) if (!position %in% c("left", "center", "right")) { stop("Invalid position.") } if (length(text) != 1) { stop("Text argument must be a character vector of length 1") } # sheet <- wb$validateSheet(1) variable not used wb$headFoot$text[wb$headFoot$pos == position & wb$headFoot$head == "foot"] <- as.character(text) } #' @name dataValidation #' @title Add data validation to cells #' @description Add Excel data validation to cells #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Contiguous columns to apply conditional formatting to #' @param rows Contiguous rows to apply conditional formatting to #' @param type One of 'whole', 'decimal', 'date', 'time', 'textLength', 'list' (see examples) #' @param operator One of 'between', 'notBetween', 'equal', #' 'notEqual', 'greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual' #' @param value a vector of length 1 or 2 depending on operator (see examples) #' @param allowBlank logical #' @param showInputMsg logical #' @param showErrorMsg logical #' @export #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' #' writeDataTable(wb, 1, x = iris[1:30, ]) #' #' dataValidation(wb, 1, #' col = 1:3, rows = 2:31, type = "whole", #' operator = "between", value = c(1, 9) #' ) #' #' dataValidation(wb, 1, #' col = 5, rows = 2:31, type = "textLength", #' operator = "between", value = c(4, 6) #' ) #' #' #' ## Date and Time cell validation #' df <- data.frame( #' "d" = as.Date("2016-01-01") + -5:5, #' "t" = as.POSIXct("2016-01-01") + -5:5 * 10000 #' ) #' #' writeData(wb, 2, x = df) #' dataValidation(wb, 2, #' col = 1, rows = 2:12, type = "date", #' operator = "greaterThanOrEqual", value = as.Date("2016-01-01") #' ) #' #' dataValidation(wb, 2, #' col = 2, rows = 2:12, type = "time", #' operator = "between", value = df$t[c(4, 8)] #' ) #' \dontrun{ #' saveWorkbook(wb, "dataValidationExample.xlsx", overwrite = TRUE) #' } #' #' #' ###################################################################### #' ## If type == 'list' #' # operator argument is ignored. #' #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' addWorksheet(wb, "Sheet 2") #' #' writeDataTable(wb, sheet = 1, x = iris[1:30, ]) #' writeData(wb, sheet = 2, x = sample(iris$Sepal.Length, 10)) #' #' dataValidation(wb, 1, col = 1, rows = 2:31, type = "list", value = "'Sheet 2'!$A$1:$A$10") #' #' # openXL(wb) dataValidation <- function(wb, sheet, cols, rows, type, operator, value, allowBlank = TRUE, showInputMsg = TRUE, showErrorMsg = TRUE) { op <- get_set_options() on.exit(options(op), add = TRUE) ## rows and cols if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } rows <- as.integer(rows) ## check length of value if (length(value) > 2) { stop("value argument must be length < 2") } valid_types <- c( "whole", "decimal", "date", "time", ## need to conv "textLength", "list" ) if (!tolower(type) %in% tolower(valid_types)) { stop("Invalid 'type' argument!") } ## operator == 'between' we leave out valid_operators <- c( "between", "notBetween", "equal", "notEqual", "greaterThan", "lessThan", "greaterThanOrEqual", "lessThanOrEqual" ) if (tolower(type) != "list") { if (!tolower(operator) %in% tolower(valid_operators)) { stop("Invalid 'operator' argument!") } operator <- valid_operators[tolower(valid_operators) %in% tolower(operator)][1] } else { operator <- "between" ## ignored } if (!is.logical(allowBlank)) { stop("Argument 'allowBlank' musts be logical!") } if (!is.logical(showInputMsg)) { stop("Argument 'showInputMsg' musts be logical!") } if (!is.logical(showErrorMsg)) { stop("Argument 'showErrorMsg' musts be logical!") } ## All inputs validated type <- valid_types[tolower(valid_types) %in% tolower(type)][1] ## check input combinations if (type == "date" & !"Date" %in% class(value)) { stop("If type == 'date' value argument must be a Date vector.") } if (type == "time" & !any(tolower(class(value)) %in% c("posixct", "posixt"))) { stop("If type == 'date' value argument must be a POSIXct or POSIXlt vector.") } value <- head(value, 2) allowBlank <- as.integer(allowBlank[1]) showInputMsg <- as.integer(showInputMsg[1]) showErrorMsg <- as.integer(showErrorMsg[1]) if (type == "list") { invisible(wb$dataValidation_list( sheet = sheet, startRow = min(rows), endRow = max(rows), startCol = min(cols), endCol = max(cols), value = value, allowBlank = allowBlank, showInputMsg = showInputMsg, showErrorMsg = showErrorMsg )) } else { invisible(wb$dataValidation( sheet = sheet, startRow = min(rows), endRow = max(rows), startCol = min(cols), endCol = max(cols), type = type, operator = operator, value = value, allowBlank = allowBlank, showInputMsg = showInputMsg, showErrorMsg = showErrorMsg )) } invisible(0) } #' @name getDateOrigin #' @title Get the date origin an xlsx file is using #' @description Return the date origin used internally by an xlsx or xlsm file #' @author Alexander Walker #' @param xlsxFile An xlsx or xlsm file. #' @details Excel stores dates as the number of days from either 1904-01-01 or 1900-01-01. This function #' checks the date origin being used in an Excel file and returns is so it can be used in [convertToDate()] #' @return One of "1900-01-01" or "1904-01-01". #' @seealso [convertToDate()] #' @examples #' #' ## create a file with some dates #' \dontrun{ #' write.xlsx(as.Date("2015-01-10") - (0:4), file = "getDateOriginExample.xlsx") #' m <- read.xlsx("getDateOriginExample.xlsx") #' #' ## convert to dates #' do <- getDateOrigin(system.file("extdata", "readTest.xlsx", package = "openxlsx")) #' convertToDate(m[[1]], do) #' } #' @export getDateOrigin <- function(xlsxFile) { xlsxFile <- getFile(xlsxFile) if (!file.exists(xlsxFile)) { stop("File does not exist.") } if (grepl("\\.xls$|\\.xlm$", xlsxFile)) { stop("openxlsx can not read .xls or .xlm files!") } ## create temp dir and unzip xmlDir <- tempfile() xmlFiles <- unzip(xlsxFile, exdir = xmlDir) on.exit(unlink(xmlDir, recursive = TRUE), add = TRUE) workbook <- grep("workbook.xml$", xmlFiles, perl = TRUE, value = TRUE) workbook <- paste(unlist(readUTF8(workbook)), collapse = "") if (grepl('date1904="1"|date1904="true"', workbook, ignore.case = TRUE)) { origin <- "1904-01-01" } else { origin <- "1900-01-01" } return(origin) } #' @name getSheetNames #' @title Get names of worksheets #' @description Returns the worksheet names within an xlsx file #' @author Alexander Walker #' @param file An xlsx or xlsm file. #' @return Character vector of worksheet names. #' @examples #' getSheetNames(system.file("extdata", "readTest.xlsx", package = "openxlsx")) #' @export getSheetNames <- function(file) { if (!file.exists(file)) { stop("file does not exist.") } if (grepl("\\.xls$|\\.xlm$", file)) { stop("openxlsx can not read .xls or .xlm files!") } ## create temp dir and unzip xmlDir <- tempfile() xmlFiles <- unzip(file, exdir = xmlDir) on.exit(unlink(xmlDir, recursive = TRUE), add = TRUE) workbook <- grep("workbook.xml$", xmlFiles, perl = TRUE, value = TRUE) workbook <- readUTF8(workbook) workbook <- removeHeadTag(workbook) sheets <- unlist(regmatches(workbook, gregexpr("(?<=).*(?=)", workbook, perl = TRUE))) sheets <- unlist(regmatches(sheets, gregexpr("]*>", sheets, perl = TRUE))) ## Some veryHidden sheets do not have a sheet content and their rId is empty. ## Such sheets need to be filtered out because otherwise their sheet names ## occur in the list of all sheet names, leading to a wrong association ## of sheet names with sheet indeces. sheets <- grep('r:id="[[:blank:]]*"', sheets, invert = TRUE, value = TRUE) sheetNames <- unlist(regmatches(sheets, gregexpr('(?<=name=")[^"]+', sheets, perl = TRUE))) sheetNames <- replaceXMLEntities(sheetNames) return(sheetNames) } #' @name sheetVisibility #' @title Get/set worksheet visible state #' @description Get and set worksheet visible state #' @param wb A workbook object #' @return Character vector of worksheet names. #' @return Vector of "hidden", "visible", "veryHidden" #' @examples #' #' wb <- createWorkbook() #' addWorksheet(wb, sheetName = "S1", visible = FALSE) #' addWorksheet(wb, sheetName = "S2", visible = TRUE) #' addWorksheet(wb, sheetName = "S3", visible = FALSE) #' #' sheetVisibility(wb) #' sheetVisibility(wb)[1] <- TRUE ## show sheet 1 #' sheetVisibility(wb)[2] <- FALSE ## hide sheet 2 #' sheetVisibility(wb)[3] <- "hidden" ## hide sheet 3 #' sheetVisibility(wb)[3] <- "veryHidden" ## hide sheet 3 from UI #' @export sheetVisibility <- function(wb) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } state <- rep("visible", length(wb$workbook$sheets)) state[grepl("hidden", wb$workbook$sheets)] <- "hidden" state[grepl("veryHidden", wb$workbook$sheets, ignore.case = TRUE)] <- "veryHidden" return(state) } #' @rdname sheetVisibility #' @param value a logical/character vector the same length as sheetVisibility(wb) #' @export `sheetVisibility<-` <- function(wb, value) { op <- get_set_options() on.exit(options(op), add = TRUE) value <- tolower(as.character(value)) if (!any(value %in% c("true", "visible"))) { stop("A workbook must have atleast 1 visible worksheet.") } value[value %in% "true"] <- "visible" value[value %in% "false"] <- "hidden" value[value %in% "veryhidden"] <- "veryHidden" exState0 <- regmatches(wb$workbook$sheets, regexpr('(?<=state=")[^"]+', wb$workbook$sheets, perl = TRUE)) exState <- tolower(exState0) exState[exState %in% "true"] <- "visible" exState[exState %in% "hidden"] <- "hidden" exState[exState %in% "false"] <- "hidden" exState[exState %in% "veryhidden"] <- "veryHidden" if (length(value) != length(wb$workbook$sheets)) { stop(sprintf("value vector must have length equal to number of worksheets in Workbook [%s]", length(exState))) } inds <- which(value != exState) if (length(inds) == 0) { return(invisible(wb)) } for (i in seq_along(wb$worksheets)) { wb$workbook$sheets[i] <- gsub(exState0[i], value[i], wb$workbook$sheets[i], fixed = TRUE) } invisible(wb) } #' @name pageBreak #' @title add a page break to a worksheet #' @description insert page breaks into a worksheet #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param i row or column number to insert page break. #' @param type One of "row" or "column" for a row break or column break. #' @export #' @seealso [addWorksheet()] #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet 1") #' writeData(wb, sheet = 1, x = iris) #' #' pageBreak(wb, sheet = 1, i = 10, type = "row") #' pageBreak(wb, sheet = 1, i = 20, type = "row") #' pageBreak(wb, sheet = 1, i = 2, type = "column") #' \dontrun{ #' saveWorkbook(wb, "pageBreakExample.xlsx", TRUE) #' } #' ## In Excel: View tab -> Page Break Preview pageBreak <- function(wb, sheet, i, type = "row") { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) type <- tolower(type)[1] if (!type %in% c("row", "column")) { stop("'type' argument must be 'row' or 'column'.") } if (!is.numeric(i)) { stop("'i' must be numeric.") } i <- round(i) if (type == "row") { wb$worksheets[[sheet]]$rowBreaks <- c( wb$worksheets[[sheet]]$rowBreaks, sprintf('', i) ) } else if (type == "column") { wb$worksheets[[sheet]]$colBreaks <- c( wb$worksheets[[sheet]]$colBreaks, sprintf('', i) ) } # wb$worksheets[[sheet]]$autoFilter <- sprintf('', paste(getCellRefs(data.frame("x" = c(rows, rows), "y" = c(min(cols), max(cols)))), collapse = ":")) invisible(wb) } #' @name conditionalFormat #' @title Add conditional formatting to cells #' @description DEPRECATED! USE [conditionalFormatting()] #' @author Alexander Walker #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Columns to apply conditional formatting to #' @param rows Rows to apply conditional formatting to #' @param rule The condition under which to apply the formatting or a vector of colours. See examples. #' @param style A style to apply to those cells that satisfy the rule. A Style object returned from createStyle() #' @details DEPRECATED! USE [conditionalFormatting()] #' #' Valid operators are "<", "<=", ">", ">=", "==", "!=". See Examples. #' Default style given by: createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") #' @param type Either 'expression', 'colorscale' or 'databar'. If 'expression' the formatting is determined #' by a formula. If colorScale cells are coloured based on cell value. See examples. #' @seealso [createStyle()] #' @export conditionalFormat <- function(wb, sheet, cols, rows, rule = NULL, style = NULL, type = "expression") { warning("conditionalFormat() has been deprecated. Use conditionalFormatting().") ## Rule always applies to top left of sqref, $ determine which cells the rule depends on ## Rule for "databar" and colourscale are colours of length 2/3 or 1 respectively. type <- tolower(type) if (tolower(type) %in% c("colorscale", "colourscale")) { type <- "colorScale" } else if (type == "databar") { type <- "dataBar" } else if (type != "expression") { stop("Invalid type argument. Type must be 'expression', 'colourScale' or 'databar'") } ## rows and cols if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } rows <- as.integer(rows) ## check valid rule if (type == "colorScale") { if (!length(rule) %in% 2:3) { stop("rule must be a vector containing 2 or 3 colours if type is 'colorScale'") } rule <- validateColour(rule, errorMsg = "Invalid colour specified in rule.") dxfId <- NULL } else if (type == "dataBar") { ## If rule is NULL use default colour if (is.null(rule)) { rule <- "FF638EC6" } else { rule <- validateColour(rule, errorMsg = "Invalid colour specified in rule.") } dxfId <- NULL } else { ## else type == "expression" rule <- toupper(gsub(" ", "", rule)) rule <- replaceIllegalCharacters(rule) rule <- gsub("!=", "<>", rule) rule <- gsub("==", "=", rule) if (!grepl("[A-Z]", substr(rule, 1, 2))) { ## formula looks like "operatorX" , attach top left cell to rule rule <- paste0(getCellRefs(data.frame("x" = min(rows), "y" = min(cols))), rule) } ## else, there is a letter in the formula and apply as is if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } invisible(dxfId <- wb$addDXFS(style)) } invisible(wb$conditionalFormatCell(sheet, startRow = min(rows), endRow = max(rows), startCol = min(cols), endCol = max(cols), dxfId, formula = rule, type = type )) invisible(0) } #' @name all.equal #' @aliases all.equal.Workbook #' @title Check equality of workbooks #' @description Check equality of workbooks #' @method all.equal Workbook #' @param target A `Workbook` object #' @param current A `Workbook` object #' @param ... ignored all.equal.Workbook <- function(target, current, ...) { # print("Comparing workbooks...") # ".rels", # "app", # "charts", # "colWidths", # "Content_Types", # "core", # "drawings", # "drawings_rels", # "media", # "rowHeights", # "workbook", # "workbook.xml.rels", # "worksheets", # "sheetOrder" # "sharedStrings", # "tables", # "tables.xml.rels", # "theme" ## TODO # sheet_data x <- target y <- current nSheets <- length(names(x)) failures <- NULL flag <- all(names(x$charts) %in% names(y$charts)) & all(names(y$charts) %in% names(x$charts)) if (!flag) { message("charts not equal") failures <- c(failures, "wb$charts") } flag <- all(sapply(1:nSheets, function(i) isTRUE(all.equal(x$colWidths[[i]], y$colWidths[[i]])))) if (!flag) { message("colWidths not equal") failures <- c(failures, "wb$colWidths") } flag <- all(x$Content_Types %in% y$Content_Types) & all(y$Content_Types %in% x$Content_Types) if (!flag) { message("Content_Types not equal") failures <- c(failures, "wb$Content_Types") } flag <- all(unlist(x$core) == unlist(y$core)) if (!flag) { message("core not equal") failures <- c(failures, "wb$core") } flag <- all(unlist(x$drawings) %in% unlist(y$drawings)) & all(unlist(y$drawings) %in% unlist(x$drawings)) if (!flag) { message("drawings not equal") failures <- c(failures, "wb$drawings") } flag <- all(unlist(x$drawings_rels) %in% unlist(y$drawings_rels)) & all(unlist(y$drawings_rels) %in% unlist(x$drawings_rels)) if (!flag) { message("drawings_rels not equal") failures <- c(failures, "wb$drawings_rels") } flag <- all(sapply(1:nSheets, function(i) isTRUE(all.equal(x$drawings_rels[[i]], y$drawings_rels[[i]])))) if (!flag) { message("drawings_rels not equal") failures <- c(failures, "wb$drawings_rels") } flag <- all(names(x$media) %in% names(y$media) & names(y$media) %in% names(x$media)) if (!flag) { message("media not equal") failures <- c(failures, "wb$media") } flag <- all(sapply(1:nSheets, function(i) isTRUE(all.equal(x$rowHeights[[i]], y$rowHeights[[i]])))) if (!flag) { message("rowHeights not equal") failures <- c(failures, "wb$rowHeights") } flag <- all(sapply(1:nSheets, function(i) isTRUE(all.equal(names(x$rowHeights[[i]]), names(y$rowHeights[[i]]))))) if (!flag) { message("rowHeights not equal") failures <- c(failures, "wb$rowHeights") } flag <- all(x$sharedStrings %in% y$sharedStrings) & all(y$sharedStrings %in% x$sharedStrings) & (length(x$sharedStrings) == length(y$sharedStrings)) if (!flag) { message("sharedStrings not equal") failures <- c(failures, "wb$sharedStrings") } # flag <- sapply(1:nSheets, function(i) isTRUE(all.equal(x$worksheets[[i]]$sheet_data, y$worksheets[[i]]$sheet_data))) # if(!all(flag)){ # # tmp_x <- x$sheet_data[[which(!flag)[[1]]]] # tmp_y <- y$sheet_data[[which(!flag)[[1]]]] # # tmp_x_e <- sapply(tmp_x, "[[", "r") # tmp_y_e <- sapply(tmp_y, "[[", "r") # flag <- paste0(tmp_x_e, "") != paste0(tmp_x_e, "") # if(any(flag)){ # message(sprintf("sheet_data %s not equal", which(!flag)[[1]])) # message(sprintf("r elements: %s", paste(which(flag), collapse = ", "))) # return(FALSE) # } # # tmp_x_e <- sapply(tmp_x, "[[", "t") # tmp_y_e <- sapply(tmp_y, "[[", "t") # flag <- paste0(tmp_x_e, "") != paste0(tmp_x_e, "") # if(any(flag)){ # message(sprintf("sheet_data %s not equal", which(!flag)[[1]])) # message(sprintf("t elements: %s", paste(which(isTRUE(flag)), collapse = ", "))) # return(FALSE) # } # # # tmp_x_e <- sapply(tmp_x, "[[", "v") # tmp_y_e <- sapply(tmp_y, "[[", "v") # flag <- paste0(tmp_x_e, "") != paste0(tmp_x_e, "") # if(any(flag)){ # message(sprintf("sheet_data %s not equal", which(!flag)[[1]])) # message(sprintf("v elements: %s", paste(which(flag), collapse = ", "))) # return(FALSE) # } # # tmp_x_e <- sapply(tmp_x, "[[", "f") # tmp_y_e <- sapply(tmp_y, "[[", "f") # flag <- paste0(tmp_x_e, "") != paste0(tmp_x_e, "") # if(any(flag)){ # message(sprintf("sheet_data %s not equal", which(!flag)[[1]])) # message(sprintf("f elements: %s", paste(which(flag), collapse = ", "))) # return(FALSE) # } # } flag <- all(names(x$styles) %in% names(y$styles)) & all(names(y$styles) %in% names(x$styles)) if (!flag) { message("names styles not equal") failures <- c(failures, "names of styles not equal") } flag <- all(unlist(x$styles) %in% unlist(y$styles)) & all(unlist(y$styles) %in% unlist(x$styles)) if (!flag) { message("styles not equal") failures <- c(failures, "styles not equal") } flag <- length(x$styleObjects) == length(y$styleObjects) if (!flag) { message("styleObjects lengths not equal") failures <- c(failures, "styleObjects lengths not equal") } nStyles <- length(x$styleObjects) if (nStyles > 0) { for (i in 1:nStyles) { sx <- x$styleObjects[[i]] sy <- y$styleObjects[[i]] flag <- isTRUE(all.equal(sx$sheet, sy$sheet)) if (!flag) { message(sprintf("styleObjects '%s' sheet name not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' sheet name not equal", i)) } flag <- isTRUE(all.equal(sx$rows, sy$rows)) if (!flag) { message(sprintf("styleObjects '%s' rows not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' rows not equal", i)) } flag <- isTRUE(all.equal(sx$cols, sy$cols)) if (!flag) { message(sprintf("styleObjects '%s' cols not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' cols not equal", i)) } ## check style class equality flag <- isTRUE(all.equal(sx$style$fontName, sy$style$fontName)) if (!flag) { message(sprintf("styleObjects '%s' fontName not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' fontName not equal", i)) } flag <- isTRUE(all.equal(sx$style$fontColour, sy$style$fontColour)) if (!flag) { message(sprintf("styleObjects '%s' fontColour not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' fontColour not equal", i)) } flag <- isTRUE(all.equal(sx$style$fontSize, sy$style$fontSize)) if (!flag) { message(sprintf("styleObjects '%s' fontSize not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' fontSize not equal", i)) } flag <- isTRUE(all.equal(sx$style$fontFamily, sy$style$fontFamily)) if (!flag) { message(sprintf("styleObjects '%s' fontFamily not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' fontFamily not equal", i)) } flag <- isTRUE(all.equal(sx$style$fontDecoration, sy$style$fontDecoration)) if (!flag) { message(sprintf("styleObjects '%s' fontDecoration not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' fontDecoration not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderTop, sy$style$borderTop)) if (!flag) { message(sprintf("styleObjects '%s' borderTop not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderTop not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderLeft, sy$style$borderLeft)) if (!flag) { message(sprintf("styleObjects '%s' borderLeft not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderLeft not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderRight, sy$style$borderRight)) if (!flag) { message(sprintf("styleObjects '%s' borderRight not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderRight not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderBottom, sy$style$borderBottom)) if (!flag) { message(sprintf("styleObjects '%s' borderBottom not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderBottom not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderTopColour, sy$style$borderTopColour)) if (!flag) { message(sprintf("styleObjects '%s' borderTopColour not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderTopColour not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderLeftColour, sy$style$borderLeftColour)) if (!flag) { message(sprintf("styleObjects '%s' borderLeftColour not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderLeftColour not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderRightColour, sy$style$borderRightColour)) if (!flag) { message(sprintf("styleObjects '%s' borderRightColour not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderRightColour not equal", i)) } flag <- isTRUE(all.equal(sx$style$borderBottomColour, sy$style$borderBottomColour)) if (!flag) { message(sprintf("styleObjects '%s' borderBottomColour not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' borderBottomColour not equal", i)) } flag <- isTRUE(all.equal(sx$style$halign, sy$style$halign)) if (!flag) { message(sprintf("styleObjects '%s' halign not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' halign not equal", i)) } flag <- isTRUE(all.equal(sx$style$valign, sy$style$valign)) if (!flag) { message(sprintf("styleObjects '%s' valign not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' valign not equal", i)) } flag <- isTRUE(all.equal(sx$style$indent, sy$style$indent)) if (!flag) { message(sprintf("styleObjects '%s' indent not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' indent not equal", i)) } flag <- isTRUE(all.equal(sx$style$textRotation, sy$style$textRotation)) if (!flag) { message(sprintf("styleObjects '%s' textRotation not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' textRotation not equal", i)) } flag <- isTRUE(all.equal(sx$style$numFmt, sy$style$numFmt)) if (!flag) { message(sprintf("styleObjects '%s' numFmt not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' numFmt not equal", i)) } flag <- isTRUE(all.equal(sx$style$fill, sy$style$fill)) if (!flag) { message(sprintf("styleObjects '%s' fill not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' fill not equal", i)) } flag <- isTRUE(all.equal(sx$style$wrapText, sy$style$wrapText)) if (!flag) { message(sprintf("styleObjects '%s' wrapText not equal", i)) failures <- c(failures, sprintf("styleObjects '%s' wrapText not equal", i)) } } } flag <- all(x$sheet_names %in% y$sheet_names) & all(y$sheet_names %in% x$sheet_names) if (!flag) { message("names workbook not equal") failures <- c(failures, "names workbook not equal") } flag <- all(unlist(x$workbook) %in% unlist(y$workbook)) & all(unlist(y$workbook) %in% unlist(x$workbook)) if (!flag) { message("workbook not equal") failures <- c(failures, "wb$workbook") } flag <- all(unlist(x$workbook.xml.rels) %in% unlist(y$workbook.xml.rels)) & all(unlist(y$workbook.xml.rels) %in% unlist(x$workbook.xml.rels)) if (!flag) { message("workbook.xml.rels not equal") failures <- c(failures, "wb$workbook.xml.rels") } for (i in 1:nSheets) { ws_x <- x$worksheets[[i]] ws_y <- y$worksheets[[i]] flag <- all(names(ws_x) %in% names(ws_y)) & all(names(ws_y) %in% names(ws_x)) if (!flag) { message(sprintf("names of worksheet elements for sheet %s not equal", i)) failures <- c(failures, sprintf("names of worksheet elements for sheet %s not equal", i)) } nms <- c( "sheetPr", "dataValidations", "sheetViews", "cols", "pageMargins", "extLst", "conditionalFormatting", "oleObjects", "colBreaks", "dimension", "drawing", "sheetFormatPr", "tableParts", "mergeCells", "hyperlinks", "headerFooter", "autoFilter", "rowBreaks", "pageSetup", "freezePane", "legacyDrawingHF", "legacyDrawing" ) for (j in nms) { flag <- isTRUE(all.equal(gsub(" |\t", "", ws_x[[j]]), gsub(" |\t", "", ws_y[[j]]))) if (!flag) { message(sprintf("worksheet '%s', element '%s' not equal", i, j)) failures <- c(failures, sprintf("worksheet '%s', element '%s' not equal", i, j)) } } } flag <- all(unlist(x$sheetOrder) %in% unlist(y$sheetOrder)) & all(unlist(y$sheetOrder) %in% unlist(x$sheetOrder)) if (!flag) { message("sheetOrder not equal") failures <- c(failures, "sheetOrder not equal") } flag <- length(x$tables) == length(y$tables) if (!flag) { message("length of tables not equal") failures <- c(failures, "length of tables not equal") } flag <- all(names(x$tables) == names(y$tables)) if (!flag) { message("names of tables not equal") failures <- c(failures, "names of tables not equal") } flag <- all(unlist(x$tables) == unlist(y$tables)) if (!flag) { message("tables not equal") failures <- c(failures, "tables not equal") } flag <- isTRUE(all.equal(x$tables.xml.rels, y$tables.xml.rels)) if (!flag) { message("tables.xml.rels not equal") failures <- c(failures, "tables.xml.rels not equal") } flag <- x$theme == y$theme if (!flag) { message("theme not equal") failures <- c(failures, "theme not equal") } if (!is.null(failures)) { return(FALSE) } # "connections", # "externalLinks", # "externalLinksRels", # "headFoot", # "pivotTables", # "pivotTables.xml.rels", # "pivotDefinitions", # "pivotRecords", # "pivotDefinitionsRels", # "queryTables", # "slicers", # "slicerCaches", # "vbaProject", return(TRUE) } #' @name sheetVisible #' @title Get worksheet visible state. #' @description DEPRECATED - Use function 'sheetVisibility() #' @author Alexander Walker #' @param wb A workbook object #' @return Character vector of worksheet names. #' @return TRUE if sheet is visible, FALSE if sheet is hidden #' @examples #' #' wb <- createWorkbook() #' addWorksheet(wb, sheetName = "S1", visible = FALSE) #' addWorksheet(wb, sheetName = "S2", visible = TRUE) #' addWorksheet(wb, sheetName = "S3", visible = FALSE) #' #' sheetVisible(wb) #' sheetVisible(wb)[1] <- TRUE ## show sheet 1 #' sheetVisible(wb)[2] <- FALSE ## hide sheet 2 #' @export sheetVisible <- function(wb) { warning("This function is deprecated. Use function 'sheetVisibility()'") if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } state <- rep(TRUE, length(wb$workbook$sheets)) state[grepl("hidden", wb$workbook$sheets)] <- FALSE return(state) } #' @rdname sheetVisible #' @param value a logical vector the same length as sheetVisible(wb) #' @export `sheetVisible<-` <- function(wb, value) { warning("This function is deprecated. Use function 'sheetVisibility()'") if (!is.logical(value)) { stop("value must be a logical vector.") } if (!any(value)) { stop("A workbook must have atleast 1 visible worksheet.") } value <- as.character(value) value[value %in% "TRUE"] <- "visible" value[value %in% "FALSE"] <- "hidden" exState <- rep("visible", length(wb$workbook$sheets)) exState[grepl("hidden", wb$workbook$sheets)] <- "hidden" if (length(value) != length(wb$workbook$sheets)) { stop(sprintf("value vector must have length equal to number of worksheets in Workbook [%s]", length(exState))) } inds <- which(value != exState) if (length(inds) == 0) { return(invisible(wb)) } for (i in inds) { wb$workbook$sheets[i] <- gsub(exState[i], value[i], wb$workbook$sheets[i]) } invisible(wb) } #' @name copyWorkbook #' @title Copy a Workbook object. #' @description Just a wrapper of wb$copy() #' @param wb A workbook object #' @return Workbook #' @examples #' #' wb <- createWorkbook() #' wb2 <- wb ## does not create a copy #' wb3 <- copyWorkbook(wb) ## wrapper for wb$copy() #' #' addWorksheet(wb, "Sheet1") ## adds worksheet to both wb and wb2 but not wb3 #' #' names(wb) #' names(wb2) #' names(wb3) #' @export copyWorkbook <- function(wb) { if (!inherits(wb, "Workbook")) { stop("argument must be a Workbook.") } return(wb$copy()) } #' @name getTables #' @title List Excel tables in a workbook #' @description List Excel tables in a workbook #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @return character vector of table names on the specified sheet #' @examples #' #' wb <- createWorkbook() #' addWorksheet(wb, sheetName = "Sheet 1") #' writeDataTable(wb, sheet = "Sheet 1", x = iris) #' writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) #' #' getTables(wb, sheet = "Sheet 1") #' @export getTables <- function(wb, sheet) { if (!inherits(wb, "Workbook")) { stop("argument must be a Workbook.") } if (length(sheet) != 1) { stop("sheet argument must be length 1") } if (length(wb$tables) == 0) { return(character(0)) } sheet <- wb$validateSheet(sheetName = sheet) table_sheets <- attr(wb$tables, "sheet") tables <- attr(wb$tables, "tableName") refs <- names(wb$tables) refs <- refs[table_sheets == sheet & !grepl("openxlsx_deleted", tables, fixed = TRUE)] tables <- tables[table_sheets == sheet & !grepl("openxlsx_deleted", tables, fixed = TRUE)] if (length(tables) > 0) { attr(tables, "refs") <- refs } return(tables) } #' @name removeTable #' @title Remove an Excel table in a workbook #' @description List Excel tables in a workbook #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param table Name of table to remove. See [getTables()] #' @return character vector of table names on the specified sheet #' @examples #' #' wb <- createWorkbook() #' addWorksheet(wb, sheetName = "Sheet 1") #' addWorksheet(wb, sheetName = "Sheet 2") #' writeDataTable(wb, sheet = "Sheet 1", x = iris, tableName = "iris") #' writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) #' #' #' removeWorksheet(wb, sheet = 1) ## delete worksheet removes table objects #' #' writeDataTable(wb, sheet = 1, x = iris, tableName = "iris") #' writeDataTable(wb, sheet = 1, x = mtcars, tableName = "mtcars", startCol = 10) #' #' ## removeTable() deletes table object and all data #' getTables(wb, sheet = 1) #' removeTable(wb = wb, sheet = 1, table = "iris") #' writeDataTable(wb, sheet = 1, x = iris, tableName = "iris", startCol = 1) #' #' getTables(wb, sheet = 1) #' removeTable(wb = wb, sheet = 1, table = "iris") #' writeDataTable(wb, sheet = 1, x = iris, tableName = "iris", startCol = 1) #' \dontrun{ #' saveWorkbook(wb = wb, file = "removeTableExample.xlsx", overwrite = TRUE) #' } #' #' @export removeTable <- function(wb, sheet, table) { if (!inherits(wb, "Workbook")) { stop("argument must be a Workbook.") } if (length(sheet) != 1) { stop("sheet argument must be length 1") } if (length(table) != 1) { stop("table argument must be length 1") } ## delete table object and all data in it sheet <- wb$validateSheet(sheetName = sheet) if (!table %in% attr(wb$tables, "tableName")) { stop(sprintf("table '%s' does not exist.", table), call. = FALSE) } ## get existing tables table_sheets <- attr(wb$tables, "sheet") table_names <- attr(wb$tables, "tableName") refs <- names(wb$tables) ## delete table object (by flagging as deleted) inds <- which(table_sheets %in% sheet & table_names %in% table) table_name_original <- table_names[inds] table_names[inds] <- paste0(table_name_original, "_openxlsx_deleted") attr(wb$tables, "tableName") <- table_names ## delete reference from worksheet to table worksheet_table_names <- attr(wb$worksheets[[sheet]]$tableParts, "tableName") to_remove <- which(worksheet_table_names == table_name_original) wb$worksheets[[sheet]]$tableParts <- wb$worksheets[[sheet]]$tableParts[-to_remove] attr(wb$worksheets[[sheet]]$tableParts, "tableName") <- worksheet_table_names[-to_remove] ## Now delete data from the worksheet refs <- strsplit(refs[[inds]], split = ":")[[1]] rows <- as.integer(gsub("[A-Z]", "", refs)) rows <- seq(from = rows[1], to = rows[2], by = 1) cols <- convertFromExcelRef(refs) cols <- seq(from = cols[1], to = cols[2], by = 1) ## now delete data deleteData(wb = wb, sheet = sheet, rows = rows, cols = cols, gridExpand = TRUE) invisible(0) } #' @name groupColumns #' @title Group columns #' @description Group a selection of columns #' @author Joshua Sturm #' @param wb A workbook object. #' @param sheet A name or index of a worksheet. #' @param cols Indices of cols to group. #' @param hidden Logical vector. If TRUE the grouped columns are hidden. Defaults to FALSE. #' @details Group columns together, with the option to hide them. #' #' NOTE: [setColWidths()] has a conflicting `hidden` parameter; changing one will update the other. #' @seealso [ungroupColumns()] to ungroup columns. [groupRows()] for grouping rows. #' @export #' groupColumns <- function(wb, sheet, cols, hidden = FALSE) { op <- get_set_options() on.exit(options(op), add = TRUE) sheet <- wb$validateSheet(sheet) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } if (any(cols) < 1L) { stop("Invalid columns selected (<= 0).") } if (!is.logical(hidden)) { stop("Hidden should be a logical value (TRUE/FALSE).") } if (length(hidden) > length(cols)) { stop("Hidden argument is of greater length than number of cols.") } levels <- rep("1", length(cols)) hidden <- rep(hidden, length.out = length(cols)) hidden <- hidden[!duplicated(cols)] levels <- levels[!duplicated(cols)] cols <- cols[!duplicated(cols)] cols <- convertFromExcelRef(cols) if (length(wb$colWidths[[sheet]]) > 0) { existing_cols <- names(wb$colWidths[[sheet]]) existing_hidden <- attr(wb$colWidths[[sheet]], "hidden", exact = TRUE) if (any(existing_cols %in% cols)) { for (i in intersect(existing_cols, cols)) { width_hidden <- attr(wb$colWidths[[sheet]], "hidden")[attr(wb$colWidths[[sheet]], "names") == i] outline_hidden <- as.character(as.integer(hidden))[cols == i] if (width_hidden != outline_hidden) { attr(wb$colWidths[[sheet]], "hidden")[attr(wb$colWidths[[sheet]], "names") == i] <- outline_hidden } } # cols <- cols[!cols %in% existing_cols] # hidden <- attr(wb$colOutlineLevels[[sheet]], "hidden")[attr(wb$colOutlineLevels[[sheet]], "name") %in% cols] # wb$colOutlineLevels[[sheet]] <- cols # attr(wb$colOutlineLevels[[sheet]], "hidden") <- as.character(as.integer(hidden)) } } if (length(wb$colOutlineLevels[[sheet]]) > 0) { existing_cols <- names(wb$colOutlineLevels[[sheet]]) existing_levels <- unname(wb$colOutlineLevels[[sheet]]) existing_hidden <- attr(wb$colOutlineLevels[[sheet]], "hidden") # check if column is already grouped flag <- existing_cols %in% cols if (any(flag)) { existing_cols <- existing_cols[!flag] existing_levels <- existing_levels[!flag] existing_hidden <- existing_hidden[!flag] } all_names <- c(existing_cols, cols) all_levels <- c(existing_levels, levels) all_hidden <- c(existing_hidden, as.character(as.integer(hidden))) ord <- order(as.integer(all_names)) all_names <- all_names[ord] all_levels <- all_levels[ord] all_hidden <- all_hidden[ord] names(all_levels) <- all_names wb$colOutlineLevels[[sheet]] <- all_levels levels <- all_levels attr(wb$colOutlineLevels[[sheet]], "hidden") <- as.character(as.integer(all_hidden)) hidden <- all_hidden } else { names(levels) <- cols wb$colOutlineLevels[[sheet]] <- levels attr(wb$colOutlineLevels[[sheet]], "hidden") <- as.character(as.integer(hidden)) } invisible(0) } #' @name ungroupColumns #' @title Ungroup Columns #' @description Ungroup a selection of columns #' @author Joshua Sturm #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Indices of columns to ungroup #' @details If column was previously hidden, it will now be shown #' @seealso [ungroupRows()] To ungroup rows #' @export ungroupColumns <- function(wb, sheet, cols) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } if (any(cols) < 1L) { stop("Invalid columns selected (<= 0).") } op <- get_set_options() on.exit(options(op), add = TRUE) customCols <- as.integer(names(wb$colOutlineLevels[[sheet]])) removeInds <- which(customCols %in% cols) # Check if any selected columns are already grouped if (length(removeInds) > 0) { remainingCols <- customCols[-removeInds] if (length(remainingCols) == 0) { wb$colOutlineLevels[[sheet]] <- list() wb$worksheets[[sheet]]$sheetFormatPr <- sub(' outlineLevelCol="1"', "", wb$worksheets[[sheet]]$sheetFormatPr) } else { rem_widths <- wb$colOutlineLevels[[sheet]][-removeInds] names(rem_widths) <- as.character(remainingCols) wb$colOutlineLevels[[sheet]] <- rem_widths } } if (length(wb$colWidths[[sheet]]) > 0) { if (any(cols %in% names(wb$colWidths[[sheet]]))) { attr(wb$colWidths[[sheet]], "hidden")[attr(wb$colWidths[[sheet]], "names") %in% cols] <- "0" } } } #' @name groupRows #' @title Group Rows #' @description Group a selection of rows #' @author Joshua Sturm #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param rows Indices of rows to group #' @param hidden Logical vector. If TRUE the grouped columns are hidden. Defaults to FALSE #' @seealso [ungroupRows()] to ungroup rows. [groupColumns()] for grouping columns. #' @export groupRows <- function(wb, sheet, rows, hidden = FALSE) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) if (length(hidden) > length(rows)) { stop("Hidden argument is of greater length than number of rows.") } if (!is.logical(hidden)) { stop("Hidden should be a logical value (TRUE/FALSE).") } if (any(rows) < 1L) { stop("Invalid rows entered (<= 0).") } hidden <- rep(as.character(as.integer(hidden)), length.out = length(rows)) op <- get_set_options() on.exit(options(op), add = TRUE) levels <- rep("1", length(rows)) # Remove duplicates hidden <- hidden[!duplicated(rows)] levels <- levels[!duplicated(rows)] rows <- rows[!duplicated(rows)] names(levels) <- rows wb$groupRows(sheet = sheet, rows = rows, hidden = hidden, levels = levels) } #' @name ungroupRows #' @title Ungroup Rows #' @description Ungroup a selection of rows #' @author Joshua Sturm #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param rows Indices of rows to ungroup #' @details If row was previously hidden, it will now be shown #' @seealso [ungroupColumns()] #' @export ungroupRows <- function(wb, sheet, rows) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } sheet <- wb$validateSheet(sheet) if (any(rows) < 1L) { stop("Invalid rows entered (<= 0).") } customRows <- as.integer(names(wb$outlineLevels[[sheet]])) removeInds <- which(customRows %in% rows) if (length(removeInds) > 0) { wb$outlineLevels[[sheet]] <- wb$outlineLevels[[sheet]][-removeInds] } if (length(wb$outlineLevels[[sheet]]) == 0) { wb$worksheets[[sheet]]$sheetFormatPr <- sub(' outlineLevelRow="1"', "", wb$worksheets[[sheet]]$sheetFormatPr) } } #' @name addCreator #' @title Add another author to the meta data of the file. #' @author Philipp Schauberger #' @description Just a wrapper of wb$addCreator() #' @param wb A workbook object #' @param Creator A string object with the name of the creator #' @examples #' #' wb <- createWorkbook() #' addCreator(wb, "test") #' @export addCreator <- function(wb, Creator) { if (!inherits(wb, "Workbook")) { stop("argument must be a Workbook.") } invisible(wb$addCreator(Creator)) } #' @name setLastModifiedBy #' @title Add another author to the meta data of the file. #' @author Philipp Schauberger #' @description Just a wrapper of wb$changeLastModifiedBy() #' @param wb A workbook object #' @param LastModifiedBy A string object with the name of the LastModifiedBy-User #' @examples #' #' wb <- createWorkbook() #' setLastModifiedBy(wb, "test") #' @export setLastModifiedBy <- function(wb, LastModifiedBy) { if (!inherits(wb, "Workbook")) { stop("argument must be a Workbook.") } invisible(wb$changeLastModifiedBy(LastModifiedBy)) } #' @name getCreators #' @title Add another author to the meta data of the file. #' @description Just a wrapper of wb$getCreators() #' Get the names of the #' @param wb A workbook object #' @author Philipp Schauberger #' @return vector of creators #' @examples #' #' wb <- createWorkbook() #' getCreators(wb) #' @export getCreators <- function(wb) { if (!inherits(wb, "Workbook")) { stop("argument must be a Workbook.") } return(wb$getCreators()) } #' @name activeSheet #' @title Get/set active sheet of the workbook #' @author Philipp Schauberger #' @description Get and set active sheet of the workbook #' @param wb A workbook object #' @return return the active sheet of the workbook #' @examples #' #' wb <- createWorkbook() #' addWorksheet(wb, sheetName = "S1") #' addWorksheet(wb, sheetName = "S2") #' addWorksheet(wb, sheetName = "S3") #' #' activeSheet(wb) # default value is the first sheet active #' activeSheet(wb) <- 1 ## active sheet S1 #' activeSheet(wb) #' activeSheet(wb) <- "S2" ## active sheet S2 #' activeSheet(wb) #' @export activeSheet <- function(wb) { if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } return(wb$ActiveSheet) } #' @rdname activeSheet #' @param value index of the active sheet or name of the active sheet #' @export `activeSheet<-` <- function(wb, value) { op <- get_set_options() on.exit(options(op), add = TRUE) if (!"Workbook" %in% class(wb)) { stop("First argument must be a Workbook.") } invisible(wb$setactiveSheet(value)) invisible(wb) } openxlsx/R/openXL.R0000644000176200001440000000637614155600363013702 0ustar liggesusers#' @name openXL #' @title Open a Microsoft Excel file (xls/xlsx) or an openxlsx Workbook #' @author Luca Braglia #' @description This function tries to open a Microsoft Excel #' (xls/xlsx) file or an openxlsx Workbook with the proper #' application, in a portable manner. #' #' In Windows (c) and Mac (c), it uses system default handlers, #' given the file type. #' #' In Linux it searches (via `which`) for available xls/xlsx #' reader applications (unless `options('openxlsx.excelApp')` #' is set to the app bin path), and if it finds anything, sets #' `options('openxlsx.excelApp')` to the program choosen by #' the user via a menu (if many are present, otherwise it will #' set the only available). Currently searched for apps are #' Libreoffice/Openoffice (`soffice` bin), Gnumeric #' (`gnumeric`) and Calligra Sheets (`calligrasheets`). #' #' @param file path to the Excel (xls/xlsx) file or Workbook object. #' @usage openXL(file=NULL) #' @export openXL #' @examples #' # file example #' example(writeData) #' # openXL("writeDataExample.xlsx") #' #' # (not yet saved) Workbook example #' wb <- createWorkbook() #' x <- mtcars[1:6, ] #' addWorksheet(wb, "Cars") #' writeData(wb, "Cars", x, startCol = 2, startRow = 3, rowNames = TRUE) #' # openXL(wb) openXL <- function(file = NULL) { op <- get_set_options() on.exit(options(op), add = TRUE) if (is.null(file)) stop("A file has to be specified.") ## workbook handling if ("Workbook" %in% class(file)) { file <- file$saveWorkbook() } if (!file.exists(file)) stop("Non existent file or wrong path.") ## execution should be in background in order to not block R ## interpreter file <- normalizePath(file) userSystem <- Sys.info()["sysname"] if ("Linux" == userSystem) { if (is.null(app <- unlist(options("openxlsx.excelApp")))) { app <- chooseExcelApp() } myCommand <- paste(app, file, "&", sep = " ") system(command = myCommand) } else if ("Windows" == userSystem) { shell(shQuote(string = file), wait = FALSE) #nolint } else if ("Darwin" == userSystem) { myCommand <- paste0("open ", file) system(command = myCommand) } else { warning("Operating system not handled.") } } chooseExcelApp <- function() { m <- c( `Libreoffice/OpenOffice` = "soffice", `Calligra Sheets` = "calligrasheets", `Gnumeric` = "gnumeric" ) prog <- Sys.which(m) names(prog) <- names(m) nApps <- length(availProg <- prog["" != prog]) if (0 == nApps) { stop( "No applications (detected) available.\n", "Set options('openxlsx.excelApp'), instead." ) } else if (1 == nApps) { cat("Only", names(availProg), "found; I'll use it.\n") unnprog <- unname(availProg) options(openxlsx.excelApp = unnprog) invisible(unnprog) } else if (1 < nApps) { if (!interactive()) { stop( "Cannot choose an Excel file opener non-interactively.\n", "Set options('openxlsx.excelApp'), instead." ) } res <- menu(names(availProg), title = "Excel Apps availables") unnprog <- unname(availProg[res]) if (res > 0L) options(openxlsx.excelApp = unnprog) invisible(unname(unnprog)) } else { stop("Unexpected error.") } } openxlsx/R/worksheet_class.R0000644000176200001440000002110614155600363015661 0ustar liggesusers #' @include class_definitions.R WorkSheet$methods(initialize = function( showGridLines = TRUE, tabSelected = FALSE, tabColour = NULL, zoom = 100, oddHeader = NULL, oddFooter = NULL, evenHeader = NULL, evenFooter = NULL, firstHeader = NULL, firstFooter = NULL, paperSize = 9, orientation = "portrait", hdpi = 300, vdpi = 300 ) { if (!is.null(tabColour)) { tabColour <- sprintf('', tabColour) } else { tabColour <- character(0) } if (zoom < 10) { zoom <- 10 } else if (zoom > 400) { zoom <- 400 } naToNULLList <- function(x) { lapply(x, function(x) { if (is.na(x)) { return(NULL) } x }) } # hf <- list( # oddHeader = naToNULLList(oddHeader), # oddFooter = naToNULLList(oddFooter), # evenHeader = naToNULLList(evenHeader), # evenFooter = naToNULLList(evenFooter), # firstHeader = naToNULLList(firstHeader), # firstFooter = naToNULLList(firstFooter) # ) hf <- list( oddHeader = oddHeader, oddFooter = oddFooter, evenHeader = evenHeader, evenFooter = evenFooter, firstHeader = firstHeader, firstFooter = firstFooter ) if (all(vapply(hf, is.null, NA))) { hf <- list() } ## list of all possible children sheetPr <<- tabColour dimension <<- '' sheetViews <<- sprintf('', as.integer(zoom), as.integer(showGridLines), as.integer(tabSelected)) sheetFormatPr <<- '' cols <<- character(0) autoFilter <<- character(0) mergeCells <<- character(0) conditionalFormatting <<- character(0) dataValidations <<- NULL dataValidationsLst <<- character(0) hyperlinks <<- list() pageMargins <<- '' pageSetup <<- sprintf('', paperSize, orientation, hdpi, vdpi) ## will always be 2 headerFooter <<- hf rowBreaks <<- character(0) colBreaks <<- character(0) drawing <<- '' ## will always be 1 legacyDrawing <<- character(0) legacyDrawingHF <<- character(0) oleObjects <<- character(0) tableParts <<- character(0) extLst <<- character(0) freezePane <<- character(0) sheet_data <<- Sheet_Data$new() }) WorkSheet$methods(get_prior_sheet_data = function() { xml <- '' if (length(sheetPr) > 0) { tmp <- sheetPr if (!any(grepl("", tmp, fixed = TRUE))) { tmp <- paste0("", paste(tmp, collapse = ""), "") } xml <- paste(xml, tmp, collapse = "") } if (length(dimension) > 0) { xml <- paste(xml, dimension, collapse = "") } ## sheetViews handled here if (length(freezePane) > 0) { xml <- paste(xml, gsub("/>", paste0(">", freezePane, ""), sheetViews, fixed = TRUE), collapse = "") } else if (length(sheetViews) > 0) { xml <- paste(xml, sheetViews, collapse = "") } if (length(sheetFormatPr) > 0) { xml <- paste(xml, sheetFormatPr, collapse = "") } if (length(cols) > 0) { xml <- paste(xml, pxml(c("", cols, "")), collapse = "") } return(xml) }) WorkSheet$methods(get_post_sheet_data = function() { xml <- "" if (length(sheetProtection) > 0) { xml <- paste0(xml, sheetProtection, collapse = "") } if (length(autoFilter) > 0) { xml <- paste0(xml, autoFilter, collapse = "") } if (length(mergeCells) > 0) { xml <- paste0(xml, paste0(sprintf('', length(mergeCells)), pxml(mergeCells), ""), collapse = "") } if (length(conditionalFormatting) > 0) { nms <- names(conditionalFormatting) xml <- paste0(xml, paste( sapply(unique(nms), function(x) { paste0( sprintf('', x), pxml(conditionalFormatting[nms == x]), "" ) }), collapse = "" ), collapse = "" ) } if (length(dataValidations) > 0) { xml <- paste0(xml, paste0(sprintf('', length(dataValidations)), pxml(dataValidations), "")) } if (length(hyperlinks) > 0) { h_inds <- paste0(seq_along(hyperlinks), "h") xml <- paste(xml, paste("", paste(sapply(seq_along(h_inds), function(i) hyperlinks[[i]]$to_xml(h_inds[i])), collapse = ""), ""), collapse = "") } if (length(pageMargins) > 0) { xml <- paste0(xml, pageMargins, collapse = "") } if (length(pageSetup) > 0) { xml <- paste0(xml, pageSetup, collapse = "") } if (!identical(headerFooter, list()) && length(headerFooter) > 0) { xml <- paste0(xml, genHeaderFooterNode(headerFooter), collapse = "") } ## rowBreaks and colBreaks if (length(rowBreaks) > 0) { xml <- paste0(xml, paste0(sprintf('', length(rowBreaks), length(rowBreaks)), paste(rowBreaks, collapse = ""), ""), collapse = "" ) } if (length(colBreaks) > 0) { xml <- paste0(xml, paste0(sprintf('', length(colBreaks), length(colBreaks)), paste(colBreaks, collapse = ""), ""), collapse = "" ) } if (length(drawing) > 0) { xml <- paste0(xml, drawing, collapse = "") } if (length(legacyDrawing) > 0) { xml <- paste0(xml, legacyDrawing, collapse = "") } if (length(legacyDrawingHF) > 0) { xml <- paste0(xml, legacyDrawingHF, collapse = "") } if (length(oleObjects) > 0) { xml <- paste0(xml, oleObjects, collapse = "") } if (length(tableParts) > 0) { xml <- paste0(xml, paste0(sprintf('', length(tableParts)), pxml(tableParts), ""), collapse = "" ) } if (length(dataValidationsLst) > 0) { dataValidationsLst_xml <- paste0(sprintf('', length(dataValidationsLst)), paste0(pxml(dataValidationsLst), ""), collapse = "" ) } else { dataValidationsLst_xml <- character(0) } if (length(extLst) > 0 || length(dataValidationsLst) > 0) { xml <- paste0(xml, sprintf("%s", paste0(pxml(extLst), dataValidationsLst_xml))) } xml <- paste0(xml, "") return(xml) }) WorkSheet$methods(order_sheetdata = function() { if (sheet_data$n_elements == 0) { return(invisible(0)) } if (sheet_data$data_count > 1) { ord <- order(sheet_data$rows, sheet_data$cols, method = "radix", na.last = TRUE) sheet_data$rows <<- sheet_data$rows[ord] sheet_data$cols <<- sheet_data$cols[ord] sheet_data$t <<- sheet_data$t[ord] sheet_data$v <<- sheet_data$v[ord] sheet_data$f <<- sheet_data$f[ord] sheet_data$style_id <<- sheet_data$style_id[ord] sheet_data$data_count <<- 1L dm1 <- paste0(int_2_cell_ref(cols = sheet_data$cols[1]), sheet_data$rows[1]) dm2 <- paste0(int_2_cell_ref(cols = sheet_data$cols[sheet_data$n_elements]), sheet_data$rows[sheet_data$n_elements]) if (length(dm1) == 1 & length(dm2) != 1) { if (!is.na(dm1) & !is.na(dm2) & dm1 != "NA" & dm2 != "NA") { dimension <<- sprintf("", dm1, dm2) } } } invisible(0) }) openxlsx/R/sheet_data_class.R0000644000176200001440000000465714155600363015763 0ustar liggesusers #' @include class_definitions.R Sheet_Data$methods(initialize = function() { rows <<- integer(0) cols <<- integer(0) t <<- integer(0) v <<- character(0) f <<- character(0) style_id <<- character(0) data_count <<- 0L n_elements <<- 0L }) Sheet_Data$methods(delete = function(rows_in, cols_in, grid_expand) { cols_in <- convertFromExcelRef(cols_in) rows_in <- as.integer(rows_in) ## rows and cols need to be the same length if (grid_expand) { n <- length(rows_in) rows_in <- rep.int(rows_in, times = length(cols_in)) cols_in <- rep(cols_in, each = n) } if (length(rows_in) != length(cols_in)) { stop("Length of rows and cols must be equal.") } inds <- which(paste(rows, cols, sep = ",") %in% paste(rows_in, cols_in, sep = ",")) if (length(inds) > 0) { ## writing over existing data rows <<- rows[-inds] cols <<- cols[-inds] t <<- t[-inds] v <<- v[-inds] f <<- f[-inds] n_elements <<- as.integer(length(rows)) if (n_elements == 0) { data_count <<- 0L } } }) Sheet_Data$methods(write = function(rows_in, cols_in, t_in, v_in, f_in, any_functions = TRUE) { if (length(rows_in) == 0 | length(cols_in) == 0) { return(invisible(0)) } possible_overlap <- FALSE if (n_elements > 0) { possible_overlap <- (min(cols_in, na.rm = TRUE) <= max(cols, na.rm = TRUE)) & (max(cols_in, na.rm = TRUE) >= min(cols, na.rm = TRUE)) & (min(rows_in, na.rm = TRUE) <= max(rows, na.rm = TRUE)) & (max(rows_in, na.rm = TRUE) >= min(rows, na.rm = TRUE)) } n <- length(cols_in) cols_in <- rep.int(cols_in, times = length(rows_in)) rows_in <- rep(rows_in, each = n) if (any_functions) { if (any(!is.na(f_in))) { v_in[!is.na(f_in)] <- as.character(NA) t_in[!is.na(f_in)] <- 3L ## "str" } } inds <- integer(0) if (possible_overlap) { inds <- which(paste(rows, cols, sep = ",") %in% paste(rows_in, cols_in, sep = ",")) } if (length(inds) > 0) { rows <<- c(rows[-inds], rows_in) cols <<- c(cols[-inds], cols_in) t <<- c(t[-inds], t_in) v <<- c(v[-inds], v_in) f <<- c(f[-inds], f_in) } else { rows <<- c(rows, rows_in) cols <<- c(cols, cols_in) t <<- c(t, t_in) v <<- c(v, v_in) f <<- c(f, f_in) } n_elements <<- as.integer(length(rows)) data_count <<- data_count + 1L }) openxlsx/R/workbook_column_widths.R0000644000176200001440000001656114155600363017266 0ustar liggesusers #' @include class_definitions.R Workbook$methods(setColWidths = function(sheet) { sheet <- validateSheet(sheet) widths <- colWidths[[sheet]] hidden <- attr(colWidths[[sheet]], "hidden", exact = TRUE) if (length(hidden) != length(widths)) { hidden <- rep("0", length(widths)) } cols <- names(colWidths[[sheet]]) autoColsInds <- widths %in% c("auto", "auto2") autoCols <- cols[autoColsInds] ## If any not auto if (any(!autoColsInds)) { widths[!autoColsInds] <- as.numeric(widths[!autoColsInds]) + 0.71 } ## If any auto if (length(autoCols) > 0) { ## only run if data on worksheet if (worksheets[[sheet]]$sheet_data$n_elements == 0) { missingAuto <- autoCols } else if (all(is.na(worksheets[[sheet]]$sheet_data$v))) { missingAuto <- autoCols } else { ## First thing - get base font max character width baseFont <- getBaseFont() baseFontName <- unlist(baseFont$name, use.names = FALSE) if (is.null(baseFontName)) { baseFontName <- "calibri" } else { baseFontName <- gsub(" ", ".", tolower(baseFontName), fixed = TRUE) if (!baseFontName %in% names(openxlsxFontSizeLookupTable)) { baseFontName <- "calibri" } } baseFontSize <- unlist(baseFont$size, use.names = FALSE) if (is.null(baseFontSize)) { baseFontSize <- 11 } else { baseFontSize <- as.numeric(baseFontSize) baseFontSize <- ifelse(baseFontSize < 8, 8, ifelse(baseFontSize > 36, 36, baseFontSize)) } baseFontCharWidth <- openxlsxFontSizeLookupTable[[baseFontName]][baseFontSize - 7] allCharWidths <- rep(baseFontCharWidth, worksheets[[sheet]]$sheet_data$n_elements) ######### ---------------------------------------------------------------- ## get char widths for each style object if (length(styleObjects) > 0 & any(!is.na(worksheets[[sheet]]$sheet_data$style_id))) { thisSheetName <- sheet_names[sheet] ## Calc font width for all styles on this worksheet styleIds <- worksheets[[sheet]]$sheet_data$style_id styObSubet <- styleObjects[sort(unique(styleIds))] stySubset <- lapply(styObSubet, "[[", "style") ## loop through stlye objects assignin a charWidth else baseFontCharWidth styleCharWidths <- sapply(stySubset, get_style_max_char_width, USE.NAMES = FALSE) ## Now assign all cells a character width allCharWidths <- styleCharWidths[worksheets[[sheet]]$sheet_data$style_id] allCharWidths[is.na(allCharWidths)] <- baseFontCharWidth } ## Now check for columns that are auto2 auto2Inds <- which(widths %in% "auto2") if (length(auto2Inds) > 0 & length(worksheets[[sheet]]$mergeCells) > 0) { ## get cell merges merged_cells <- regmatches(worksheets[[sheet]]$mergeCells, regexpr("[A-Z0-9]+:[A-Z0-9]+", worksheets[[sheet]]$mergeCells)) comps <- lapply(merged_cells, function(rectCoords) unlist(strsplit(rectCoords, split = ":"))) merge_cols <- lapply(comps, convertFromExcelRef) merge_cols <- lapply(merge_cols, function(x) x[x %in% cols[auto2Inds]]) ## subset to auto2Inds merge_rows <- lapply(comps, function(x) as.numeric(gsub("[A-Z]", "", x, perl = TRUE))) merge_rows <- merge_rows[sapply(merge_cols, length) > 0] merge_cols <- merge_cols[sapply(merge_cols, length) > 0] sd <- worksheets[[sheet]]$sheet_data if (length(merge_cols) > 0) { all_merged_cells <- lapply(seq_along(merge_cols), function(i) { expand.grid( "rows" = min(merge_rows[[i]]):max(merge_rows[[i]]), "cols" = min(merge_cols[[i]]):max(merge_cols[[i]]) ) }) all_merged_cells <- do.call("rbind", all_merged_cells) ## only want the sheet data in here refs <- paste(all_merged_cells[[1]], all_merged_cells[[2]], sep = ",") existing_cells <- paste(worksheets[[sheet]]$sheet_data$rows, worksheets[[sheet]]$sheet_data$cols, sep = ",") keep <- which(!existing_cells %in% refs & !is.na(worksheets[[sheet]]$sheet_data$v)) sd <- Sheet_Data$new() sd$cols <- worksheets[[sheet]]$sheet_data$cols[keep] sd$t <- worksheets[[sheet]]$sheet_data$t[keep] sd$v <- worksheets[[sheet]]$sheet_data$v[keep] sd$n_elements <- length(sd$cols) allCharWidths <- allCharWidths[keep] } else { sd <- worksheets[[sheet]]$sheet_data } } else { sd <- worksheets[[sheet]]$sheet_data } ## Now that we have the max character width for the largest font on the page calculate the column widths calculatedWidths <- calc_column_widths( sheet_data = sd, sharedStrings = unlist(sharedStrings, use.names = FALSE), autoColumns = as.integer(autoCols), widths = allCharWidths, baseFontCharWidth = baseFontCharWidth, minW = openxlsx_getOp("minWidth", 3), maxW = openxlsx_getOp("maxWidth", 250) ) missingAuto <- autoCols[!autoCols %in% names(calculatedWidths)] widths[names(calculatedWidths)] <- calculatedWidths + 0.71 } widths[missingAuto] <- 9.15 } # Check if any conflicting existing levels if (any(cols %in% names(worksheets[[sheet]]$cols))) { for (i in intersect(cols, names(worksheets[[sheet]]$cols))) { width_hidden <- attr(colWidths[[sheet]], "hidden")[attr(colWidths[[sheet]], "names") == i] width_widths <- as.numeric(colWidths[[sheet]][attr(colWidths[[sheet]], "names") == i]) + 0.71 # If column already has a custom width, just update the width and hidden attributes if (grepl("customWidth", worksheets[[sheet]]$cols[[i]])) { worksheets[[sheet]]$cols[[i]] <<- sub('(width=\\").*?(\\"\\shidden=\\").*?(\\")', paste0("\\1", width_widths, "\\2", width_hidden, "\\3"), worksheets[[sheet]]$cols[[i]], perl = TRUE) } else { # If column exists, but doesn't have a custom width worksheets[[sheet]]$cols[[i]] <<- sub("((?<=hidden=\")(\\w)\")", paste0(width_hidden, "\" width=\"", width_widths, "\" customWidth=\"1\"/>"), worksheets[[sheet]]$cols[[i]], perl = TRUE) } } cols <- cols[!cols %in% names(worksheets[[sheet]]$cols)] } # Add remaining columns if (length(cols) > 0) { colNodes <- sprintf('', cols, cols, widths, hidden) names(colNodes) <- cols worksheets[[sheet]]$cols <<- append(worksheets[[sheet]]$cols, colNodes) } }) get_style_max_char_width <- function(thisStyle) { fN <- unlist(thisStyle$fontName, use.names = FALSE) if (is.null(fN)) { fN <- "calibri" } else { fN <- gsub(" ", ".", tolower(fN), fixed = TRUE) if (!fN %in% names(openxlsxFontSizeLookupTable)) { fN <- "calibri" } } fS <- unlist(thisStyle$fontSize, use.names = FALSE) if (is.null(fS)) { fS <- 11 } else { fS <- as.numeric(fS) fS <- ifelse(fS < 8, 8, ifelse(fS > 36, 36, fS)) } if ("BOLD" %in% thisStyle$fontDecoration) { styleMaxCharWidth <- openxlsxFontSizeLookupTableBold[[fN]][fS - 7] } else { styleMaxCharWidth <- openxlsxFontSizeLookupTable[[fN]][fS - 7] } return(styleMaxCharWidth) } openxlsx/R/workbook_write_data.R0000644000176200001440000002277214155600363016533 0ustar liggesusers #' @include class_definitions.R Workbook$methods(writeData = function( df, sheet, startRow, startCol, colNames, colClasses, hlinkNames, keepNA, na.string, list_sep ) { sheet <- validateSheet(sheet) nCols <- ncol(df) nRows <- nrow(df) df_nms <- names(df) allColClasses <- unlist(colClasses) isPOSIXlt <- function(data) sapply(lapply(data, class), FUN = function(x) any(x == "POSIXlt")) to_convert <- isPOSIXlt(df) if (any(to_convert)) { message("Found POSIXlt. Converting to POSIXct") df[to_convert] <- lapply(df[to_convert], as.POSIXct) } df <- as.list(df) ###################################################################### ## standardise all column types ## pull out NaN values nans <- unlist(lapply(1:nCols, function(i) { tmp <- df[[i]] if (!"character" %in% class(tmp) & !"list" %in% class(tmp)) { v <- which(is.nan(tmp) | is.infinite(tmp)) if (length(v) == 0) { return(v) } return(as.integer(nCols * (v - 1) + i)) ## row position } })) ## convert any Dates to integers and create date style object if (any(c("date", "posixct", "posixt") %in% allColClasses)) { dInds <- which(sapply(colClasses, function(x) "date" %in% x)) origin <- 25569L if (grepl('date1904="1"|date1904="true"', stri_join(unlist(workbook), collapse = ""), ignore.case = TRUE)) { origin <- 24107L } for (i in dInds) { df[[i]] <- as.integer(df[[i]]) + origin if (origin == 25569L){ earlyDate <- which(df[[i]] < 60) df[[i]][earlyDate] <- df[[i]][earlyDate] - 1 } } pInds <- which(sapply(colClasses, function(x) any(c("posixct", "posixt", "posixlt") %in% x))) if (length(pInds) > 0 & nRows > 0) { parseOffset <- function(tz) { suppressWarnings( ifelse(stri_sub(tz, 1, 1) == "+", 1L, -1L) * (as.integer(stri_sub(tz, 2, 3)) + as.integer(stri_sub(tz, 4, 5)) / 60) / 24 ) } t <- lapply(df[pInds], function(x) format(x, "%z")) offSet <- lapply(t, parseOffset) offSet <- lapply(offSet, function(x) ifelse(is.na(x), 0, x)) for (i in seq_along(pInds)) { df[[pInds[i]]] <- as.numeric(as.POSIXct(df[[pInds[i]]])) / 86400 + origin + offSet[[i]] } } } ## convert any Dates to integers and create date style object if (any(c("currency", "accounting", "percentage", "3", "comma") %in% allColClasses)) { cInds <- which(sapply(colClasses, function(x) any(c("accounting", "currency", "percentage", "3", "comma") %in% tolower(x)))) for (i in cInds) { df[[i]] <- as.numeric(gsub("[^0-9\\.-]", "", df[[i]], perl = TRUE)) } class(df[[i]]) <- "numeric" } ## convert scientific if ("scientific" %in% allColClasses) { for (i in which(sapply(colClasses, function(x) "scientific" %in% x))) { class(df[[i]]) <- "numeric" } } ## if ("list" %in% allColClasses) { for (i in which(sapply(colClasses, function(x) "list" %in% x))) { # check for and replace NA df_i <- lapply(df[[i]], unlist) df_i <- lapply(df_i, function(x) { x[is.na(x)] <- na.string x }) df[[i]] <- sapply(df_i, stri_join, collapse = list_sep) } } if ("hyperlink" %in% allColClasses) { for (i in which(sapply(colClasses, function(x) "hyperlink" %in% x))) { class(df[[i]]) <- "hyperlink" } } if (any(c("formula", "array_formula") %in% allColClasses)) { frm <- "formula" cls <- "openxlsx_formula" if ("array_formula" %in% allColClasses) { frm <- "array_formula" cls <- "openxlsx_array_formula" } for (i in which(sapply(colClasses, function(x) frm %in% x))) { df[[i]] <- replaceIllegalCharacters(as.character(df[[i]])) class(df[[i]]) <- cls } } colClasses <- sapply(df, function(x) tolower(class(x))[[1]]) ## by here all cols must have a single class only ## convert logicals (Excel stores logicals as 0 & 1) if ("logical" %in% allColClasses) { for (i in which(sapply(colClasses, function(x) "logical" %in% x))) { class(df[[i]]) <- "numeric" } } ## convert all numerics to character (this way preserves digits) if ("numeric" %in% colClasses) { for (i in which(sapply(colClasses, function(x) "numeric" %in% x))) { class(df[[i]]) <- "character" } } ## End standardise all column types ###################################################################### ## cell types t <- build_cell_types_integer(classes = colClasses, n_rows = nRows) for (i in which(sapply(colClasses, function(x) !"character" %in% x & !"numeric" %in% x))) { df[[i]] <- as.character(df[[i]]) } ## cell values v <- as.character(t(as.matrix( data.frame(df, stringsAsFactors = FALSE, check.names = FALSE, fix.empty.names = FALSE) ))) if (keepNA) { if (is.null(na.string)) { t[is.na(v)] <- 4L v[is.na(v)] <- "#N/A" } else { t[is.na(v)] <- 1L v[is.na(v)] <- as.character(na.string) } } else { t[is.na(v)] <- as.integer(NA) v[is.na(v)] <- as.character(NA) } ## If any NaN values if (length(nans) > 0) { t[nans] <- 4L v[nans] <- "#NUM!" } # prepend column headers if (colNames) { t <- c(rep.int(1L, nCols), t) v <- c(df_nms, v) nRows <- nRows + 1L } ## Formulas f_in <- rep.int(as.character(NA), length(t)) any_functions <- FALSE ref_cell <- paste0(int_2_cell_ref(startCol), startRow) if (any(c("openxlsx_formula", "openxlsx_array_formula") %in% colClasses)) { ## alter the elements of t where we have a formula to be "str" if ("openxlsx_formula" %in% colClasses) { formula_cols <- which(sapply(colClasses, function(x) "openxlsx_formula" %in% x, USE.NAMES = FALSE), useNames = FALSE) formula_strs <- stri_join("", unlist(df[formula_cols], use.names = FALSE), "") } else { # openxlsx_array_formula formula_cols <- which(sapply(colClasses, function(x) "openxlsx_array_formula" %in% x, USE.NAMES = FALSE), useNames = FALSE) formula_strs <- stri_join("", unlist(df[formula_cols], use.names = FALSE), "") } formula_inds <- unlist(lapply(formula_cols, function(i) i + (1:(nRows - colNames) - 1) * nCols + (colNames * nCols)), use.names = FALSE) f_in[formula_inds] <- formula_strs any_functions <- TRUE rm(formula_cols) rm(formula_strs) rm(formula_inds) } suppressWarnings(try(rm(df), silent = TRUE)) ## Append hyperlinks, convert h to s in cell type hyperlink_cols <- which(sapply(colClasses, function(x) "hyperlink" %in% x, USE.NAMES = FALSE), useNames = FALSE) if (length(hyperlink_cols) > 0) { hyperlink_inds <- sort(unlist(lapply(hyperlink_cols, function(i) i + (1:(nRows - colNames) - 1) * nCols + (colNames * nCols)), use.names = FALSE)) na_hyperlink <- intersect(hyperlink_inds, which(is.na(t))) if (length(hyperlink_inds) > 0) { t[t %in% 9] <- 1L ## set cell type to "s" hyperlink_refs <- convert_to_excel_ref_expand(cols = hyperlink_cols + startCol - 1, LETTERS = LETTERS, rows = as.character((startRow + colNames):(startRow + nRows - 1L))) if (length(na_hyperlink) > 0) { to_remove <- which(hyperlink_inds %in% na_hyperlink) hyperlink_refs <- hyperlink_refs[-to_remove] hyperlink_inds <- hyperlink_inds[-to_remove] } exHlinks <- worksheets[[sheet]]$hyperlinks targets <- replaceIllegalCharacters(v[hyperlink_inds]) if (!is.null(hlinkNames) & length(hlinkNames) == length(hyperlink_inds)) { v[hyperlink_inds] <- hlinkNames } ## this is text to display instead of hyperlink ## create hyperlink objects newhl <- lapply(seq_along(hyperlink_inds), function(i) { Hyperlink$new(ref = hyperlink_refs[i], target = targets[i], location = NULL, display = NULL, is_external = TRUE) }) worksheets[[sheet]]$hyperlinks <<- append(worksheets[[sheet]]$hyperlinks, newhl) } } ## convert all strings to references in sharedStrings and update values (v) strFlag <- which(t == 1L) newStrs <- v[strFlag] if (length(newStrs) > 0) { newStrs <- replaceIllegalCharacters(newStrs) vl <- stri_length(newStrs) for (i in which(vl > 32767)) { if(vl[i]>32768+30){ warning( paste0( stri_sub(newStrs[i], 32768, 32768 + 15), " ... " , stri_sub(newStrs[i], vl[i] - 15, vl[i]), " is truncated. Number of characters exeed the limit of 32767." ) ) } else { warning( paste0( stri_sub(newStrs[i], 32768, -1), " is truncated. Number of characters exeed the limit of 32767." ) ) } # v[i] <- stri_sub(v[i], 1, 32767) } newStrs <- stri_join("", newStrs, "") uNewStr <- unique(newStrs) .self$updateSharedStrings(uNewStr) v[strFlag] <- match(newStrs, sharedStrings) - 1L } # ## Create cell list of lists worksheets[[sheet]]$sheet_data$write( rows_in = startRow:(startRow + nRows - 1L), cols_in = startCol:(startCol + nCols - 1L), t_in = t, v_in = v, f_in = f_in, any_functions = any_functions ) invisible(0) }) openxlsx/R/conditional_formatting.R0000644000176200001440000005117414155600363017226 0ustar liggesusers #' @name conditionalFormatting #' @aliases databar #' @title Add conditional formatting to cells #' @description Add conditional formatting to cells #' @author Alexander Walker, Philipp Schauberger #' @param wb A workbook object #' @param sheet A name or index of a worksheet #' @param cols Columns to apply conditional formatting to #' @param rows Rows to apply conditional formatting to #' @param rule The condition under which to apply the formatting. See examples. #' @param style A style to apply to those cells that satisfy the rule. Default is createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") #' @param type Either 'expression', 'colourScale', 'databar', 'duplicates', 'beginsWith', #' 'endsWith', 'topN', 'bottomN', 'contains' or 'notContains' (case insensitive). #' @param ... See below #' @details See Examples. #' #' If type == "expression" #' \itemize{ #' \item{style is a Style object. See [createStyle()]} #' \item{rule is an expression. Valid operators are "<", "<=", ">", ">=", "==", "!=".} #' } #' #' If type == "colourScale" #' \itemize{ #' \item{style is a vector of colours with length 2 or 3} #' \item{rule can be NULL or a vector of colours of equal length to styles} #' } #' #' If type == "databar" #' \itemize{ #' \item{style is a vector of colours with length 2 or 3} #' \item{rule is a numeric vector specifying the range of the databar colours. Must be equal length to style} #' \item{... #' \itemize{ #' \item{**showvalue** If FALSE the cell value is hidden. Default TRUE.} #' \item{**gradient** If FALSE colour gradient is removed. Default TRUE.} #' \item{**border** If FALSE the border around the database is hidden. Default TRUE.} #' } #' } #' } #' #' If type == "duplicates" #' \itemize{ #' \item{style is a Style object. See [createStyle()]} #' \item{rule is ignored.} #' } #' #' If type == "contains" #' \itemize{ #' \item{style is a Style object. See [createStyle()]} #' \item{rule is the text to look for within cells} #' } #' #' If type == "between" #' \itemize{ #' \item{style is a Style object. See [createStyle()]} #' \item{rule is a numeric vector of length 2 specifying lower and upper bound (Inclusive)} #' } #' #' If type == "topN" #' \itemize{ #' \item{style is a Style object. See [createStyle()]} #' \item{rule is ignored} #' \item{... #' \itemize{ #' \item{**rank** numeric vector of length 1 indicating number of highest values.} #' \item{**percent** TRUE if you want top N percentage.} #' } #' } #' } #' #' If type == "bottomN" #' \itemize{ #' \item{style is a Style object. See [createStyle()]} #' \item{rule is ignored} #' \item{... #' \itemize{ #' \item{**rank** numeric vector of length 1 indicating number of lowest values.} #' \item{**percent** TRUE if you want bottom N percentage.} #' } #' } #' } #' #' @seealso [createStyle()] #' @export #' @examples #' wb <- createWorkbook() #' addWorksheet(wb, "cellIs") #' addWorksheet(wb, "Moving Row") #' addWorksheet(wb, "Moving Col") #' addWorksheet(wb, "Dependent on") #' addWorksheet(wb, "Duplicates") #' addWorksheet(wb, "containsText") #' addWorksheet(wb, "notcontainsText") #' addWorksheet(wb, "beginsWith") #' addWorksheet(wb, "endsWith") #' addWorksheet(wb, "colourScale", zoom = 30) #' addWorksheet(wb, "databar") #' addWorksheet(wb, "between") #' addWorksheet(wb, "topN") #' addWorksheet(wb, "bottomN") #' addWorksheet(wb, "logical operators") #' #' negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") #' posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE") #' #' ## rule applies to all each cell in range #' writeData(wb, "cellIs", -5:5) #' writeData(wb, "cellIs", LETTERS[1:11], startCol = 2) #' conditionalFormatting(wb, "cellIs", #' cols = 1, #' rows = 1:11, rule = "!=0", style = negStyle #' ) #' conditionalFormatting(wb, "cellIs", #' cols = 1, #' rows = 1:11, rule = "==0", style = posStyle #' ) #' #' ## highlight row dependent on first cell in row #' writeData(wb, "Moving Row", -5:5) #' writeData(wb, "Moving Row", LETTERS[1:11], startCol = 2) #' conditionalFormatting(wb, "Moving Row", #' cols = 1:2, #' rows = 1:11, rule = "$A1<0", style = negStyle #' ) #' conditionalFormatting(wb, "Moving Row", #' cols = 1:2, #' rows = 1:11, rule = "$A1>0", style = posStyle #' ) #' #' ## highlight column dependent on first cell in column #' writeData(wb, "Moving Col", -5:5) #' writeData(wb, "Moving Col", LETTERS[1:11], startCol = 2) #' conditionalFormatting(wb, "Moving Col", #' cols = 1:2, #' rows = 1:11, rule = "A$1<0", style = negStyle #' ) #' conditionalFormatting(wb, "Moving Col", #' cols = 1:2, #' rows = 1:11, rule = "A$1>0", style = posStyle #' ) #' #' ## highlight entire range cols X rows dependent only on cell A1 #' writeData(wb, "Dependent on", -5:5) #' writeData(wb, "Dependent on", LETTERS[1:11], startCol = 2) #' conditionalFormatting(wb, "Dependent on", #' cols = 1:2, #' rows = 1:11, rule = "$A$1<0", style = negStyle #' ) #' conditionalFormatting(wb, "Dependent on", #' cols = 1:2, #' rows = 1:11, rule = "$A$1>0", style = posStyle #' ) #' #' ## highlight cells in column 1 based on value in column 2 #' writeData(wb, "Dependent on", data.frame(x = 1:10, y = runif(10)), startRow = 15) #' conditionalFormatting(wb, "Dependent on", #' cols = 1, #' rows = 16:25, rule = "B16<0.5", style = negStyle #' ) #' conditionalFormatting(wb, "Dependent on", #' cols = 1, #' rows = 16:25, rule = "B16>=0.5", style = posStyle #' ) #' #' #' ## highlight duplicates using default style #' writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE)) #' conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates") #' #' ## cells containing text #' fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") #' writeData(wb, "containsText", sapply(1:10, fn)) #' conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains", rule = "A") #' #' ## cells not containing text #' fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") #' writeData(wb, "containsText", sapply(1:10, fn)) #' conditionalFormatting(wb, "notcontainsText", cols = 1, #' rows = 1:10, type = "notcontains", rule = "A") #' #' #' ## cells begins with text #' fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") #' writeData(wb, "beginsWith", sapply(1:100, fn)) #' conditionalFormatting(wb, "beginsWith", cols = 1, rows = 1:100, type = "beginsWith", rule = "A") #' #' #' ## cells ends with text #' fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") #' writeData(wb, "endsWith", sapply(1:100, fn)) #' conditionalFormatting(wb, "endsWith", cols = 1, rows = 1:100, type = "endsWith", rule = "A") #' #' ## colourscale colours cells based on cell value #' df <- read.xlsx(system.file("extdata", "readTest.xlsx", package = "openxlsx"), sheet = 4) #' writeData(wb, "colourScale", df, colNames = FALSE) ## write data.frame #' #' ## rule is a vector or colours of length 2 or 3 (any hex colour or any of colours()) #' ## If rule is NULL, min and max of cells is used. Rule must be the same length as style or NULL. #' conditionalFormatting(wb, "colourScale", #' cols = 1:ncol(df), rows = 1:nrow(df), #' style = c("black", "white"), #' rule = c(0, 255), #' type = "colourScale" #' ) #' #' setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07) #' setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5) #' #' ## Databars #' writeData(wb, "databar", -5:5) #' conditionalFormatting(wb, "databar", cols = 1, rows = 1:11, type = "databar") ## Default colours #' #' ## Between #' # Highlight cells in interval [-2, 2] #' writeData(wb, "between", -5:5) #' conditionalFormatting(wb, "between", cols = 1, rows = 1:11, type = "between", rule = c(-2, 2)) #' #' ## Top N #' writeData(wb, "topN", data.frame(x = 1:10, y = rnorm(10))) #' # Highlight top 5 values in column x #' conditionalFormatting(wb, "topN", cols = 1, rows = 2:11, #' style = posStyle, type = "topN", rank = 5)#' #' # Highlight top 20 percentage in column y #' conditionalFormatting(wb, "topN", cols = 2, rows = 2:11, #' style = posStyle, type = "topN", rank = 20, percent = TRUE) #' #'## Bottom N #' writeData(wb, "bottomN", data.frame(x = 1:10, y = rnorm(10))) #' # Highlight bottom 5 values in column x #' conditionalFormatting(wb, "bottomN", cols = 1, rows = 2:11, #' style = negStyle, type = "topN", rank = 5) #' # Highlight bottom 20 percentage in column y #' conditionalFormatting(wb, "bottomN", cols = 2, rows = 2:11, #' style = negStyle, type = "topN", rank = 20, percent = TRUE) #' #' ## Logical Operators #' # You can use Excels logical Operators #' writeData(wb, "logical operators", 1:10) #' conditionalFormatting(wb, "logical operators", #' cols = 1, rows = 1:10, #' rule = "OR($A1=1,$A1=3,$A1=5,$A1=7)" #' ) #' \dontrun{ #' saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE) #' } #' #' #' ######################################################################### #' ## Databar Example #' #' wb <- createWorkbook() #' addWorksheet(wb, "databar") #' #' ## Databars #' writeData(wb, "databar", -5:5, startCol = 1) #' conditionalFormatting(wb, "databar", cols = 1, rows = 1:11, type = "databar") ## Defaults #' #' writeData(wb, "databar", -5:5, startCol = 3) #' conditionalFormatting(wb, "databar", cols = 3, rows = 1:11, type = "databar", border = FALSE) #' #' writeData(wb, "databar", -5:5, startCol = 5) #' conditionalFormatting(wb, "databar", #' cols = 5, rows = 1:11, #' type = "databar", style = c("#a6a6a6"), showValue = FALSE #' ) #' #' writeData(wb, "databar", -5:5, startCol = 7) #' conditionalFormatting(wb, "databar", #' cols = 7, rows = 1:11, #' type = "databar", style = c("#a6a6a6"), showValue = FALSE, gradient = FALSE #' ) #' #' writeData(wb, "databar", -5:5, startCol = 9) #' conditionalFormatting(wb, "databar", #' cols = 9, rows = 1:11, #' type = "databar", style = c("#a6a6a6", "#a6a6a6"), showValue = FALSE, gradient = FALSE #' ) #' \dontrun{ #' saveWorkbook(wb, file = "databarExample.xlsx", overwrite = TRUE) #' } #' conditionalFormatting <- function(wb, sheet, cols, rows, rule = NULL, style = NULL, type = "expression", ...) { op <- get_set_options() on.exit(options(op), add = TRUE) type <- tolower(type) params <- list(...) if (type %in% c("colorscale", "colourscale")) { type <- "colorScale" } else if (type == "databar") { type <- "dataBar" } else if (type == "duplicates") { type <- "duplicatedValues" } else if (type == "contains") { type <- "containsText" } else if (type == "notcontains") { type <- "notContainsText" } else if (type == "beginswith") { type <- "beginsWith" } else if (type == "endswith") { type <- "endsWith" } else if (type == "between") { type <- "between" } else if (type == "topn") { type <- "topN" } else if (type == "bottomn") { type <- "bottomN" } else if (type != "expression") { stop( "Invalid type argument. Type must be one of 'expression', 'colourScale', 'databar', 'duplicates', 'beginsWith', 'endsWith', 'contains' or 'notContains'" ) } ## rows and cols if (!is.numeric(cols)) { cols <- convertFromExcelRef(cols) } rows <- as.integer(rows) ## check valid rule values <- NULL dxfId <- NULL if (type == "colorScale") { # type == "colourScale" # - style is a vector of colours with length 2 or 3 # - rule specifies the quantiles (numeric vector of length 2 or 3), if NULL min and max are used if (is.null(style)) { stop("If type == 'colourScale', style must be a vector of colours of length 2 or 3.") } if (class(style) != "character") { stop("If type == 'colourScale', style must be a vector of colours of length 2 or 3.") } if (!length(style) %in% 2:3) { stop("If type == 'colourScale', style must be a vector of length 2 or 3.") } if (!is.null(rule)) { if (length(rule) != length(style)) { stop("If type == 'colourScale', rule and style must have equal lengths.") } } style <- validateColour(style, errorMsg = "Invalid colour specified in style.") values <- rule rule <- style } else if (type == "dataBar") { # type == "databar" # - style is a vector of colours of length 2 or 3 # - rule specifies the quantiles (numeric vector of length 2 or 3), if NULL min and max are used if (is.null(style)) { style <- "#638EC6" } if (class(style) != "character") { stop("If type == 'dataBar', style must be a vector of colours of length 1 or 2.") } if (!length(style) %in% 1:2) { stop("If type == 'dataBar', style must be a vector of length 1 or 2.") } if (!is.null(rule)) { if (length(rule) != length(style)) { stop("If type == 'dataBar', rule and style must have equal lengths.") } } ## Additional parameters passed by ... if ("showValue" %in% names(params)) { params$showValue <- as.integer(params$showValue) if (is.na(params$showValue)) { stop("showValue must be 0/1 or TRUE/FALSE") } } if ("gradient" %in% names(params)) { params$gradient <- as.integer(params$gradient) if (is.na(params$gradient)) { stop("gradient must be 0/1 or TRUE/FALSE") } } if ("border" %in% names(params)) { params$border <- as.integer(params$border) if (is.na(params$border)) { stop("border must be 0/1 or TRUE/FALSE") } } style <- validateColour(style, errorMsg = "Invalid colour specified in style.") values <- rule rule <- style } else if (type == "expression") { # type == "expression" # - style = createStyle() # - rule is an expression to evaluate # rule <- gsub(" ", "", rule) rule <- replaceIllegalCharacters(rule) rule <- gsub("!=", "<>", rule) rule <- gsub("==", "=", rule) if (!grepl("[A-Z]", substr(rule, 1, 2))) { ## formula looks like "operatorX" , attach top left cell to rule rule <- paste0(getCellRefs(data.frame( "x" = min(rows), "y" = min(cols) )), rule) } ## else, there is a letter in the formula and apply as is if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"Style" %in% class(style)) { stop("If type == 'expression', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) } else if (type == "duplicatedValues") { # type == "duplicatedValues" # - style is a Style object # - rule is ignored if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"Style" %in% class(style)) { stop("If type == 'duplicates', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) rule <- style } else if (type == "containsText") { # type == "contains" # - style is Style object # - rule is text to look for if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"character" %in% class(rule)) { stop("If type == 'contains', rule must be a character vector of length 1.") } if (!"Style" %in% class(style)) { stop("If type == 'contains', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) values <- rule rule <- style } else if (type == "notContainsText") { # type == "contains" # - style is Style object # - rule is text to look for if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"character" %in% class(rule)) { stop("If type == 'notContains', rule must be a character vector of length 1.") } if (!"Style" %in% class(style)) { stop("If type == 'notContains', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) values <- rule rule <- style } else if (type == "beginsWith") { # type == "contains" # - style is Style object # - rule is text to look for if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"character" %in% class(rule)) { stop("If type == 'beginsWith', rule must be a character vector of length 1.") } if (!"Style" %in% class(style)) { stop("If type == 'beginsWith', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) values <- rule rule <- style } else if (type == "endsWith") { # type == "contains" # - style is Style object # - rule is text to look for if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"character" %in% class(rule)) { stop("If type == 'endsWith', rule must be a character vector of length 1.") } if (!"Style" %in% class(style)) { stop("If type == 'endsWith', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) values <- rule rule <- style } else if (type == "between") { rule <- range(rule) if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"Style" %in% class(style)) { stop("If type == 'between', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) } else if (type == "topN") { # type == "topN" # - rule is ignored # - 'rank' and 'percent' are named params if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"Style" %in% class(style)) { stop("If type == 'topN', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) ## Additional parameters passed by ... if ("percent" %in% names(params)) { params$percent <- as.integer(params$percent) if (is.na(params$percent)) { stop("percent must be 0/1 or TRUE/FALSE") } } if ("rank" %in% names(params)) { params$rank <- as.integer(params$rank) if (is.na(params$rank)) { stop("rank must be a number") } } invisible(dxfId <- wb$addDXFS(style)) values <- params rule <- style } else if (type == "bottomN") { # type == "bottomN" # - rule is ignored # - 'rank' and 'percent' are named params if (is.null(style)) { style <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } if (!"Style" %in% class(style)) { stop("If type == 'bottomN', style must be a Style object.") } invisible(dxfId <- wb$addDXFS(style)) ## Additional parameters passed by ... if ("percent" %in% names(params)) { params$percent <- as.integer(params$percent) if (is.na(params$percent)) { stop("percent must be 0/1 or TRUE/FALSE") } } if ("rank" %in% names(params)) { params$rank <- as.integer(params$rank) if (is.na(params$rank)) { stop("rank must be a number") } } invisible(dxfId <- wb$addDXFS(style)) values <- params rule <- style } invisible( wb$conditionalFormatting( sheet, startRow = min(rows), endRow = max(rows), startCol = min(cols), endCol = max(cols), dxfId = dxfId, formula = rule, type = type, values = values, params = params ) ) invisible(0) } openxlsx/R/loadWorkbook.R0000644000176200001440000012175214155600363015126 0ustar liggesusers #' @name loadWorkbook #' @title Load an existing .xlsx file #' @author Alexander Walker, Philipp Schauberger #' @param file A path to an existing .xlsx or .xlsm file #' @param xlsxFile alias for file #' @param isUnzipped Set to TRUE if the xlsx file is already unzipped #' @description loadWorkbook returns a workbook object conserving styles and #' formatting of the original .xlsx file. #' @return Workbook object. #' @export #' @seealso [removeWorksheet()] #' @examples #' ## load existing workbook from package folder #' wb <- loadWorkbook(file = system.file("extdata", "loadExample.xlsx", package = "openxlsx")) #' names(wb) # list worksheets #' wb ## view object #' ## Add a worksheet #' addWorksheet(wb, "A new worksheet") #' #' ## Save workbook #' \dontrun{ #' saveWorkbook(wb, "loadExample.xlsx", overwrite = TRUE) #' } #' loadWorkbook <- function(file, xlsxFile = NULL, isUnzipped = FALSE) { ## If this is a unzipped workbook, skip the temp dir stuff if (isUnzipped) { xmlDir <- file xmlFiles <- list.files(path = xmlDir, full.names = TRUE, recursive = TRUE, all.files = TRUE) } else { if (!is.null(xlsxFile)) { file <- xlsxFile } file <- getFile(file) if (!file.exists(file)) { stop("File does not exist.") } ## create temp dir xmlDir <- tempfile() ## Unzip files to temp directory xmlFiles <- unzip(file, exdir = xmlDir) } wb <- createWorkbook() ## Not used # .relsXML <- xmlFiles[grepl("_rels/.rels$", xmlFiles, perl = TRUE)] # appXML <- xmlFiles[grepl("app.xml$", xmlFiles, perl = TRUE)] drawingsXML <- grep("drawings/drawing[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) worksheetsXML <- grep("/worksheets/sheet[0-9]+", xmlFiles, perl = TRUE, value = TRUE) coreXML <- grep("core.xml$", xmlFiles, perl = TRUE, value = TRUE) workbookXML <- grep("workbook.xml$", xmlFiles, perl = TRUE, value = TRUE) stylesXML <- grep("styles.xml$", xmlFiles, perl = TRUE, value = TRUE) sharedStringsXML <- grep("sharedStrings.xml$", xmlFiles, perl = TRUE, value = TRUE) themeXML <- grep("theme[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) drawingRelsXML <- grep("drawing[0-9]+.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) sheetRelsXML <- grep("sheet[0-9]+.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) media <- grep("image[0-9]+.[a-z]+$", xmlFiles, perl = TRUE, value = TRUE) vmlDrawingXML <- grep("drawings/vmlDrawing[0-9]+\\.vml$", xmlFiles, perl = TRUE, value = TRUE) vmlDrawingRelsXML <- grep("vmlDrawing[0-9]+.vml.rels$", xmlFiles, perl = TRUE, value = TRUE) commentsXML <- grep("xl/comments[0-9]+\\.xml", xmlFiles, perl = TRUE, value = TRUE) threadCommentsXML <- grep("xl/threadedComments/threadedComment[0-9]+\\.xml", xmlFiles, perl = TRUE, value = TRUE) personXML <- grep("xl/persons/person.xml$", xmlFiles, perl = TRUE, value = TRUE) embeddings <- grep("xl/embeddings", xmlFiles, perl = TRUE, value = TRUE) charts <- grep("xl/charts/.*xml$", xmlFiles, perl = TRUE, value = TRUE) chartsRels <- grep("xl/charts/_rels", xmlFiles, perl = TRUE, value = TRUE) chartSheetsXML <- grep("xl/chartsheets/sheet[0-9]+\\.xml", xmlFiles, perl = TRUE, value = TRUE) tablesXML <- grep("tables/table[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) tableRelsXML <- grep("table[0-9]+.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) queryTablesXML <- grep("queryTable[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) connectionsXML <- grep("connections.xml$", xmlFiles, perl = TRUE, value = TRUE) extLinksXML <- grep("externalLink[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) extLinksRelsXML <- grep("externalLink[0-9]+.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) # pivot tables pivotTableXML <- grep("pivotTable[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) pivotTableRelsXML <- grep("pivotTable[0-9]+.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) pivotDefXML <- grep("pivotCacheDefinition[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) pivotDefRelsXML <- grep("pivotCacheDefinition[0-9]+.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) pivotCacheRecords <- grep("pivotCacheRecords[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) ## slicers slicerXML <- grep("slicer[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) slicerCachesXML <- grep("slicerCache[0-9]+.xml$", xmlFiles, perl = TRUE, value = TRUE) ## VBA Macro vbaProject <- grep("vbaProject\\.bin$", xmlFiles, perl = TRUE, value = TRUE) ## remove all EXCEPT media and charts if (!isUnzipped) { on.exit({ paths <- grep( "charts|media|vmlDrawing|comment|embeddings|pivot|slicer|vbaProject|person", xmlFiles, ignore.case = TRUE, value = TRUE, invert = TRUE ) unlink(paths, recursive = TRUE, force = TRUE) }, add = TRUE ) } ## core if (length(coreXML) == 1) { coreXML <- paste(readUTF8(coreXML), collapse = "") wb$core <- removeHeadTag(x = coreXML) } nSheets <- length(worksheetsXML) + length(chartSheetsXML) ## get Rid of chartsheets, these do not have a worksheet/sheeti.xml worksheet_rId_mapping <- NULL workbookRelsXML <- grep("workbook.xml.rels$", xmlFiles, perl = TRUE, value = TRUE) if (length(workbookRelsXML) > 0) { workbookRelsXML <- paste(readUTF8(workbookRelsXML), collapse = "") workbookRelsXML <- getChildlessNode(xml = workbookRelsXML, tag = "Relationship") worksheet_rId_mapping <- grep("worksheets/sheet", workbookRelsXML, fixed = TRUE, value = TRUE) } ## chartSheetRIds <- NULL if (length(chartSheetsXML) > 0) { workbookRelsXML <- grep("chartsheets/sheet", workbookRelsXML, fixed = TRUE, value = TRUE) chartSheetRIds <- unlist(getId(workbookRelsXML)) chartsheet_rId_mapping <- unlist(regmatches(workbookRelsXML, gregexpr("sheet[0-9]+\\.xml", workbookRelsXML, perl = TRUE, ignore.case = TRUE))) sheetNo <- as.integer(regmatches(chartSheetsXML, regexpr("(?<=sheet)[0-9]+(?=\\.xml)", chartSheetsXML, perl = TRUE))) chartSheetsXML <- chartSheetsXML[order(sheetNo)] chartSheetsRelsXML <- grep("xl/chartsheets/_rels", xmlFiles, perl = TRUE, value = TRUE) sheetNo2 <- as.integer(regmatches(chartSheetsRelsXML, regexpr("(?<=sheet)[0-9]+(?=\\.xml\\.rels)", chartSheetsRelsXML, perl = TRUE))) chartSheetsRelsXML <- chartSheetsRelsXML[order(sheetNo2)] chartSheetsRelsDir <- dirname(chartSheetsRelsXML[1]) } ## xl\ ## xl\workbook if (length(workbookXML) > 0) { workbook <- readUTF8(workbookXML) workbook <- removeHeadTag(workbook) sheets <- unlist(regmatches(workbook, gregexpr("(?<=).*(?=)", workbook, perl = TRUE))) sheets <- unlist(regmatches(sheets, gregexpr("]*>", sheets, perl = TRUE))) ## Some veryHidden sheets do not have a sheet content and their rId is empty. ## Such sheets need to be filtered out because otherwise their sheet names ## occur in the list of all sheet names, leading to a wrong association ## of sheet names with sheet indeces. sheets <- grep('r:id="[[:blank:]]*"', sheets, invert = TRUE, value = TRUE) ## sheetId is meaningless ## sheet rId links to the workbook.xml.resl which links worksheets/sheet(i).xml file ## order they appear here gives order of worksheets in xlsx file sheetrId <- unlist(getRId(sheets)) sheetId <- unlist(regmatches(sheets, gregexpr('(?<=sheetId=")[0-9]+', sheets, perl = TRUE))) sheetNames <- unlist(regmatches(sheets, gregexpr('(?<=name=")[^"]+', sheets, perl = TRUE))) sheetNames <- replaceXMLEntities(sheetNames) is_chart_sheet <- sheetrId %in% chartSheetRIds is_visible <- !grepl("hidden", sheets) if (length(is_visible) != length(sheetrId)) { is_visible <- rep(TRUE, length(sheetrId)) } # #active sheet ----------------------------------------------------------- ## add worksheets to wb j <- 1 for (i in seq_along(sheetrId)) { if (is_chart_sheet[i]) { # count <- 0 variable not used txt <- paste(readUTF8(chartSheetsXML[j]), collapse = "") zoom <- regmatches(txt, regexpr('(?<=zoomScale=")[0-9]+', txt, perl = TRUE)) if (length(zoom) == 0) { zoom <- 100 } tabColour <- getChildlessNode(xml = txt, tag = "tabColor") if (length(tabColour) == 0) { tabColour <- NULL } j <- j + 1L wb$addChartSheet(sheetName = sheetNames[i], tabColour = tabColour, zoom = as.numeric(zoom)) } else { wb$addWorksheet(sheetNames[i], visible = is_visible[i]) } } ## replace sheetId for (i in 1:nSheets) { wb$workbook$sheets[[i]] <- gsub(sprintf(' sheetId="%s"', i), sprintf(' sheetId="%s"', sheetId[i]), wb$workbook$sheets[[i]]) } ## additional workbook attributes calcPr <- getChildlessNode(xml = workbook, tag = "calcPr") if (length(calcPr) > 0) { wb$workbook$calcPr <- calcPr } ## additional workbook attributes extLst <- getChildlessNode(xml = workbook, tag = "extLst") if (length(extLst) > 0) { wb$workbook$extLst <- extLst } workbookPr <- getChildlessNode(xml = workbook, tag = "workbookPr") if (length(workbookPr) > 0) { wb$workbook$workbookPr <- workbookPr } workbookProtection <- getChildlessNode(xml = workbook, tag = "workbookProtection") if (length(workbookProtection) > 0) { wb$workbook$workbookProtection <- workbookProtection } ## defined Names dNames <- getNodes(xml = workbook, tagIn = "") if (length(dNames) > 0) { dNames <- gsub("^|$", "", dNames) wb$workbook$definedNames <- paste0(getNodes(xml = dNames, tagIn = "") } } ## xl\sharedStrings if (length(sharedStringsXML) > 0) { sharedStrings <- readUTF8(sharedStringsXML) sharedStrings <- paste(sharedStrings, collapse = "\n") sharedStrings <- removeHeadTag(sharedStrings) uniqueCount <- as.integer(regmatches(sharedStrings, regexpr('(?<=uniqueCount=")[0-9]+', sharedStrings, perl = TRUE))) ## read in and get nodes vals <- getNodes(xml = sharedStrings, tagIn = "") if ("" %in% vals) { vals[vals == ""] <- "NA" Encoding(vals) <- "UTF-8" attr(vals, "uniqueCount") <- uniqueCount - 1L } else { Encoding(vals) <- "UTF-8" attr(vals, "uniqueCount") <- uniqueCount } wb$sharedStrings <- vals } ## xl\pivotTables & xl\pivotCache if (length(pivotTableXML) > 0) { # pivotTable cacheId links to workbook.xml which links to workbook.xml.rels via rId # we don't modify the cacheId, only the rId nPivotTables <- length(pivotDefXML) rIds <- 20000L + 1:nPivotTables ## pivot tables pivotTableXML <- pivotTableXML[order(nchar(pivotTableXML), pivotTableXML)] pivotTableRelsXML <- pivotTableRelsXML[order(nchar(pivotTableRelsXML), pivotTableRelsXML)] ## Cache pivotDefXML <- pivotDefXML[order(nchar(pivotDefXML), pivotDefXML)] pivotDefRelsXML <- pivotDefRelsXML[order(nchar(pivotDefRelsXML), pivotDefRelsXML)] pivotCacheRecords <- pivotCacheRecords[order(nchar(pivotCacheRecords), pivotCacheRecords)] wb$pivotDefinitionsRels <- character(nPivotTables) pivot_content_type <- NULL if (length(pivotTableRelsXML) > 0) { wb$pivotTables.xml.rels <- unlist(lapply(pivotTableRelsXML, function(x) removeHeadTag(cppReadFile(x)))) } # ## Check what caches are used cache_keep <- unlist(regmatches(wb$pivotTables.xml.rels, gregexpr("(?<=pivotCache/pivotCacheDefinition)[0-9](?=\\.xml)", wb$pivotTables.xml.rels, perl = TRUE, ignore.case = TRUE ))) ## pivot cache records tmp <- unlist(regmatches(pivotCacheRecords, gregexpr("(?<=pivotCache/pivotCacheRecords)[0-9]+(?=\\.xml)", pivotCacheRecords, perl = TRUE, ignore.case = TRUE))) pivotCacheRecords <- pivotCacheRecords[tmp %in% cache_keep] ## pivot cache definitions rels tmp <- unlist(regmatches(pivotDefRelsXML, gregexpr("(?<=_rels/pivotCacheDefinition)[0-9]+(?=\\.xml)", pivotDefRelsXML, perl = TRUE, ignore.case = TRUE))) pivotDefRelsXML <- pivotDefRelsXML[tmp %in% cache_keep] ## pivot cache definitions tmp <- unlist(regmatches(pivotDefXML, gregexpr("(?<=pivotCache/pivotCacheDefinition)[0-9]+(?=\\.xml)", pivotDefXML, perl = TRUE, ignore.case = TRUE))) pivotDefXML <- pivotDefXML[tmp %in% cache_keep] if (length(pivotTableXML) > 0) { wb$pivotTables[seq_along(pivotTableXML)] <- pivotTableXML pivot_content_type <- c( pivot_content_type, sprintf('', seq_along(pivotTableXML)) ) } if (length(pivotDefXML) > 0) { wb$pivotDefinitions[seq_along(pivotDefXML)] <- pivotDefXML pivot_content_type <- c( pivot_content_type, sprintf('', seq_along(pivotDefXML)) ) } if (length(pivotCacheRecords) > 0) { wb$pivotRecords[seq_along(pivotCacheRecords)] <- pivotCacheRecords pivot_content_type <- c( pivot_content_type, sprintf('', seq_along(pivotCacheRecords)) ) } if (length(pivotDefRelsXML) > 0) { wb$pivotDefinitionsRels[seq_along(pivotDefRelsXML)] <- pivotDefRelsXML } ## update content_types wb$Content_Types <- c(wb$Content_Types, pivot_content_type) ## workbook rels wb$workbook.xml.rels <- c( wb$workbook.xml.rels, sprintf('', rIds, seq_along(pivotDefXML)) ) caches <- getNodes(xml = workbook, tagIn = "") caches <- getChildlessNode(xml = caches, tag = "pivotCache") for (i in seq_along(caches)) { caches[i] <- gsub('"rId[0-9]+"', sprintf('"rId%s"', rIds[i]), caches[i]) } wb$workbook$pivotCaches <- paste0("", paste(caches, collapse = ""), "") } ## xl\vbaProject if (length(vbaProject) > 0) { wb$vbaProject <- vbaProject wb$Content_Types[grepl('' wb$Content_Types <- c(wb$Content_Types, '') } ## xl\styles if (length(stylesXML) > 0) { styleObjects <- wb$loadStyles(stylesXML) } else { styleObjects <- list() } ## xl\media if (length(media) > 0) { mediaNames <- regmatches(media, regexpr("image[0-9]+\\.[a-z]+$", media)) fileTypes <- unique(gsub("image[0-9]+\\.", "", mediaNames)) contentNodes <- sprintf('', fileTypes, fileTypes) contentNodes[fileTypes == "emf"] <- '' wb$Content_Types <- c(contentNodes, wb$Content_Types) names(media) <- mediaNames wb$media <- media } ## xl\chart if (length(charts) > 0) { chartNames <- basename(charts) nCharts <- sum(grepl("chart[0-9]+.xml", chartNames)) nChartStyles <- sum(grepl("style[0-9]+.xml", chartNames)) nChartCol <- sum(grepl("colors[0-9]+.xml", chartNames)) if (nCharts > 0) { wb$Content_Types <- c(wb$Content_Types, sprintf('', 1:nCharts)) } if (nChartStyles > 0) { wb$Content_Types <- c(wb$Content_Types, sprintf('', 1:nChartStyles)) } if (nChartCol > 0) { wb$Content_Types <- c(wb$Content_Types, sprintf('', 1:nChartCol)) } if (length(chartsRels)) { charts <- c(charts, chartsRels) chartNames <- c(chartNames, file.path("_rels", basename(chartsRels))) } names(charts) <- chartNames wb$charts <- charts } ## xl\theme if (length(themeXML) > 0) { wb$theme <- removeHeadTag(paste(unlist(lapply(sort(themeXML)[[1]], readUTF8)), collapse = "")) } ## externalLinks if (length(extLinksXML) > 0) { wb$externalLinks <- lapply(sort(extLinksXML), function(x) removeHeadTag(cppReadFile(x))) wb$Content_Types <- c( wb$Content_Types, sprintf('', seq_along(extLinksXML)) ) wb$workbook.xml.rels <- c(wb$workbook.xml.rels, sprintf( '', seq_along(extLinksXML) )) } ## externalLinksRels if (length(extLinksRelsXML) > 0) { wb$externalLinksRels <- lapply(sort(extLinksRelsXML), function(x) removeHeadTag(cppReadFile(x))) } ##* ----------------------------------------------------------------------------------------------*## ### BEGIN READING IN WORKSHEET DATA ##* ----------------------------------------------------------------------------------------------*## ## xl\worksheets file_names <- regmatches(worksheet_rId_mapping, regexpr("sheet[0-9]+\\.xml", worksheet_rId_mapping, perl = TRUE)) file_rIds <- unlist(getId(worksheet_rId_mapping)) file_names <- file_names[match(sheetrId, file_rIds)] worksheetsXML <- file.path(dirname(worksheetsXML), file_names) wb <- loadworksheets(wb = wb, styleObjects = styleObjects, xmlFiles = worksheetsXML, is_chart_sheet = is_chart_sheet) ## Fix styleobject encoding if (length(wb$styleObjects) > 0) { style_names <- sapply(wb$styleObjects, "[[", "sheet") Encoding(style_names) <- "UTF-8" wb$styleObjects <- lapply(seq_along(style_names), function(i) { wb$styleObjects[[i]]$sheet <- style_names[[i]] wb$styleObjects[[i]] }) } ## Fix headers/footers for (i in seq_along(worksheetsXML)) { if (!is_chart_sheet[i]) { if (length(wb$worksheets[[i]]$headerFooter) > 0) { wb$worksheets[[i]]$headerFooter <- lapply(wb$worksheets[[i]]$headerFooter, splitHeaderFooter) } } } ##* ----------------------------------------------------------------------------------------------*## ### READING IN WORKSHEET DATA COMPLETE ##* ----------------------------------------------------------------------------------------------*## ## Next sheetRels to see which drawings_rels belongs to which sheet if (length(sheetRelsXML) > 0) { ## sheetrId is order sheet appears in xlsx file ## create a 1-1 vector of rels to worksheet ## haveRels is boolean vector where i-the element is TRUE/FALSE if sheet has a rels sheet if (length(chartSheetsXML) == 0) { allRels <- file.path(dirname(sheetRelsXML[1]), paste0(file_names, ".rels")) haveRels <- allRels %in% sheetRelsXML } else { haveRels <- rep(FALSE, length(wb$worksheets)) allRels <- rep("", length(wb$worksheets)) for (i in 1:nSheets) { if (is_chart_sheet[i]) { ind <- which(chartSheetRIds == sheetrId[i]) rels_file <- file.path(chartSheetsRelsDir, paste0(chartsheet_rId_mapping[ind], ".rels")) } else { ind <- sheetrId[i] rels_file <- file.path(xmlDir, "xl", "worksheets", "_rels", paste0(file_names[i], ".rels")) } if (file.exists(rels_file)) { allRels[i] <- rels_file haveRels[i] <- TRUE } } } ## sheet.xml have been reordered to be in the order of sheetrId ## not every sheet has a worksheet rels xml <- lapply(seq_along(allRels), function(i) { if (haveRels[i]) { xml <- readUTF8(allRels[[i]]) xml <- removeHeadTag(xml) xml <- gsub("", "", xml) xml <- gsub("", "", xml) xml <- getChildlessNode(xml = xml, tag = "Relationship") } else { xml <- "" } return(xml) }) ## Slicers ------------------------------------------------------------------------------------- if (length(slicerXML) > 0) { slicerXML <- slicerXML[order(nchar(slicerXML), slicerXML)] slicersFiles <- lapply(xml, function(x) as.integer(regmatches(x, regexpr("(?<=slicer)[0-9]+(?=\\.xml)", x, perl = TRUE)))) inds <- sapply(slicersFiles, length) > 0 ## worksheet_rels Id for slicer will be rId0 k <- 1L wb$slicers <- rep("", nSheets) for (i in 1:nSheets) { ## read in slicer[j].XML sheets into sheet[i] if (inds[i]) { wb$slicers[[i]] <- slicerXML[k] k <- k + 1L wb$worksheets_rels[[i]] <- unlist(c( wb$worksheets_rels[[i]], sprintf('', i) )) wb$Content_Types <- c( wb$Content_Types, sprintf('', i) ) slicer_xml_exists <- FALSE ## Append slicer to worksheet extLst if (length(wb$worksheets[[i]]$extLst) > 0) { if (grepl('x14:slicer r:id="rId[0-9]+"', wb$worksheets[[i]]$extLst)) { wb$worksheets[[i]]$extLst <- sub('x14:slicer r:id="rId[0-9]+"', 'x14:slicer r:id="rId0"', wb$worksheets[[i]]$extLst) slicer_xml_exists <- TRUE } } if (!slicer_xml_exists) { wb$worksheets[[i]]$extLst <- c(wb$worksheets[[i]]$extLst, genBaseSlicerXML()) } } } } if (length(slicerCachesXML) > 0) { ## ---- slicerCaches inds <- seq_along(slicerCachesXML) wb$Content_Types <- c(wb$Content_Types, sprintf('', inds)) wb$slicerCaches <- sapply(slicerCachesXML[order(nchar(slicerCachesXML), slicerCachesXML)], function(x) removeHeadTag(cppReadFile(x))) wb$workbook.xml.rels <- c(wb$workbook.xml.rels, sprintf('', 1E5 + inds, inds)) wb$workbook$extLst <- c(wb$workbook$extLst, genSlicerCachesExtLst(1E5 + inds)) } ## Tables -------------------------------------------------------------------------------------- if (length(tablesXML) > 0) { tables <- lapply(xml, function(x) as.integer(regmatches(x, regexpr("(?<=table)[0-9]+(?=\\.xml)", x, perl = TRUE)))) tableSheets <- unlist(lapply(seq_along(sheetrId), function(i) rep(i, length(tables[[i]])))) if (length(unlist(tables)) > 0) { ## get the tables that belong to each worksheet and create a worksheets_rels for each tCount <- 2L ## table r:Ids start at 3 for (i in seq_along(tables)) { if (length(tables[[i]]) > 0) { k <- seq_along(tables[[i]]) + tCount wb$worksheets_rels[[i]] <- unlist(c( wb$worksheets_rels[[i]], sprintf('', k, k) )) wb$worksheets[[i]]$tableParts <- sprintf("", k) tCount <- tCount + length(k) } } ## sort the tables into the order they appear in the xml and tables variables names(tablesXML) <- basename(tablesXML) tablesXML <- tablesXML[sprintf("table%s.xml", unlist(tables))] ## tables are now in correct order so we can read them in as they are wb$tables <- sapply(tablesXML, function(x) removeHeadTag(paste(readUTF8(x), collapse = ""))) ## pull out refs and attach names refs <- regmatches(wb$tables, regexpr('(?<=ref=")[0-9A-Z:]+', wb$tables, perl = TRUE)) names(wb$tables) <- refs wb$Content_Types <- c(wb$Content_Types, sprintf('', seq_along(wb$tables) + 2)) ## relabel ids for (i in seq_along(wb$tables)) { newId <- sprintf(' id="%s" ', i + 2) wb$tables[[i]] <- sub(' id="[0-9]+" ', newId, wb$tables[[i]]) } displayNames <- unlist(regmatches(wb$tables, regexpr('(?<=displayName=").*?[^"]+', wb$tables, perl = TRUE))) if (length(displayNames) != length(tablesXML)) { displayNames <- paste0("Table", seq_along(tablesXML)) } attr(wb$tables, "sheet") <- tableSheets attr(wb$tables, "tableName") <- displayNames for (i in seq_along(tableSheets)) { table_sheet_i <- tableSheets[i] attr(wb$worksheets[[table_sheet_i]]$tableParts, "tableName") <- c(attr(wb$worksheets[[table_sheet_i]]$tableParts, "tableName"), displayNames[i]) } } } ## if(length(tablesXML) > 0) ## might we have some external hyperlinks if (any(sapply(wb$worksheets[!is_chart_sheet], function(x) length(x$hyperlinks) > 0))) { ## Do we have external hyperlinks hlinks <- lapply(xml, function(x) x[grepl("hyperlink", x) & grepl("External", x)]) hlinksInds <- which(sapply(hlinks, length) > 0) ## If it's an external hyperlink it will have a target in the sheet_rels if (length(hlinksInds) > 0) { for (i in hlinksInds) { ids <- unlist(lapply(hlinks[[i]], function(x) regmatches(x, gregexpr('(?<=Id=").*?"', x, perl = TRUE))[[1]])) ids <- gsub('"$', "", ids) targets <- unlist(lapply(hlinks[[i]], function(x) regmatches(x, gregexpr('(?<=Target=").*?"', x, perl = TRUE))[[1]])) targets <- gsub('"$', "", targets) ids2 <- lapply(wb$worksheets[[i]]$hyperlinks, function(x) regmatches(x, gregexpr('(?<=r:id=").*?"', x, perl = TRUE))[[1]]) ids2[sapply(ids2, length) == 0] <- NA ids2 <- gsub('"$', "", unlist(ids2)) targets <- targets[match(ids2, ids)] names(wb$worksheets[[i]]$hyperlinks) <- targets } } } ## Drawings ------------------------------------------------------------------------------------ ## xml is in the order of the sheets, drawIngs is toes to sheet position of hasDrawing ## Not every sheet has a drawing.xml drawXMLrelationship <- lapply(xml, function(x) grep("drawings/drawing", x, value = TRUE)) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing if (length(drawingRelsXML) > 0) { dRels <- lapply(drawingRelsXML, readUTF8) dRels <- unlist(lapply(dRels, removeHeadTag)) dRels <- gsub("", "", dRels) dRels <- gsub("", "", dRels) } if (length(drawingsXML) > 0) { dXML <- lapply(drawingsXML, readUTF8) dXML <- unlist(lapply(dXML, removeHeadTag)) dXML <- gsub("", "", dXML) dXML <- gsub("", "", dXML) # ptn1 <- "<(mc:AlternateContent|xdr:oneCellAnchor|xdr:twoCellAnchor|xdr:absoluteAnchor)" # ptn2 <- "" ## split at one/two cell Anchor # dXML <- regmatches(dXML, gregexpr(paste0(ptn1, ".*?", ptn2), dXML)) } ## loop over all worksheets and assign drawing to sheet if (any(hasDrawing)) { for (i in seq_along(xml)) { if (hasDrawing[i]) { target <- unlist(lapply(drawXMLrelationship[[i]], function(x) regmatches(x, gregexpr('(?<=Target=").*?"', x, perl = TRUE))[[1]])) target <- basename(gsub('"$', "", target)) ## sheet_i has which(hasDrawing)[[i]] relsInd <- grepl(target, drawingRelsXML) if (any(relsInd)) { wb$drawings_rels[i] <- dRels[relsInd] } drawingInd <- grepl(target, drawingsXML) if (any(drawingInd)) { wb$drawings[i] <- dXML[drawingInd] } } } } ## VML Drawings -------------------------------------------------------------------------------- if (length(vmlDrawingXML) > 0) { wb$Content_Types <- c(wb$Content_Types, '') drawXMLrelationship <- lapply(xml, function(x) grep("drawings/vmlDrawing", x, value = TRUE)) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing ## loop over all worksheets and assign drawing to sheet if (any(hasDrawing)) { for (i in seq_along(xml)) { if (hasDrawing[i]) { target <- unlist(lapply(drawXMLrelationship[[i]], function(x) regmatches(x, gregexpr('(?<=Target=").*?"', x, perl = TRUE))[[1]])) target <- basename(gsub('"$', "", target)) ind <- grepl(target, vmlDrawingXML) if (any(ind)) { txt <- paste(readUTF8(vmlDrawingXML[ind]), collapse = "\n") txt <- removeHeadTag(txt) i1 <- regexpr("", txt, fixed = TRUE) wb$vml[[i]] <- substring(text = txt, first = i1, last = (i2 - 1L)) relsInd <- grepl(target, vmlDrawingRelsXML) if (any(relsInd)) { wb$vml_rels[i] <- vmlDrawingRelsXML[relsInd] } } } } } } ## vmlDrawing and comments if (length(commentsXML) > 0) { drawXMLrelationship <- lapply(xml, function(x) grep("drawings/vmlDrawing[0-9]+\\.vml", x, value = TRUE)) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing commentXMLrelationship <- lapply(xml, function(x) grep("comments[0-9]+\\.xml", x, value = TRUE)) hasComment <- sapply(commentXMLrelationship, length) > 0 ## which sheets have a comment for (i in seq_along(xml)) { if (hasComment[i]) { target <- unlist(lapply(drawXMLrelationship[[i]], function(x) regmatches(x, gregexpr('(?<=Target=").*?"', x, perl = TRUE))[[1]])) target <- basename(gsub('"$', "", target)) ind <- grepl(target, vmlDrawingXML) if (any(ind)) { txt <- paste(readUTF8(vmlDrawingXML[ind]), collapse = "\n") txt <- removeHeadTag(txt) cd <- unique(getNodes(xml = txt, tagIn = "") ## now loada comment target <- unlist(lapply(commentXMLrelationship[[i]], function(x) regmatches(x, gregexpr('(?<=Target=").*?"', x, perl = TRUE))[[1]])) target <- basename(gsub('"$', "", target)) txt <- paste(readUTF8(grep(target, commentsXML, value = TRUE)), collapse = "\n") txt <- removeHeadTag(txt) authors <- getNodes(xml = txt, tagIn = "") authors <- gsub("|", "", authors) comments <- getNodes(xml = txt, tagIn = "") comments <- gsub("", "", comments) comments <- getNodes(xml = comments, tagIn = "))[\\s\\S]+?(?=)", comments, perl = TRUE)) comments <- lapply(comments, function(x) gsub(".*?>", "", x, perl = TRUE)) wb$comments[[i]] <- lapply(seq_along(comments), function(j) { comment_list <- list( "ref" = refs[j], "author" = authors[j], "comment" = comments[[j]], "style" = style[[j]], "clientData" = cd[[j]] ) }) } } } } ## Threaded comments if (length(threadCommentsXML) > 0) { threadCommentsXMLrelationship <- lapply(xml, function(x) grep("threadedComment[0-9]+\\.xml", x, value = TRUE)) hasThreadComments<- sapply(threadCommentsXMLrelationship, length) > 0 if(any(hasThreadComments)) { for (i in seq_along(xml)) { if (hasThreadComments[i]) { target <- unlist(lapply(threadCommentsXMLrelationship[[i]], function(x) regmatches(x, gregexpr('(?<=Target=").*?"', x, perl = TRUE))[[1]])) target <- basename(gsub('"$', "", target)) wb$threadComments[[i]] <- grep(target, threadCommentsXML, value = TRUE) } } } wb$Content_Types <- c( wb$Content_Types, sprintf('', sapply(threadCommentsXML, basename)) ) } ## Persons (needed for Threaded Comment) if(length(personXML) > 0){ wb$persons <- personXML wb$Content_Types <- c( wb$Content_Types, '' ) wb$workbook.xml.rels <- c( wb$workbook.xml.rels, '') } ## rels image drawXMLrelationship <- lapply(xml, function(x) grep("relationships/image", x, value = TRUE)) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing if (any(hasDrawing)) { for (i in seq_along(xml)) { if (hasDrawing[i]) { image_ids <- unlist(getId(drawXMLrelationship[[i]])) new_image_ids <- paste0("rId", seq_along(image_ids) + 70000) for (j in seq_along(image_ids)) { wb$worksheets[[i]]$oleObjects <- gsub(image_ids[j], new_image_ids[j], wb$worksheets[[i]]$oleObjects, fixed = TRUE) wb$worksheets_rels[[i]] <- c(wb$worksheets_rels[[i]], gsub(image_ids[j], new_image_ids[j], drawXMLrelationship[[i]][j], fixed = TRUE)) } } } } ## rels image drawXMLrelationship <- lapply(xml, function(x) grep("relationships/package", x, value = TRUE)) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing if (any(hasDrawing)) { for (i in seq_along(xml)) { if (hasDrawing[i]) { image_ids <- unlist(getId(drawXMLrelationship[[i]])) new_image_ids <- paste0("rId", seq_along(image_ids) + 90000) for (j in seq_along(image_ids)) { wb$worksheets[[i]]$oleObjects <- gsub(image_ids[j], new_image_ids[j], wb$worksheets[[i]]$oleObjects, fixed = TRUE) wb$worksheets_rels[[i]] <- c( wb$worksheets_rels[[i]], sprintf("", new_image_ids[j]) ) } } } } ## Embedded docx if (length(embeddings) > 0) { wb$Content_Types <- c(wb$Content_Types, '') wb$embeddings <- embeddings } ## pivot tables if (length(pivotTableXML) > 0) { # pivotTableJ <- lapply(xml, function(x) as.integer(regmatches(x, regexpr("(?<=pivotTable)[0-9]+(?=\\.xml)", x, perl = TRUE)))) variable not used # sheetWithPivot <- which(sapply(pivotTableJ, length) > 0) variable not used pivotRels <- lapply(xml, function(x) { y <- grep("pivotTable", x, value = TRUE) y[order(nchar(y), y)] }) hasPivot <- sapply(pivotRels, length) > 0 ## Modify rIds for (i in seq_along(pivotRels)) { if (hasPivot[i]) { for (j in seq_along(pivotRels[[i]])) { pivotRels[[i]][j] <- gsub('"rId[0-9]+"', sprintf('"rId%s"', 20000L + j), pivotRels[[i]][j]) } wb$worksheets_rels[[i]] <- c(wb$worksheets_rels[[i]], pivotRels[[i]]) } } ## remove any workbook_res references to pivot tables that are not being used in worksheet_rels inds <- seq_along(wb$pivotTables.xml.rels) fileNo <- as.integer(unlist(regmatches(unlist(wb$worksheets_rels), gregexpr("(?<=pivotTable)[0-9]+(?=\\.xml)", unlist(wb$worksheets_rels), perl = TRUE)))) inds <- inds[!inds %in% fileNo] if (length(inds) > 0) { toRemove <- paste(sprintf("(pivotCacheDefinition%s\\.xml)", inds), collapse = "|") fileNo <- grep(toRemove, wb$pivotTables.xml.rels) toRemove <- paste(sprintf("(pivotCacheDefinition%s\\.xml)", fileNo), collapse = "|") ## remove reference to file from workbook.xml.res wb$workbook.xml.rels <- wb$workbook.xml.rels[!grepl(toRemove, wb$workbook.xml.rels)] } } } ## end of worksheetRels ## convert hyperliks to hyperlink objects for (i in 1:nSheets) { wb$worksheets[[i]]$hyperlinks <- xml_to_hyperlink(wb$worksheets[[i]]$hyperlinks) } ## queryTables if (length(queryTablesXML) > 0) { ids <- as.numeric(regmatches(queryTablesXML, regexpr("[0-9]+(?=\\.xml)", queryTablesXML, perl = TRUE))) wb$queryTables <- unlist(lapply(queryTablesXML[order(ids)], function(x) removeHeadTag(cppReadFile(xmlFile = x)))) wb$Content_Types <- c( wb$Content_Types, sprintf('', seq_along(queryTablesXML)) ) } ## connections if (length(connectionsXML) > 0) { wb$connections <- removeHeadTag(cppReadFile(xmlFile = connectionsXML)) wb$workbook.xml.rels <- c(wb$workbook.xml.rels, '') wb$Content_Types <- c(wb$Content_Types, '') } ## table rels if (length(tableRelsXML) > 0) { ## table_i_might have tableRels_i but I am re-ordering the tables to be in order of worksheets ## I make every table have a table_rels so i need to fill in the gaps if any table_rels are missing tmp <- paste0(basename(tablesXML), ".rels") hasRels <- tmp %in% basename(tableRelsXML) ## order tableRelsXML tableRelsXML <- tableRelsXML[match(tmp[hasRels], basename(tableRelsXML))] ## wb$tables.xml.rels <- character(length = length(tablesXML)) ## which sheet does it belong to xml <- sapply(tableRelsXML, cppReadFile, USE.NAMES = FALSE) xml <- sapply(xml, removeHeadTag, USE.NAMES = FALSE) wb$tables.xml.rels[hasRels] <- xml } else if (length(tablesXML) > 0) { wb$tables.xml.rels <- rep("", length(tablesXML)) } activesheet <- unlist(regmatches(workbook, gregexpr("(?<=).*(?=)", workbook, perl = TRUE))) activesheet <- unlist(regmatches(activesheet, gregexpr("]*>", activesheet, perl = TRUE))) wb$ActiveSheet <- as.integer(getAttrs(activesheet,"activeTab")$activeTab) + 1L if(length(wb$ActiveSheet) == 0){ wb$ActiveSheet <- 1L } return(wb) } openxlsx/R/openxlsx.R0000644000176200001440000001211214155600363014336 0ustar liggesusers#' xlsx reading, writing and editing. #' #' openxlsx simplifies the the process of writing and styling Excel xlsx files from R #' and removes the dependency on Java. #' #' @name openxlsx #' @docType package #' @useDynLib openxlsx, .registration=TRUE #' @importFrom zip zipr #' @importFrom utils download.file head menu unzip #' #' @seealso #' \itemize{ #' \item{`vignette("Introduction", package = "openxlsx")`} #' \item{`vignette("formatting", package = "openxlsx")`} #' \item{[writeData()]} #' \item{[writeDataTable()]} #' \item{[write.xlsx()]} #' \item{[read.xlsx()]} #' \item{[op.openxlsx()]} #' } #' for examples #' #' @details #' The openxlsx package uses global options, most to simplify formatting. These #' are stored in the `op.openxlsx` object. #' #' \describe{ #' \item{openxlsx.bandedCols}{FALSE} #' \item{openxlsx.bandedRows}{TRUE} #' \item{openxlsx.borderColour}{"black"} #' \item{openxlsx.borders}{"none"} #' \item{openxlsx.borderStyle}{"thin"} #' \item{openxlsx.compressionLevel}{"9"} #' \item{openxlsx.creator}{""} #' \item{openxlsx.dateFormat}{"mm/dd/yyyy"} #' \item{openxlsx.datetimeFormat}{"yyyy-mm-dd hh:mm:ss"} #' \item{openxlsx.headerStyle}{NULL} #' \item{openxlsx.keepNA}{FALSE} #' \item{openxlsx.na.string}{NULL} #' \item{openxlsx.numFmt}{NULL} #' \item{openxlsx.orientation}{"portrait"} #' \item{openxlsx.paperSize}{9} #' \item{openxlsx.tabColour}{"TableStyleLight9"} #' \item{openxlsx.tableStyle}{"TableStyleLight9"} #' \item{openxlsx.withFilter}{NA Whether to write data with or without a #' filter. If NA will make filters with `writeDataTable` and will not for #' `writeData`} #' } #' #' See the Formatting vignette for examples. #' #' Additional options #' #' NULL #' openxlsx Options #' #' See and get the openxlsx options #' #' @details #' #' `openxlsx_getOp()` retrieves the `"openxlsx"` options found in #' `op.openxlsx`. If none are set (currently `NULL`) retrieves the #' default option from `op.openxlsx`. This will also check that the #' intended option is a standard option (listed in `op.openxlsx`) and #' will provide a warning otherwise. #' #' `openxlsx_setOp()` is a safer way to set an option as it will first #' check that the option is a standard option (as above) before setting. #' #' @examples #' openxlsx_getOp("borders") #' op.openxlsx[["openxlsx.borders"]] #' #' @export #' @name openxlsx_options op.openxlsx <- list( openxlsx.bandedCols = FALSE, openxlsx.bandedRows = TRUE, openxlsx.borderColour = "black", openxlsx.borders = NULL, openxlsx.borderStyle = "thin", # Where is compressionLevel called? openxlsx.compressionLevel = 9, openxlsx.creator = "", openxlsx.dateFormat = "mm/dd/yyyy", openxlsx.datetimeFormat = "yyyy-mm-dd hh:mm:ss", openxlsx.hdpi = 300, openxlsx.header = NULL, openxlsx.headerStyle = NULL, openxlsx.firstColumn = NULL, openxlsx.firstFooter = NULL, openxlsx.firstHeader = NULL, openxlsx.footer = NULL, openxlsx.evenFooter = NULL, openxlsx.evenHeader = NULL, openxlsx.gridLines = TRUE, openxlsx.keepNA = FALSE, openxlsx.lastColumn = NULL, openxlsx.na.string = NULL, openxlsx.maxWidth = 250, openxlsx.minWidth = 3, openxlsx.numFmt = "GENERAL", openxlsx.oddFooter = NULL, openxlsx.oddHeader = NULL, openxlsx.orientation = "portrait", openxlsx.paperSize = 9, openxlsx.showGridLines = NA, openxlsx.tabColour = NULL, openxlsx.tableStyle = "TableStyleLight9", openxlsx.vdpi = 300, openxlsx.withFilter = NULL ) #' @param x An option name (`"openxlsx."` prefix optional) #' @param default A default value if `NULL` #' @rdname openxlsx_options #' @export openxlsx_getOp <- function(x, default = NULL) { if (length(x) != 1L || length(default) > 1L) { stop("x must be length 1 and default NULL or length 1", call. = FALSE) } x <- check_openxlsx_op(x) getOption(x, op.openxlsx[[x]]) %||% default } #' @param value The new value for the option (optional if x is a named list) #' @rdname openxlsx_options #' @export openxlsx_setOp <- function(x, value) { if (is.list(x)) { if (is.null(names(x))) { stop("x cannot be an unnamed list", call. = FALSE) } return(invisible(mapply(openxlsx_setOp, x = names(x), value = x))) } value <- as.list(value) names(value) <- check_openxlsx_op(x) options(value) } check_openxlsx_op <- function(x) { if (length(x) != 1L || !is.character(x)) { stop("option must be a character vector of length 1", call. = FALSE) } if (!grepl("^openxlsx[.]", x)) { x <- paste0("openxlsx.", x) } if (!x %in% names(op.openxlsx)) { warning( x, " is not a standard openxlsx option\nCheck spelling", call. = FALSE ) } x } openxlsx_resetOp <- function() { options(op.openxlsx) } openxlsx/R/borderFunctions.R0000644000176200001440000003560314155600363015636 0ustar liggesusers genBaseColStyle <- function(cc) { colStyle <- createStyle() specialFormat <- TRUE if ("date" %in% cc) { colStyle <- createStyle(numFmt = "date") } else if (any(c("posixlt", "posixct", "posixt") %in% cc)) { colStyle <- createStyle(numFmt = "longdate") } else if ("currency" %in% cc) { colStyle$numFmt <- list(numFmtId = "164", "formatCode" = ""$"#,##0.00") } else if ("accounting" %in% cc) { colStyle$numFmt <- list(numFmtId = "44") } else if ("hyperlink" %in% cc) { colStyle$fontColour <- list(theme = "10") } else if ("percentage" %in% cc) { colStyle$numFmt <- list(numFmtId = "10") } else if ("scientific" %in% cc) { colStyle$numFmt <- list(numFmtId = "11") } else if (any(c("3", "comma") %in% cc)) { colStyle$numFmt <- list(numFmtId = "3") } else if ("numeric" %in% cc & !grepl("[^0\\.,#\\$\\* %]", openxlsx_getOp("numFmt"))) { colStyle$numFmt <- list(numFmtId = 9999, formatCode = openxlsx_getOp("numFmt")) } else { colStyle$numFmt <- list(numFmtId = "0") specialFormat <- FALSE } list( style = colStyle, specialFormat = specialFormat ) } Workbook$methods(surroundingBorders = function( colClasses, sheet, startRow, startCol, nRow, nCol, borderColour, borderStyle, borderType ) { sheet <- sheet_names[[validateSheet(sheet)]] ## steps # get column class # get corresponding base style for (i in 1:nCol) { tmp <- genBaseColStyle(colClasses[[i]]) colStyle <- tmp$style specialFormat <- tmp$specialFormat ## create style objects sTop <- colStyle$copy() sMid <- colStyle$copy() sBot <- colStyle$copy() ## First column if (i == 1) { if (nRow == 1 & nCol == 1) { ## All sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol ) )) } else if (nCol == 1) { ## Top sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour ## Middle sMid$borderLeft <- borderStyle sMid$borderLeftColour <- borderColour sMid$borderRight <- borderStyle sMid$borderRightColour <- borderColour ## Bottom sBot$borderBottom <- borderStyle sBot$borderBottomColour <- borderColour sBot$borderLeft <- borderStyle sBot$borderLeftColour <- borderColour sBot$borderRight <- borderStyle sBot$borderRightColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol ) )) styleObjects <<- append(styleObjects, list( list( "style" = sMid, "sheet" = sheet, "rows" = (startRow + 1L):(startRow + nRow - 2L), # 2nd -> 2nd to last "cols" = rep.int(startCol, nRow - 2L) ) )) styleObjects <<- append(styleObjects, list( list( "style" = sBot, "sheet" = sheet, "rows" = startRow + nRow - 1L, "cols" = startCol ) )) } else if (nRow == 1) { ## All sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol ) )) } else { ## Top sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour ## Middle sMid$borderLeft <- borderStyle sMid$borderLeftColour <- borderColour ## Bottom sBot$borderLeft <- borderStyle sBot$borderLeftColour <- borderColour sBot$borderBottom <- borderStyle sBot$borderBottomColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol ) )) if (nRow > 2) { styleObjects <<- append(styleObjects, list( list( "style" = sMid, "sheet" = sheet, "rows" = (startRow + 1L):(startRow + nRow - 2L), # 2nd -> 2nd to last "cols" = rep.int(startCol, nRow - 2L) ) )) } styleObjects <<- append(styleObjects, list( list( "style" = sBot, "sheet" = sheet, "rows" = startRow + nRow - 1L, "cols" = startCol ) )) } } else if (i == nCol) { if (nRow == 1) { ## All sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol + nCol - 1L ) )) } else { ## Top sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour ## Middle sMid$borderRight <- borderStyle sMid$borderRightColour <- borderColour ## Bottom sBot$borderRight <- borderStyle sBot$borderRightColour <- borderColour sBot$borderBottom <- borderStyle sBot$borderBottomColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol + nCol - 1L ) )) if (nRow > 2) { styleObjects <<- append(styleObjects, list( list( "style" = sMid, "sheet" = sheet, "rows" = (startRow + 1L):(startRow + nRow - 2L), # 2nd -> 2nd to last "cols" = rep.int(startCol + nCol - 1L, nRow - 2L) ) )) } styleObjects <<- append(styleObjects, list( list( "style" = sBot, "sheet" = sheet, "rows" = startRow + nRow - 1L, "cols" = startCol + nCol - 1L ) )) } } else { ## inside columns if (nRow == 1) { ## Top sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour ## Bottom sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol + i - 1L ) )) } else { ## Top sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour ## Bottom sBot$borderBottom <- borderStyle sBot$borderBottomColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol + i - 1L ) )) ## Middle if (specialFormat) { styleObjects <<- append(styleObjects, list( list( "style" = sMid, "sheet" = sheet, "rows" = (startRow + 1L):(startRow + nRow - 2L), # 2nd -> 2nd to last "cols" = rep.int(startCol + i - 1L, nRow - 2L) ) )) } styleObjects <<- append(styleObjects, list( list( "style" = sBot, "sheet" = sheet, "rows" = startRow + nRow - 1L, "cols" = startCol + i - 1L ) )) } } ## End of if(i == 1), i == NCol, else inside columns } ## End of loop through columns invisible(0) }) Workbook$methods(rowBorders = function( colClasses, sheet, startRow, startCol, nRow, nCol, borderColour, borderStyle, borderType ) { sheet <- sheet_names[[validateSheet(sheet)]] ## steps # get column class # get corresponding base style for (i in 1:nCol) { tmp <- genBaseColStyle(colClasses[[i]]) sTop <- tmp$style ## First column if (i == 1) { if (nCol == 1) { ## All borders (rows and surrounding) sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour } else { ## Top, Left, Bottom sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour } } else if (i == nCol) { ## Top, Right, Bottom sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour } else { ## inside columns ## Top, Middle, Bottom sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour } ## End of if(i == 1), i == NCol, else inside columns styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = (startRow):(startRow + nRow - 1L), "cols" = rep(startCol + i - 1L, nRow) ) )) } ## End of loop through columns invisible(0) }) Workbook$methods(columnBorders = function( colClasses, sheet, startRow, startCol, nRow, nCol, borderColour, borderStyle, borderType ) { sheet <- sheet_names[[validateSheet(sheet)]] ## steps # get column class # get corresponding base style for (i in 1:nCol) { tmp <- genBaseColStyle(colClasses[[i]]) colStyle <- tmp$style specialFormat <- tmp$specialFormat ## create style objects sTop <- colStyle$copy() sMid <- colStyle$copy() sBot <- colStyle$copy() if (nRow == 1) { ## Top sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = startCol + i - 1L ) )) } else { ## Top sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour ## Middle sMid$borderLeft <- borderStyle sMid$borderLeftColour <- borderColour sMid$borderRight <- borderStyle sMid$borderRightColour <- borderColour ## Bottom sBot$borderBottom <- borderStyle sBot$borderBottomColour <- borderColour sBot$borderLeft <- borderStyle sBot$borderLeftColour <- borderColour sBot$borderRight <- borderStyle sBot$borderRightColour <- borderColour colInd <- startCol + i - 1L styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = startRow, "cols" = colInd ) )) if (nRow > 2) { styleObjects <<- append(styleObjects, list( list( "style" = sMid, "sheet" = sheet, "rows" = (startRow + 1L):(startRow + nRow - 2L), "cols" = rep(colInd, nRow - 2L) ) )) } styleObjects <<- append(styleObjects, list( list( "style" = sBot, "sheet" = sheet, "rows" = startRow + nRow - 1L, "cols" = colInd ) )) } } ## End of loop through columns invisible(0) }) Workbook$methods(allBorders = function( colClasses, sheet, startRow, startCol, nRow, nCol, borderColour, borderStyle, borderType ) { sheet <- sheet_names[[validateSheet(sheet)]] ## steps # get column class # get corresponding base style for (i in 1:nCol) { tmp <- genBaseColStyle(colClasses[[i]]) sTop <- tmp$style ## All borders sTop$borderTop <- borderStyle sTop$borderTopColour <- borderColour sTop$borderBottom <- borderStyle sTop$borderBottomColour <- borderColour sTop$borderLeft <- borderStyle sTop$borderLeftColour <- borderColour sTop$borderRight <- borderStyle sTop$borderRightColour <- borderColour styleObjects <<- append(styleObjects, list( list( "style" = sTop, "sheet" = sheet, "rows" = (startRow):(startRow + nRow - 1L), "cols" = rep(startCol + i - 1L, nRow) ) )) } ## End of loop through columns invisible(0) }) openxlsx/R/chartsheet_class.R0000644000176200001440000000274014155600363016003 0ustar liggesusers #' @include class_definitions.R ChartSheet$methods(initialize = function(tabSelected = FALSE, tabColour = character(0), zoom = 100) { if (length(tabColour) > 0) { tabColour <- sprintf("%s", tabColour) } else { tabColour <- character(0) } if (zoom < 10) { zoom <- 10 } else if (zoom > 400) { zoom <- 400 } sheetPr <<- tabColour sheetViews <<- sprintf('', as.integer(zoom), as.integer(tabSelected)) pageMargins <<- '' drawing <<- '' hyperlinks <<- character(0) return(invisible(0)) }) ChartSheet$methods(get_prior_sheet_data = function() { xml <- '>' if (length(sheetPr) > 0) { xml <- paste(xml, sheetPr, collapse = "") } if (length(sheetViews) > 0) { xml <- paste(xml, sheetViews, collapse = "") } if (length(pageMargins) > 0) { xml <- paste(xml, pageMargins, collapse = "") } if (length(drawing) > 0) { xml <- paste(xml, drawing, collapse = "") } xml <- paste(xml, "") return(xml) }) openxlsx/NEWS.md0000644000176200001440000004153514155741042013243 0ustar liggesusers# openxlsx 4.2.5 ## Fixes * `openxlsx_setOp()` now works with named list ([#215](https://github.com/ycphs/openxlsx/issues/215)) * `loadWorkbook()` imports `inlineStr`. Values remain `inlineStr` when writing the workbook with `saveWorkbook()`. Similar `read.xlsx` and `readWorkbook` import `inlineStr`. * `read.xlsx()` no longer changes random seed ([#183](https://github.com/ycphs/openxlsx/issues/183)) * fixed a regression that caused fonts to be read in incorrectly ([#207](https://github.com/ycphs/openxlsx/issues/207)) * add option to save as read only recommended ([#201](https://github.com/ycphs/openxlsx/issues/201)) * fixed writing hyperlink formulas ([#200](https://github.com/ycphs/openxlsx/issues/200)) * `write.xlsx()` now throws an error if it doesn't have write permissions ([#190](https://github.com/ycphs/openxlsx/issues/190)) * `write.xlsx()` now again uses the default of `overwrite = TRUE` for saving files ([#249](https://github.com/ycphs/openxlsx/issues/249)) ## Improvements * `options()` are more consistently set in functions (see: [#289](https://github.com/ycphs/openxlsx/issues/262)) * `Workbook$show()` no longer fails when called in a 0 sheet workbook([#240](https://github.com/ycphs/openxlsx/issues/240)) * `read.xlsx()` again accepts `.xlsm` files ([#205](https://github.com/ycphs/openxlsx/issues/205), [#209](https://github.com/ycphs/openxlsx/issues/209)) * `makeHyperlinkString()` does no longer require a sheet argument ([#57](https://github.com/ycphs/openxlsx/issues/57), [#58](https://github.com/ycphs/openxlsx/issues/58)) * improvements in how `openxlsx` creates temporary directories (see [#262](https://github.com/ycphs/openxlsx/issues/262)) * `writeData()` calls `force(x)` to evaluate the object before options are set ([#264](https://github.com/ycphs/openxlsx/issues/264)) * `createComment()` now correctly handles `integers` in `width` and `height` ([#275](https://github.com/ycphs/openxlsx/issues/275)) * `setStyles()` accepts `halign="justify"` ([#305](https://github.com/ycphs/openxlsx/issues/305)) # openxlsx 4.2.4 ## Fixes * `write.xlsx()` now successfully passes `withFilter` ([#151](https://github.com/ycphs/openxlsx/issues/151)) * code clean up PR [#168](https://github.com/ycphs/openxlsx/pull/168) * removal of unused variables PR [#168](https://github.com/ycphs/openxlsx/pull/168) ## New features * adds `buildWorkbook()` to generate a `Workbook` object from a (named) list or a data.frame ([#192](https://github.com/ycphs/openxlsx/issues/192), [#187](https://github.com/ycphs/openxlsx/issues/187)) * this is now recommended rather than the `write.xlsx(x, file) ; wb <- read.xlsx(file)` functionality before * `write.xlsx()` is now a wrapper for `wb <- buildWorkbook(x); saveWorkbook(x, file)` * parameter checking from `write.xlsx()` >> `buildWorkbook()` are now held off until passed to `writeData()`, `writeDataTable()`, etc * `row.names` is now deprecated for `writeData()` and `writeDataTable()`; please use `rowNames` instead * `read.xlsx()` now checks for the file extension `.xlsx`; previously it would throw an error when the file was `.xls` or `.xlm` files * memory allocation improvements * global options added for `minWidth` and `maxWidth` * `write.xlsx()` >> `buildWorkbook()` can now handle `colWidths` passed as either a single element or a `list()` * Added ability to change positioning of summary columns and rows. * These can be set with the `summaryCol` and `summaryRow` arguments in `pageSetup()`. * `activeSheet` allows to set and get the active (displayed) sheet of a workbook. * Adds new global options for workbook formatting ([#165](https://github.com/ycphs/openxlsx/issues/165); see `?op.openxlsx`) # openxlsx 4.2.3 ## New Features * Most of functions in openxlsx now support non-ASCII arguments better. More specifically, we can use non-ASCII strings as names or contents for `createNamedRegion()` ([#103](https://github.com/ycphs/openxlsx/issues/103)), `writeComment()`, `writeData()`, `writeDataTable()` and `writeFormula()`. In addition, openxlsx now reads comments and region names that contain non-ASCII strings correctly on Windows. Thanks to @shrektan for the PR [#118](https://github.com/ycphs/openxlsx/pull/118). * `setColWidths()` now supports zero-length `cols`, which is convenient when `cols` is dynamically provided [#128](https://github.com/ycphs/openxlsx/issues/128). Thanks to @shrektan for the feature request and the PR. ## Fixes for Check issues * Fix to pass the tests for link-time optimization type mismatches * Fix to pass the checks of native code (C/C++) based on static code analysis ## Bug Fixes * Grouping columns after setting widths no longer throws an error ([#100](https://github.com/ycphs/openxlsx/issues/100)) * Fix inability to save workbook more than once ([#106](https://github.com/ycphs/openxlsx/issues/106)) * Fix `loadWorkbook()` sometimes importing incorrect column attributes # openxlsx 4.2.2 ## New Features * Added features for `conditionalFormatting` to support also 'contains not', 'begins with' and 'ends with' * Added return value for `saveWorkbook()` the default value for `returnValue` is `FALSE` ([#71](https://github.com/ycphs/openxlsx/issues/71)) * Added Tests for new parameter of `saveWorkbook()` ## Bug Fixes * Solved CRAN check errors based on the change discussed in [PR#17277](https://bugs.r-project.org/show_bug.cgi?id=17277) # openxlsx 4.2.0 ## New Features * Added `groupColumns()`, `groupRows()`, `ungroupColumns()`, and `ungroupRows()` to group/ugroup columns/rows ([#32](https://github.com/ycphs/openxlsx/issues/32)) ## Bug Fixes * Allow xml-sensitive characters in sheetnames ([#78](https://github.com/ycphs/openxlsx/issues/78)) ## Internal * Updated roxygen2 to 7.1.1 # openxlsx 4.1.5.1 ## Bug Fixes * fixed issue [#68](https://github.com/ycphs/openxlsx/issues/68]) # openxlsx 4.1.5 ## New Features * Add functions to get and set the creator of the xlsx file * add function to set the name of the user who last modified the xlsx file ## Bug Fixes * Fixed NEWS hyperlink * Fixed writing of mixed EST/EDT datetimes * Added description for `writeFormula()` to use only English function names * Fixed validateSheet for special characters ## Internal * applied the tidyverse-style to the package `styler::style_pkg()` * include tests for `cloneWorksheet` # openxlsx 4.1.4 ## New Features * Added `getCellRefs()` as function. [#7](https://github.com/ycphs/openxlsx/issues/7) * Added parameter for customizing na.strings ## Bug Fixes * Use `zip::zipr()` instead of `zip::zip()`. * Keep correct visibility option for loadWorkbook. [#12](https://github.com/ycphs/openxlsx/issues/12]) * Add space surrounding "wrapText" [#17](https://github.com/ycphs/openxlsx/issues/17) * Corrected Percentage, Accounting, Comma, Currency class on column level ## Internal * update to roxygen2 7.0.0 # openxlsx 4.1.3 ## New Features * Added a `NEWS.md` file to track changes to the package. * Added `pkgdown` to create site. ## Bug Fixes * Return values for cpp changed to R_NilValue for r-devel tests * Added empty lines at the end of files # openxlsx 4.1.2 * Changed maintainer # openxlsx 4.1.1 ## New Features * `sep.names` allows choose other separator than '.' for variable names with a blank inside * Improve handling of non-region names in `getNamedRegions` and add related test # openxlsx 4.1.0 ## New Features * `deleteNamedRegions` to delete named region and optionally the worksheet data * set Workbook properties 'title', 'subject', 'category' ## Bug Fixes * `pageSetup` fails when passing in sheet by name * matching sheet names with special characters now works * `skipEmptyCols` being ignored by `read.xlsx.Workbook` * zero column data.frames would throw an error. * `read.xlsx` on files created using apache poi failed to match sheet name to xml file. * deleted table re-appearing after save & load. * newline characters in table names would corrupt file * datetime precision # openxlsx 4.0.17 ## New Features * `getNamedRegions` returns sheet name and cell references along with the named regions. * `borderStyle` and `borderColour` can be vector to specify different values for each side * `dataValidation` type "list" * `dataBar showValue`, gradient and border can now be set through conditionalFormatting() * options("openxlsx.zipflags") to pass additional flags to zip application e.g. compression level * `getTables()` and `removeTable()` to show and remove Excel table objects * set column to 'hidden' with `setColWidths()` ## Bug Fixes * `skipEmptyRows` & `skipEmptyCols` was being ignored by `read.xlsx` * date detection basic_string error * multiple spaces in table column names were not being maintained thus corrupting the xlsx file. * openXL fail silently on relative paths * `headerStyle` failed when writing a list of length 1 using `write.xlsx` * `detectDate` for `read.xlsx` issues * some Excel column types causing existing styling to be removed * `na.strings` no longer ignored for `read.xlsx.Workbook` * partial dollar matches on 'font' and 'fill' fixed * maintain hidden columns and their custom widths in `loadWorkbook()` * overwriting cells with borders sometimes removed the border styling # openxlsx 4.0.0 ## New Features * Reduced RAM usage and improved performance * maintain vbaProject, slicers, pivotTables on load * Read and load from URL ## Bug Fixes * Fix date time conversion accuracy issues. * Allow multibyte characters in names and comments. * Remove `tolower()` over style number formats to allow uppercase cell formatting * Stacking styles fixed. # openxlsx 3.0.2 ## New Features * "between" type for conditional formatting values in some interval. * `colWidths` parameter added to `write.xlsx` for auto column widths. * `freezePane` parameter handling added to `write.xlsx`. * `visible` parameter to `addWorksheet` to hide worksheets. * `sheetVisible` function to get and assign worksheet visibility state "hidden"/"visible" * `pageBreak` function to add page breaks to worksheets. ## Bug Fixes * `keepNA` parameter added to `write.xlsx`. Passed to `writeData`/`writeDataTable` # openxlsx 3.0.1 ## New Features * improved performance of `read.xlsx` and `loadWorkbook` * `writeFormula` function added to write cell formulas. Also columns with class "formula" are written as cell formulas similar how column classes determine cell styling * Functionality to write comments and maintain comments with `loadWorkbook` * `check.names` argument added `read.xlsx` to make syntactically valid variable names * `loadWorkbook` maintains cell indents * `namedRegion` parameter added to `read.xlsx` to read a named region. * `getNamed` regions to return names of named regions in a workbook * `getSheetNames` to get worksheet names within an xlsx file. ## Bug Fixes * `convertToDateTime` now handles NA values * `read.xlsx` rows bug fixed where non-consecutive cells were skipped. * `convertToDate` & `convertToDateTime` now handle NA values. * out of bounds worksheet fixed for libre office xlsx files. * `loadWorkbook` now maintains `chartSheets ` # openxlsx 2.4.0 ## New Features * stackable cell styling * `getDateOrigin` function to return the date origin used internally by the xlsx file to pass to `convertToDate` * Auto-detection of date cells. Cells that "look" like dates will be converted to dates when reading from file. * `read.xlsx.Workbook` to read from workbook objects * `colIndex`, `rowIndex` added to `read.xlsx` to only read specified rows and columns * Excel slicers now maintained by `loadWorkbook` * fill styles extended to support `gradientFill` ## Bug Fixes * Encoding fixed and multi-byte characters now supported. * `read.xlsx` now maintains multiple consecutive spaces and newline characters. * `convertToDate` & `convertToDateTime` now handle NA values. * multiple selected worksheet issue which preventing adding of new worksheets in Excel. * `zoom` parameter now limited to [10, 400] and documentation updated. * `write.xlsx` colnames parameter being assigned to rownames * Handling of NaN and Inf values in `writeData` # openxlsx 2.1.3 ## New Features * `conditionalFormatting` type "databar" * `asTable` parameter to `write.xlsx` to writing using `writeDataTable`. * extended `numFmt` formatting to numeric rounding also added option("openxlsx.numFmt" = ...) for default number formatting of numeric columns * additional `numFmt` "comma" to format numerics with "," thousands separator * `tableName` parameter to `writeDataTable` to assign the table a name * `headerStyle` parameter to `writeDataTable` for additional column names styling * `textRotation` parameter to `createStyle` to rotate cell text * functions `addFilter` & `removeFilter` to add filters to columns * Headers & footers extended, can now be set with `addWorksheet` and `setHeaderFooter`. `setHeader` & `setFooter` deprecated. * "fitToWidth" and "fitToHeight" logicals in `pageSetup`. * "zoom" parameter in addWorksheet to set worksheet zoom level. * "withFilter"" parameter to writeDataTable and writeData to remove table filters * `keepNa` parameter to `writeDataTable` and `writeData` to write NA values as #N/A * auto column widths can now be set with width = "auto" ## VIGNETTE * section on `write.xlsx` in Introductory vignette ## Bug Fixes * Fix reading in of apostrophes * Styling blank cells no longer corrupts workbooks * `read.xlsx` now correctly reads `sharedStrings` with inline styling * `sharedStrings` now exact matches true/false to determine logical values from workbooks. * fomulas in column caused openxlsx to crash. This has been fixed. # openxlsx 2.0.15 ## New Features * `writeData` now style based on column class the same as `writeDataTable` * Vignette "Formatting" for examples focused on formatting * Customizable date formatting with `createStyle` and also through option("openxlsx.dateFormat" = ...) * Customizable POSIX formatting with `createStyle` and also through option("openxlsx.datetimeFormat" = ...) * Generalised `conditionalFormat` function to complex expressions and color scales. * `writeData` border type "all" to draw all borders and maintain column styling. * Deprecated "sheets" and replaced with "names" function * column class "scientific" to automatically style as scientific numbers * `writeData` now handles additional object classes: coxph, cox.zph, summary.coxph1 from Survival package ## Bug Fixes * Invalid XML characters in hyperlinks now replaced. * Encoding issues when writing data read in with `read.xlsx` * scientific notation resulting in corrupt workbooks fix * Multiple saves of Workbooks containing conditional formatting were corrupt. * Latin1 characters now write correctly. * logicals written as 0/1 instead of TRUE/FALSE # openxlsx 2.0.1 ## New Features * `write.xlsx` function to write data directly to file via the `writeData` function with basic cell styling. * `writeDataTable` now styles columns of class 'Date', 'POSIXct', 'POSIXt', 'currency', 'accounting', 'percentage' as Excel formats Date, Date, Date, Currency, Accounting, Percentage respectively. * Data of class 'Date', 'POSIXct', 'POSIXt', 'currency', 'accounting' are converted to integers upon writing (as opposed to characters). * `writeDataTable` converts columns of class 'hyperlink' to hyperlinks. * logicals are converted to Excel booleans * hyperlinks in loaded workbooks are now maintained * `borderStyle` argument to `createStyle` to modify border line type. * `borderStyle` argument to `writeData` to modify border line type. * "worksheetOrder" function to shuffle order of worksheets when writing to file * `openXL` function to open an excel file or Workbook object ## Bug Fixes * conversion of numeric data to integer in `read.xlsx` fixed. * `readWorkbook`/`read.xlsx` should work now. Empty values are now padded with NA. Many other bugs fixed. * borders on single row and/or column data.frames now work. * `readWorkbook`/`read.xlsx` check for TRUE/FALSE values is now case-insensitive. * sheet names containing invalid xml characters (&, <, >, ', ") now work when referencing by name and will not result in a corrupt workbook. * sheet names containing non-local characters can now be referenced by name. * Invalid factor level when missing values in `writeData` * `saveWorkbook` now accepts relative paths. * Non-local character encoding issues. * errors in vignette examples. * numbers with > 8 digits were rounded in `writeData` openxlsx/MD50000644000176200001440000003156314156124026012453 0ustar liggesusers0ee7dddc28597bfeb4535a0833d6d754 *DESCRIPTION 7b6c9251a2fc7cf0233e205091619653 *LICENSE 371afe0512d43633337c3f0bef8e0013 *NAMESPACE 1d8355aeb0b737e446388ff632f5072d *NEWS.md aefc02086b72488b3d1bf2508eea389e *R/CommentClass.R 52ed90adfec08ebf8d6c8fd3cf30d04e *R/HyperlinkClass.R 1cfd53b832f7445b90eeac17de224123 *R/RcppExports.R 8f1e14d3db9a17e3d5e555404db1f9d0 *R/StyleClass.R 3bd32ad89d55d781ff29feaf1ebba0b2 *R/WorkbookClass.R 03dcd17147d2be65848d3f99c0677433 *R/asserts.R 37a69f7c394e43cf006f990fcf1cc6ed *R/baseXML.R 5376e1aca1eb05f899b2e0249d3a6e51 *R/borderFunctions.R af007869aaefae8677d1435e03a9c641 *R/build_workbook.R a3d492922ef30c79b677ec904ed7a830 *R/chartsheet_class.R 26e58b9c2e7ddd99d54a19149ef1a55e *R/class_definitions.R 14bfa2b761920db98250c1657ae6a0ff *R/conditional_formatting.R c799fdbfef94a69869a5c3ae5a2a8f5f *R/data-fontSizeLookupTables.R 2ffb24e255de01cbf9240f33a0f870d4 *R/helperFunctions.R 02cbee7307fdd40737c3c09b7ea709b9 *R/loadWorkbook.R c6673c95be1ed5484336ef2375cdd13c *R/onUnload.R 7ba170cbcd37ad9b51fa54ad9f9ff680 *R/openXL.R 75cfbf10fc07d8fe81b2d624c74df9dd *R/openxlsx-package.R 7c64314e40a8d14df0a9f7d2ff26288e *R/openxlsx.R a4c54d6854a56526080440d6d5289124 *R/openxlsxCoerce.R dd4adab0b92471e60f255c53e92367fd *R/readWorkbook.R 54f6490db5f29507421e1de8601d5c75 *R/sheet_data_class.R 6f1990036b65c51003d24a4bc17198bc *R/sysdata.rda d91747829fb03d4b8886a48ea69c7b15 *R/utils.R b786e6732c9ce1cec2091c4b4a393027 *R/workbook_column_widths.R 8ea06682e0d27e48e89b1bf47647f003 *R/workbook_read_workbook.R 6913ec78106895729b439653888d21a4 *R/workbook_write_data.R 6df271e92451301608e8de73ef464741 *R/worksheet_class.R f1f86635a597a1c02e49ff7254890b6b *R/wrappers.R f4984b0ac75b865db69940204d66c521 *R/writeData.R 8df7c1096ecec9b3696e5cca8c48ce72 *R/writeDataTable.R 8bb38cde620b0b5f4302287e5b99fe80 *R/writexlsx.R dd30c7b1d3710c3efb4e4afce40f6424 *R/zzz.R 09da45628805144d06ceca71629882ae *README.md 6f70825900d686c3ed9ee307e14fbad0 *build/vignette.rds 884ec602987c2e44ef29642b947c9764 *inst/WORDLIST f3a680efd8bb22c5144da7750c43cc7e *inst/doc/Formatting.R 1981b3039d3a126b8bbe2a592140dc54 *inst/doc/Formatting.Rmd e0095ab263e3377e5c381e223c8919d4 *inst/doc/Formatting.html b4122d79e4ac41b97086744612f5d482 *inst/doc/Introduction.R 70e145f1e10533307946e36d66a4c1a5 *inst/doc/Introduction.Rmd 8ec970d6bfac532afbcdce6c527cf1f3 *inst/doc/Introduction.html b882b13f509c96d058883be4eb6664a1 *inst/extdata/ColorTabs3.xlsx fcbfe30b58098920c92bb7c3dce6394d *inst/extdata/build_font_size_lookup.R 13cccf9835335301211ac8d8a4785659 *inst/extdata/cloneEmptyWorksheetExample.xlsx c2f3a10132c34da8e7c70a72f52d06c5 *inst/extdata/cloneWorksheetExample.xlsx d1aec846b3a29c873799d02567eb665c *inst/extdata/conditional_formatting_testing.R 36a7feeb6214d7e79ac8b89df3c45df0 *inst/extdata/einstein.jpg 0c1574a0171de89f03b8cfc5dcd0d0e5 *inst/extdata/groupTest.xlsx 4e2a987f8dc163fe8e53a1fe46757201 *inst/extdata/inlineStr.xlsx fb9a2de7bc2ec82fe52394335d80050d *inst/extdata/loadExample.xlsx 170b968dd1a7c0bbb8ff70ac6a53565a *inst/extdata/loadPivotTables.xlsx a26ff22341de278fefc53dd6baba61e8 *inst/extdata/loadThreadComment.xlsx 8123c907acba826714a9219c82cf1b81 *inst/extdata/load_xlsx_testing.R b880cccb0e6a0573c9107453505ee04a *inst/extdata/namedRegions.xlsx 5c6ee667b971ee565af8d65a715176fe *inst/extdata/namedRegions2.xlsx e02ca6f0caae9cd6dc155c8c76b2c7eb *inst/extdata/namedRegions3.xlsx 1febf7741950a8f461c80f0975895d1e *inst/extdata/readTest.xlsx 87c13e763f8e6097bbdc81159798622f *inst/extdata/read_failure_test.xlsx 49a26eeb294578053af80b4108d88223 *inst/extdata/stack_style_testing.R 601033236770879ad94046af9a917c66 *man/NamedRegion.Rd 201d75364bc6702f97a70e75430f0d5f *man/activeSheet.Rd db31f0557788e83b463c44395100f10d *man/addCreator.Rd ab9f8d7a4f1b8124935418d3c2e33a32 *man/addFilter.Rd 8fad49c0547c247ff51675f67a7c9bda *man/addStyle.Rd b7ae906c2ac1d3a6e290846198b9918c *man/addWorksheet.Rd 8dc26272e0d412b671ba25d4aa83cfc5 *man/all.equal.Rd 3f45620674b5b0c9e807af0bad1db5b3 *man/buildWorkbook.Rd cfda8912ba566dae1307e32bde30cc06 *man/cloneWorksheet.Rd 21196d093fd0b7829b539c43f7bff30c *man/col2int.Rd 3c2917f7e6bdb5b4c01043e26c4c1491 *man/conditionalFormat.Rd 8f62966b09aa1e565c7b181855d08392 *man/conditionalFormatting.Rd a156966c76eba650cc45d0619bcc0b73 *man/convertFromExcelRef.Rd 8a15f9c8822626d078b36fa1639b201e *man/convertToDate.Rd 1e6a8f4d74954dee7301934b95b12edb *man/convertToDateTime.Rd c3715eccf4cd9b7ad6b3a9db006bc2f3 *man/copyWorkbook.Rd b644037d373c762ba5bd41eac5d988a3 *man/createComment.Rd 349ef65772bd5dd2af46787b0d0a8095 *man/createStyle.Rd d878c5235941c0f7e8f5ec0a972c828f *man/createWorkbook.Rd a8af92d66830f85c88278e1abc815e81 *man/dataValidation.Rd ef1ef7b6bf313d47b94fb530346a4f31 *man/deleteData.Rd cb1e46f469cfbbbde29c8b5113e1d789 *man/figures/lifecycle-archived.svg c0d2e5a54f1fa4ff02bf9533079dd1f7 *man/figures/lifecycle-defunct.svg a1b8c987c676c16af790f563f96cbb1f *man/figures/lifecycle-deprecated.svg c3978703d8f40f2679795335715e98f4 *man/figures/lifecycle-experimental.svg 952b59dc07b171b97d5d982924244f61 *man/figures/lifecycle-maturing.svg 27b879bf3677ea76e3991d56ab324081 *man/figures/lifecycle-questioning.svg 46de21252239c5a23d400eae83ec6b2d *man/figures/lifecycle-retired.svg 6902bbfaf963fbc4ed98b86bda80caa2 *man/figures/lifecycle-soft-deprecated.svg 53b3f893324260b737b3c46ed2a0e643 *man/figures/lifecycle-stable.svg 9935549e5ab436e1a14b39aa6eb5f586 *man/figures/tableoptions.pdf 57f30d30484a5402e9fce7539de1d3a6 *man/figures/tableoptions.png cf725539a0af56f8bc8a369a427d6baf *man/freezePane.Rd 67735a734cb5c0c128e8fec4a756d68a *man/getBaseFont.Rd b310cd3548869e1339d753b67b09ad9a *man/getCellRefs.Rd 6cb16b0bc210abd402778981e7f172cc *man/getCreators.Rd f49d5a7c434c95d540722178465ca667 *man/getDateOrigin.Rd a9ccd1c9ca1e9a9736e480ec5eadb956 *man/getNamedRegions.Rd 2bbe613bd2499b19f57f3af23b8551a5 *man/getSheetNames.Rd cbb19a2f8637b078396fff57869f1e3a *man/getStyles.Rd 6df5ff236ad412d5c08786a0dc9ed570 *man/getTables.Rd 66068cbe15a1171e134fa845fe9aad50 *man/groupColumns.Rd de41af2bae48efc4e279de49372d4c2d *man/groupRows.Rd 5a9c0e0d2b902e22c6e2636e0fbfb469 *man/if_null_then.Rd 5f63b3e5a1cb5de5812462596634e26e *man/insertImage.Rd 272092de7c811c474ea8c8a03e30930b *man/insertPlot.Rd b90ce05bbbc26cfeb2e2a3a12af2a0f4 *man/int2col.Rd bedc98880c781aeede2fef0d137d9d5a *man/loadWorkbook.Rd b4a84457c38a921a957c9f366f7a5e99 *man/makeHyperlinkString.Rd b265302ac812bf3c97028aced1270672 *man/mergeCells.Rd 7559c893eb5dcc65fcb92d167a530ef1 *man/modifyBaseFont.Rd ba6ec5f20adc33de28a0b129ab57413d *man/names.Rd 981ff02212d4411b668dba1bf4c23da1 *man/openXL.Rd bd9922d48bd0718fb624db4ea10f5892 *man/openxlsx.Rd eec24e942c67ca1a5e6fa14ed1dad963 *man/openxlsxFontSizeLookupTable.Rd 373a091615fcd179eae12841c9c2f95a *man/openxlsx_options.Rd aaba9eaa1b5e8168ee80baacf5283688 *man/pageBreak.Rd d859f1f0d965eb91ce5222a2ef82fbae *man/pageSetup.Rd 9ef1e7585598f3294679bdbf9ac1aad1 *man/protectWorkbook.Rd 9dce795724ca32d33ad602b2673529f3 *man/protectWorksheet.Rd f88559e262101108a6073239f20bd30e *man/read.xlsx.Rd f56372d5b220b4c7d444e9f42e1d5c76 *man/readWorkbook.Rd 8203b44baacc245cf5d7e553a3fbc456 *man/removeCellMerge.Rd 6edc9c6d0a1ca90f88ef45d382987bc7 *man/removeColWidths.Rd 3711c4fa3337fad7f8742ff5b53c4eb8 *man/removeComment.Rd 7e0d1b7fa9f21e44cb73401d97dab329 *man/removeFilter.Rd 4bbd5ff732df302077aa6d492be4ac92 *man/removeRowHeights.Rd b7188f23e0e0005906ee6fc9b01c5992 *man/removeTable.Rd 9a84fe734b6890b50e862774d05c7360 *man/removeWorksheet.Rd aa612794bdaf9598886a43d783211bb2 *man/renameWorksheet.Rd d64d9803f64d9349f5e508e8ad69ae44 *man/replaceStyle.Rd aca546aa250ae4ced4a5e3c23bb2daba *man/saveWorkbook.Rd 49004cedce652b5da38d07e6a44be06b *man/setColWidths.Rd bcb40bbc971d2a7e378cd42f54af4d3c *man/setFooter.Rd b9d60468d109790a3948635b549c4ee6 *man/setHeader.Rd 2d7ed92dd56f9825aa2c556584242fe0 *man/setHeaderFooter.Rd da9ab876544aa914fb7862150a1fc9de *man/setLastModifiedBy.Rd edad5e0cdc25d0eb517c5f26856b1b78 *man/setRowHeights.Rd 72f77db7f39906a610094601e964a845 *man/sheetVisibility.Rd 631c2cce5a711681e5c0478b93fc33ce *man/sheetVisible.Rd 33f085772350659ac5624c630772ddd4 *man/sheets.Rd e2ea549ccb5bcf2d47a8590c74bd94c7 *man/showGridLines.Rd 5961a3c08016954dd0fcc8d9af167d91 *man/temp_xlsx.Rd 1ca0c630b199daf2ee4d0a6f406b75fc *man/ungroupColumns.Rd 19e75905f7c9ed4a5ba17b0807a55e85 *man/ungroupRows.Rd a1c65d7381d181ab2c2ebf8cdca45c1b *man/worksheetOrder.Rd 478e471f082b8ba3b084df9e1a2a0aa1 *man/write.xlsx.Rd 4e1756633e50241f104e0e4eb9d22ba2 *man/writeComment.Rd a36c701ba39ae3d8114d7477074f3877 *man/writeData.Rd 24b7b5b73623aa115df349f73d99fdfd *man/writeDataTable.Rd 1515052e58b0af77686ed23acec527db *man/writeFormula.Rd 458ccf3508f0ddf06fb3ccb5651823ce *src/RcppExports.cpp 71289967be1025e3cff58b2edbe0fc77 *src/helper_functions.cpp 08842fbf636f2398a534d98f615436de *src/load_workbook.cpp 1385cba6d8f3abe5f80618b30fcd938b *src/openxlsx.h 0873c1a2b23aab7079327c020b886ce3 *src/read_workbook.cpp c2ca7900d8ce0d25dfa73382dc8bd2af *src/write_data.cpp afb6d0ce468e4b16135d8bd4bee5e0d8 *src/write_file.cpp 484d69b3a6c975fdc692e9eb3015afe9 *src/write_file_2.cpp 1491151710e8a3fec494f98ba7e0676f *tests/testthat.R 0969e0aa603aa0583491c7f3bcb64088 *tests/testthat/test-CommentClass.R 9bd237c329f1505c9aa5b98b8d06b10f *tests/testthat/test-Workbook_properties.R c7b68c2fad641fb94acdeeb30dfef307 *tests/testthat/test-Worksheet_naming.R a670de0581cb1c63244228e569165312 *tests/testthat/test-activeSheet.R ac77ac309103fda2d377f4296e626fa4 *tests/testthat/test-border_parsing.R b9988de26cddcc866264aaeb45b73ac1 *tests/testthat/test-build_workbook.R 1a70132af6cd1aab8a6555cd7ab1f005 *tests/testthat/test-cloneWorksheet.R ec606a79126ff7d8e470623f9f206481 *tests/testthat/test-conditionalFormatting.R 51043229822f11cf0422e370771b9a5e *tests/testthat/test-date_time_conversion.R 77b3a586f551a1c14b416a62fc2cdbb8 *tests/testthat/test-deleting_tables.R 24ef609bad59fa48e121c4c05a1cfa3e *tests/testthat/test-encoding.R c938f37ea8f4db1080a6b0057c4c0fc1 *tests/testthat/test-fill_merged_cells.R b27eced92ee53b5af4fd588e386f9e4d *tests/testthat/test-fontSizeLookupTables.R dabb92e75814130a91a7b3f2abb63465 *tests/testthat/test-freeze_pane.R 93cabb37e8784313b0d2c25b35801787 *tests/testthat/test-getBaseFont.R a878e98afb87801091507be0ed7e8166 *tests/testthat/test-getCellRefs.R 823519e66b4b358fc5d5a60e2b74c617 *tests/testthat/test-load_read_file_read_equality.R 11e9ed4b84093d02ff158f4a7bd148fa *tests/testthat/test-loading_workbook.R 8c1ecfd6d2d4a5bc93b4da82ab8c84ae *tests/testthat/test-loading_workbook_tables.R 82e0d3ed6c305eed1e99baae96efdc58 *tests/testthat/test-loading_workbook_unzipped.R 7986491afe5e6d1e936b8c0091bcd052 *tests/testthat/test-named_regions.R 64fff1439a519d971e67960bdc01fb89 *tests/testthat/test-options.R 66ba479cd36c2a6fdeadb0b526f69af2 *tests/testthat/test-outlines.R a11acbf3cfd1b047156ecfef15e696bd *tests/testthat/test-page_setup.R 8009d31ef34c1fe500f810f00a9aada3 *tests/testthat/test-protect-workbook.R 338598a46888c8a3e64fc9ed42e053ce *tests/testthat/test-protect-worksheet.R 21f5694833298eac112c21d0d0fd7361 *tests/testthat/test-read_from_created_wb.R 8ef5acf08323d812f27aedde83394a9e *tests/testthat/test-read_from_loaded_workbook.R cc167dce938b0f1afe9205503e8dc6d7 *tests/testthat/test-read_sources.R 6751e19b2982fbbeba85ccd8a3f172d2 *tests/testthat/test-read_write_logicals.R a18369b0ad2df625c983b1d1f55bc99a *tests/testthat/test-read_xlsx_correct_sheet.R 1fbe6ba77f4727b1f310b10794bb9575 *tests/testthat/test-read_xlsx_random_seed.R 0a296bbfe3198fd5948f769650333098 *tests/testthat/test-remove_worksheets.R e21571984e2ae29b02beb23b14b8b16c *tests/testthat/test-saveWorkbook.R 54bebd50ca1d668af69cc0d458743ea1 *tests/testthat/test-skip_empty_cols.R 179a3782f7a1e55d7d4c24ec83df3bbb *tests/testthat/test-skip_empty_rows.R 9f3b6630177ce77ac5755d4144d28cb4 *tests/testthat/test-styles.R 06e0d2a8da3c7647c19bdafdc63f6e17 *tests/testthat/test-table_overlaps.R 7685508da3dd7844060f87c79e6c0764 *tests/testthat/test-trying_to_break_openxlsx.R f400fa5db71c9ab82875f2821f29a94f *tests/testthat/test-v3_0_0_bugs.R d937626a3feced240f4a67d71e6e47fa *tests/testthat/test-validate_table_name.R fde352f1312b4b66fafe569e318abe44 *tests/testthat/test-worksheet_ordering.R 8de4228f95a3af0a4b91f4e11cab9ef7 *tests/testthat/test-worksheet_renaming.R 9e8b382c6803a05cd800dd5639c874bf *tests/testthat/test-wrappers.R b1f494ece93839f4c3660fa3b19f0815 *tests/testthat/test-write-permissions.R 9fe1311efe2df719ddea19d307d0702c *tests/testthat/test-writeData.R 30e75874906d40b5568209abd1ecbdf5 *tests/testthat/test-write_data_to_sheetData.R 149627a0393b95d927e2ba2b01abf2fe *tests/testthat/test-write_data_to_sheetData_NAs.R 869e72a4da891348f2d600d420da57aa *tests/testthat/test-write_read_equality.R 600c9e014924e16107ca3f176a76db62 *tests/testthat/test-write_xlsx_vector_args.R 34be2e6e1b5b86ba7a012c3698c7ce6b *tests/testthat/test-writing_posixct.R 2cf18ac5314d141ab2932a007a19650c *tests/testthat/test-writing_sheet_data.R 1981b3039d3a126b8bbe2a592140dc54 *vignettes/Formatting.Rmd 70e145f1e10533307946e36d66a4c1a5 *vignettes/Introduction.Rmd e37f875bb932ea389ed1a8abe3405ccf *vignettes/tableStyles.PNG openxlsx/inst/0000755000176200001440000000000014155741777013131 5ustar liggesusersopenxlsx/inst/doc/0000755000176200001440000000000014155741777013676 5ustar liggesusersopenxlsx/inst/doc/Introduction.Rmd0000644000176200001440000004067114155600364017015 0ustar liggesusers--- title: "Introduction" author: "Alexander Walker, Philipp Schauberger" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Introduction} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Basic Examples ### write.xlsx The simplest way to write to a workbook is write.xlsx(). By default, write.xlsx calls writeData. If asTable is TRUE write.xlsx will write x as an Excel table. ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} ## write to working directory library(openxlsx) write.xlsx(iris, file = "writeXLSX1.xlsx") write.xlsx(iris, file = "writeXLSXTable1.xlsx", asTable = TRUE) ``` ### write list of data.frames to xlsx-file ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} ## write a list of data.frames to individual worksheets using list names as worksheet names l <- list("IRIS" = iris, "MTCARS" = mtcars) write.xlsx(l, file = "writeXLSX2.xlsx") write.xlsx(l, file = "writeXLSXTable2.xlsx", asTable = TRUE) ``` ### write.xlsx also accepts styling parameters #### The simplest way is to set default options and set column class ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} options("openxlsx.borderColour" = "#4F80BD") options("openxlsx.borderStyle" = "thin") options("openxlsx.dateFormat" = "mm/dd/yyyy") options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") options("openxlsx.numFmt" = NULL) ## For default style rounding of numeric columns df <- data.frame("Date" = Sys.Date()-0:19, "LogicalT" = TRUE, "Time" = Sys.time()-0:19*60*60, "Cash" = paste("$",1:20), "Cash2" = 31:50, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(0, 1, length.out=20), "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE) class(df$Cash) <- "currency" class(df$Cash2) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumbers) <- "scientific" write.xlsx(df, "writeXLSX3.xlsx") write.xlsx(df, file = "writeXLSXTable3.xlsx", asTable = TRUE) ``` ## Workbook styles ### define a style for column headers ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", halign = "center", valign = "center", textDecoration = "Bold", border = "TopBottomLeftRight", textRotation = 45) write.xlsx(iris, file = "writeXLSX4.xlsx", borders = "rows", headerStyle = hs) write.xlsx(iris, file = "writeXLSX5.xlsx", borders = "columns", headerStyle = hs) write.xlsx(iris, "writeXLSXTable4.xlsx", asTable = TRUE, headerStyle = createStyle(textRotation = 45)) ``` ### When writing a list, the stylings will apply to all list elements ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} l <- list("IRIS" = iris, "colClasses" = df) write.xlsx(l, file = "writeXLSX6.xlsx", borders = "columns", headerStyle = hs) write.xlsx(l, file = "writeXLSXTable5.xlsx", asTable = TRUE, tableStyle = "TableStyleLight2") openXL("writeXLSX6.xlsx") openXL("writeXLSXTable5.xlsx") ``` ### write.xlsx returns the workbook object for further editing ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} wb <- write.xlsx(iris, "writeXLSX6.xlsx") setColWidths(wb, sheet = 1, cols = 1:5, widths = 20) saveWorkbook(wb, "writeXLSX6.xlsx", overwrite = TRUE) ``` ## Workbook creation walk-through ### create workbook and set default border Colour and style ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} require(ggplot2) wb <- createWorkbook() options("openxlsx.borderColour" = "#4F80BD") options("openxlsx.borderStyle" = "thin") modifyBaseFont(wb, fontSize = 10, fontName = "Arial Narrow") ``` ### Add Sheets ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} addWorksheet(wb, sheetName = "Motor Trend Car Road Tests", gridLines = FALSE) addWorksheet(wb, sheetName = "Iris", gridLines = FALSE) ``` ### write data to sheet 1 ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} freezePane(wb, sheet = 1, firstRow = TRUE, firstCol = TRUE) ## freeze first row and column writeDataTable(wb, sheet = 1, x = mtcars, colNames = TRUE, rowNames = TRUE, tableStyle = "TableStyleLight9") setColWidths(wb, sheet = 1, cols = "A", widths = 18) ``` ### write data to sheet 2 iris data.frame is added as excel table on sheet 2. ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} writeDataTable(wb, sheet = 2, iris, startCol = "K", startRow = 2) qplot(data=iris, x = Sepal.Length, y= Sepal.Width, colour = Species) insertPlot(wb, 2, xy=c("B", 16)) ## insert plot at cell B16 means <- aggregate(x = iris[,-5], by = list(iris$Species), FUN = mean) vars <- aggregate(x = iris[,-5], by = list(iris$Species), FUN = var) ``` ### add write group means ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} headSty <- createStyle(fgFill="#DCE6F1", halign="center", border = "TopBottomLeftRight") writeData(wb, 2, x = "Iris dataset group means", startCol = 2, startRow = 2) writeData(wb, 2, x = means, startCol = "B", startRow=3, borders="rows", headerStyle = headSty) ``` ### add write group variances ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} writeData(wb, 2, x = "Iris dataset group variances", startCol = 2, startRow = 9) writeData(wb, 2, x= vars, startCol = "B", startRow=10, borders="columns", headerStyle = headSty) setColWidths(wb, 2, cols=2:6, widths = 12) ## width is recycled for each col setColWidths(wb, 2, cols=11:15, widths = 15) ``` ### add style mean & variance table headers ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} s1 <- createStyle(fontSize=14, textDecoration=c("bold", "italic")) addStyle(wb, 2, style = s1, rows=c(2,9), cols=c(2,2)) ``` ### save workbook ```{r include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} saveWorkbook(wb, "basics.xlsx", overwrite = TRUE) ## save to working directory ``` ## Gallery ```{r eval=FALSE, include=TRUE} ## inspired by xtable gallery #https://CRAN.R-project.org/package=xtable/vignettes/xtableGallery.pdf ## Create a new workbook wb <- createWorkbook() data(tli, package = "xtable") ## data.frame test.n <- "data.frame" my.df <- tli[1:10, ] addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.df, borders = "n") ## matrix test.n <- "matrix" design.matrix <- model.matrix(~ sex * grade, data = my.df) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = design.matrix) ## aov test.n <- "aov" fm1 <- aov(tlimth ~ sex + ethnicty + grade + disadvg, data = tli) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = fm1) ## lm test.n <- "lm" fm2 <- lm(tlimth ~ sex*ethnicty, data = tli) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = fm2) ## anova 1 test.n <- "anova" my.anova <- anova(fm2) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.anova) ## anova 2 test.n <- "anova2" fm2b <- lm(tlimth ~ ethnicty, data = tli) my.anova2 <- anova(fm2b, fm2) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.anova2) ## glm test.n <- "glm" fm3 <- glm(disadvg ~ ethnicty*grade, data = tli, family = binomial()) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = fm3) ## prcomp test.n <- "prcomp" pr1 <- prcomp(USArrests) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = pr1) ## summary.prcomp test.n <- "summary.prcomp" addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = summary(pr1)) ## simple table test.n <- "table" data(airquality) airquality$OzoneG80 <- factor(airquality$Ozone > 80, levels = c(FALSE, TRUE), labels = c("Oz <= 80", "Oz > 80")) airquality$Month <- factor(airquality$Month, levels = 5:9, labels = month.abb[5:9]) my.table <- with(airquality, table(OzoneG80,Month) ) addWorksheet(wb = wb, sheetName = test.n) writeData(wb = wb, sheet = test.n, x = my.table) ## survdiff 1 library(survival) test.n <- "survdiff1" addWorksheet(wb = wb, sheetName = test.n) x <- survdiff(Surv(futime, fustat) ~ rx, data = ovarian) writeData(wb = wb, sheet = test.n, x = x) ## survdiff 2 test.n <- "survdiff2" addWorksheet(wb = wb, sheetName = test.n) expect <- survexp(futime ~ ratetable(age=(accept.dt - birth.dt), sex=1,year=accept.dt,race="white"), jasa, cohort=FALSE, ratetable=survexp.usr) x <- survdiff(Surv(jasa$futime, jasa$fustat) ~ offset(expect)) writeData(wb = wb, sheet = test.n, x = x) ## coxph 1 test.n <- "coxph1" addWorksheet(wb = wb, sheetName = test.n) bladder$rx <- factor(bladder$rx, labels = c("Pla","Thi")) x <- coxph(Surv(stop,event) ~ rx, data = bladder) writeData(wb = wb, sheet = test.n, x = x) ## coxph 2 test.n <- "coxph2" addWorksheet(wb = wb, sheetName = test.n) x <- coxph(Surv(stop,event) ~ rx + cluster(id), data = bladder) writeData(wb = wb, sheet = test.n, x = x) ## cox.zph test.n <- "cox.zph" addWorksheet(wb = wb, sheetName = test.n) x <- cox.zph(coxph(Surv(futime, fustat) ~ age + ecog.ps, data=ovarian)) writeData(wb = wb, sheet = test.n, x = x) ## summary.coxph 1 test.n <- "summary.coxph1" addWorksheet(wb = wb, sheetName = test.n) x <- summary(coxph(Surv(stop,event) ~ rx, data = bladder)) writeData(wb = wb, sheet = test.n, x = x) ## summary.coxph 2 test.n <- "summary.coxph2" addWorksheet(wb = wb, sheetName = test.n) x <- summary(coxph(Surv(stop,event) ~ rx + cluster(id), data = bladder)) writeData(wb = wb, sheet = test.n, x = x) ## view without saving openXL(wb) ``` ## Further Examples ### Stock Price ```{r eval=FALSE, include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE} require(ggplot2) wb <- createWorkbook() ## read historical prices from yahoo finance ticker <- "CBA.AX" csv.url <- paste0("https://query1.finance.yahoo.com/v7/finance/download/", ticker, "?period1=1597597610&period2=1629133610&interval=1d&events=history&includeAdjustedClose=true") prices <- read.csv(url(csv.url), as.is = TRUE) prices$Date <- as.Date(prices$Date) close <- prices$Close prices$logReturns = c(0, log(close[2:length(close)]/close[1:(length(close)-1)])) ## Create plot of price series and add to worksheet ggplot(data = prices, aes(as.Date(Date), as.numeric(Close))) + geom_line(colour="royalblue2") + labs(x = "Date", y = "Price", title = ticker) + geom_area(fill = "royalblue1",alpha = 0.3) + coord_cartesian(ylim=c(min(prices$Close)-1.5, max(prices$Close)+1.5)) ## Add worksheet and write plot to sheet addWorksheet(wb, sheetName = "CBA") insertPlot(wb, sheet = 1, xy = c("J", 3)) ## Histogram of log returns ggplot(data = prices, aes(x = logReturns)) + geom_bar(binwidth=0.0025) + labs(title = "Histogram of log returns") ## currency class(prices$Close) <- "currency" ## styles as currency in workbook ## write historical data and histogram of returns writeDataTable(wb, sheet = "CBA", x = prices) insertPlot(wb, sheet = 1, startRow=25, startCol = "J") ## Add conditional formatting to show where logReturn > 0.01 using default style conditionalFormat(wb, sheet = 1, cols = 1:ncol(prices), rows = 2:(nrow(prices)+1), rule = "$H2 > 0.01") ## style log return col as a percentage logRetStyle <- createStyle(numFmt = "percentage") addStyle(wb, 1, style = logRetStyle, rows = 2:(nrow(prices) + 1), cols = "H", gridExpand = TRUE) setColWidths(wb, sheet=1, cols = c("A", "F", "G", "H"), widths = 15) ## save workbook to working directory saveWorkbook(wb, "stockPrice.xlsx", overwrite = TRUE) openXL("stockPrice.xlsx") ``` ### Image Compression using PCA ```{r eval=FALSE, include=TRUE} require(openxlsx) require(jpeg) require(ggplot2) plotFn <- function(x, ...){ colvec <- grey(x) colmat <- array(match(colvec, unique(colvec)), dim = dim(x)[1:2]) image(x = 0:(dim(colmat)[2]), y = 0:(dim(colmat)[1]), z = t(colmat[nrow(colmat):1, ]), col = unique(colvec), xlab = "", ylab = "", axes = FALSE, asp = 1, bty ="n", frame.plot=F, ann=FALSE) } ## Create workbook and add a worksheet, hide gridlines wb <- createWorkbook("Einstein") addWorksheet(wb, "Original Image", gridLines = FALSE) A <- readJPEG(file.path(path.package("openxlsx"), "einstein.jpg")) height <- nrow(A); width <- ncol(A) ## write "Original Image" to cell B2 writeData(wb, 1, "Original Image", xy = c(2,2)) ## write Object size to cell B3 writeData(wb, 1, sprintf("Image object size: %s bytes", format(object.size(A+0)[[1]], big.mark=',')), xy = c(2,3)) ## equivalent to startCol = 2, startRow = 3 ## Plot image par(mar=rep(0, 4), xpd = NA); plotFn(A) ## insert plot currently showing in plot window insertPlot(wb, 1, width, height, units="px", startRow= 5, startCol = 2) ## SVD of covariance matrix rMeans <- rowMeans(A) rowMeans <- do.call("cbind", lapply(1:ncol(A), function(X) rMeans)) A <- A - rowMeans E <- svd(A %*% t(A) / (ncol(A) - 1)) # SVD on covariance matrix of A pve <- data.frame("Eigenvalues" = E$d, "PVE" = E$d/sum(E$d), "Cumulative PVE" = cumsum(E$d/sum(E$d))) ## write eigenvalues to worksheet addWorksheet(wb, "Principal Component Analysis") hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", halign = "CENTER", textDecoration = "Bold", border = "TopBottomLeftRight", borderColour = "#4F81BD") writeData(wb, 2, x="Proportions of variance explained by Eigenvector" ,startRow = 2) mergeCells(wb, sheet=2, cols=1:4, rows=2) setColWidths(wb, 2, cols = 1:3, widths = c(14, 12, 15)) writeData(wb, 2, x=pve, startRow = 3, startCol = 1, borders="rows", headerStyle=hs) ## Plots pve <- cbind(pve, "Ind" = 1:nrow(pve)) ggplot(data = pve[1:20,], aes(x = Ind, y = 100*PVE)) + geom_bar(stat="identity", position = "dodge") + xlab("Principal Component Index") + ylab("Proportion of Variance Explained") + geom_line(size = 1, col = "blue") + geom_point(size = 3, col = "blue") ## Write plot to worksheet 2 insertPlot(wb, 2, width = 5, height = 4, startCol = "E", startRow = 2) ## Plot of cumulative explained variance ggplot(data = pve[1:50,], aes(x = Ind, y = 100*Cumulative.PVE)) + geom_point(size=2.5) + geom_line(size=1) + xlab("Number of PCs") + ylab("Cumulative Proportion of Variance Explained") insertPlot(wb, 2, width = 5, height = 4, xy= c("M", 2)) ## Reconstruct image using increasing number of PCs nPCs <- c(5, 7, 12, 20, 50, 200) startRow <- rep(c(2, 24), each = 3) startCol <- rep(c("B", "H", "N"), 2) ## create a worksheet to save reconstructed images to addWorksheet(wb, "Reconstructed Images", zoom = 90) for(i in 1:length(nPCs)){ V <- E$v[, 1:nPCs[i]] imgHat <- t(V) %*% A ## project img data on to PCs imgSize <- object.size(V) + object.size(imgHat) + object.size(rMeans) imgHat <- V %*% imgHat + rowMeans ## reconstruct from PCs and add back row means imgHat <- round((imgHat - min(imgHat)) / (max(imgHat) - min(imgHat))*255) # scale plotFn(imgHat/255) ## write strings to worksheet 3 writeData(wb, "Reconstructed Images", sprintf("Number of principal components used: %s", nPCs[[i]]), startCol[i], startRow[i]) writeData(wb, "Reconstructed Images", sprintf("Sum of component object sizes: %s bytes", format(as.numeric(imgSize), big.mark=',')), startCol[i], startRow[i]+1) ## write reconstruced image insertPlot(wb, "Reconstructed Images", width, height, units="px", xy = c(startCol[i], startRow[i]+3)) } # hide grid lines showGridLines(wb, sheet = 3, showGridLines = FALSE) ## Make text above images BOLD boldStyle <- createStyle(textDecoration="BOLD") ## only want to apply style to specified cells (not all combinations of rows & cols) addStyle(wb, "Reconstructed Images", style=boldStyle, rows = c(startRow, startRow+1), cols = rep(startCol, 2), gridExpand = FALSE) ## save workbook to working directory saveWorkbook(wb, "Image dimensionality reduction.xlsx", overwrite = TRUE) ## remove example files for cran test if (identical(Sys.getenv("NOT_CRAN", unset = "true"), "false")) { file_list<-list.files(pattern="\\.xlsx",recursive = T) file_list<-fl[!grepl("inst/extdata",file_list)&!grepl("man/",file_list)] if(length(file_list)>0){ rm(file_list) } ``` openxlsx/inst/doc/Introduction.R0000644000176200001440000004107214155741776016505 0ustar liggesusers## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # ## write to working directory # library(openxlsx) # write.xlsx(iris, file = "writeXLSX1.xlsx") # write.xlsx(iris, file = "writeXLSXTable1.xlsx", asTable = TRUE) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # ## write a list of data.frames to individual worksheets using list names as worksheet names # l <- list("IRIS" = iris, "MTCARS" = mtcars) # write.xlsx(l, file = "writeXLSX2.xlsx") # write.xlsx(l, file = "writeXLSXTable2.xlsx", asTable = TRUE) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # options("openxlsx.borderColour" = "#4F80BD") # options("openxlsx.borderStyle" = "thin") # options("openxlsx.dateFormat" = "mm/dd/yyyy") # options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") # options("openxlsx.numFmt" = NULL) ## For default style rounding of numeric columns # # df <- data.frame("Date" = Sys.Date()-0:19, "LogicalT" = TRUE, # "Time" = Sys.time()-0:19*60*60, # "Cash" = paste("$",1:20), "Cash2" = 31:50, # "hLink" = "https://CRAN.R-project.org/", # "Percentage" = seq(0, 1, length.out=20), # "TinyNumbers" = runif(20) / 1E9, stringsAsFactors = FALSE) # # class(df$Cash) <- "currency" # class(df$Cash2) <- "accounting" # class(df$hLink) <- "hyperlink" # class(df$Percentage) <- "percentage" # class(df$TinyNumbers) <- "scientific" # # write.xlsx(df, "writeXLSX3.xlsx") # write.xlsx(df, file = "writeXLSXTable3.xlsx", asTable = TRUE) # # ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", # halign = "center", valign = "center", textDecoration = "Bold", # border = "TopBottomLeftRight", textRotation = 45) # # write.xlsx(iris, file = "writeXLSX4.xlsx", borders = "rows", headerStyle = hs) # write.xlsx(iris, file = "writeXLSX5.xlsx", borders = "columns", headerStyle = hs) # # write.xlsx(iris, "writeXLSXTable4.xlsx", asTable = TRUE, # headerStyle = createStyle(textRotation = 45)) # # ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # l <- list("IRIS" = iris, "colClasses" = df) # write.xlsx(l, file = "writeXLSX6.xlsx", borders = "columns", headerStyle = hs) # write.xlsx(l, file = "writeXLSXTable5.xlsx", asTable = TRUE, tableStyle = "TableStyleLight2") # # openXL("writeXLSX6.xlsx") # openXL("writeXLSXTable5.xlsx") ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # wb <- write.xlsx(iris, "writeXLSX6.xlsx") # setColWidths(wb, sheet = 1, cols = 1:5, widths = 20) # saveWorkbook(wb, "writeXLSX6.xlsx", overwrite = TRUE) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # require(ggplot2) # wb <- createWorkbook() # options("openxlsx.borderColour" = "#4F80BD") # options("openxlsx.borderStyle" = "thin") # modifyBaseFont(wb, fontSize = 10, fontName = "Arial Narrow") ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # addWorksheet(wb, sheetName = "Motor Trend Car Road Tests", gridLines = FALSE) # addWorksheet(wb, sheetName = "Iris", gridLines = FALSE) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # freezePane(wb, sheet = 1, firstRow = TRUE, firstCol = TRUE) ## freeze first row and column # writeDataTable(wb, sheet = 1, x = mtcars, # colNames = TRUE, rowNames = TRUE, # tableStyle = "TableStyleLight9") # # setColWidths(wb, sheet = 1, cols = "A", widths = 18) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # writeDataTable(wb, sheet = 2, iris, startCol = "K", startRow = 2) # # qplot(data=iris, x = Sepal.Length, y= Sepal.Width, colour = Species) # insertPlot(wb, 2, xy=c("B", 16)) ## insert plot at cell B16 # # means <- aggregate(x = iris[,-5], by = list(iris$Species), FUN = mean) # vars <- aggregate(x = iris[,-5], by = list(iris$Species), FUN = var) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # headSty <- createStyle(fgFill="#DCE6F1", halign="center", border = "TopBottomLeftRight") # writeData(wb, 2, x = "Iris dataset group means", startCol = 2, startRow = 2) # writeData(wb, 2, x = means, startCol = "B", startRow=3, borders="rows", headerStyle = headSty) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # writeData(wb, 2, x = "Iris dataset group variances", startCol = 2, startRow = 9) # writeData(wb, 2, x= vars, startCol = "B", startRow=10, borders="columns", # headerStyle = headSty) # # setColWidths(wb, 2, cols=2:6, widths = 12) ## width is recycled for each col # setColWidths(wb, 2, cols=11:15, widths = 15) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # s1 <- createStyle(fontSize=14, textDecoration=c("bold", "italic")) # addStyle(wb, 2, style = s1, rows=c(2,9), cols=c(2,2)) ## ----include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------------------- # saveWorkbook(wb, "basics.xlsx", overwrite = TRUE) ## save to working directory ## ----eval=FALSE, include=TRUE------------------------------------------------- # ## inspired by xtable gallery # #https://CRAN.R-project.org/package=xtable/vignettes/xtableGallery.pdf # # ## Create a new workbook # wb <- createWorkbook() # data(tli, package = "xtable") # # ## data.frame # test.n <- "data.frame" # my.df <- tli[1:10, ] # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = my.df, borders = "n") # # ## matrix # test.n <- "matrix" # design.matrix <- model.matrix(~ sex * grade, data = my.df) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = design.matrix) # # ## aov # test.n <- "aov" # fm1 <- aov(tlimth ~ sex + ethnicty + grade + disadvg, data = tli) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = fm1) # # ## lm # test.n <- "lm" # fm2 <- lm(tlimth ~ sex*ethnicty, data = tli) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = fm2) # # ## anova 1 # test.n <- "anova" # my.anova <- anova(fm2) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = my.anova) # # ## anova 2 # test.n <- "anova2" # fm2b <- lm(tlimth ~ ethnicty, data = tli) # my.anova2 <- anova(fm2b, fm2) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = my.anova2) # # ## glm # test.n <- "glm" # fm3 <- glm(disadvg ~ ethnicty*grade, data = tli, family = binomial()) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = fm3) # # ## prcomp # test.n <- "prcomp" # pr1 <- prcomp(USArrests) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = pr1) # # ## summary.prcomp # test.n <- "summary.prcomp" # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = summary(pr1)) # # ## simple table # test.n <- "table" # data(airquality) # airquality$OzoneG80 <- factor(airquality$Ozone > 80, # levels = c(FALSE, TRUE), # labels = c("Oz <= 80", "Oz > 80")) # airquality$Month <- factor(airquality$Month, # levels = 5:9, # labels = month.abb[5:9]) # my.table <- with(airquality, table(OzoneG80,Month) ) # addWorksheet(wb = wb, sheetName = test.n) # writeData(wb = wb, sheet = test.n, x = my.table) # # ## survdiff 1 # library(survival) # test.n <- "survdiff1" # addWorksheet(wb = wb, sheetName = test.n) # x <- survdiff(Surv(futime, fustat) ~ rx, data = ovarian) # writeData(wb = wb, sheet = test.n, x = x) # # ## survdiff 2 # test.n <- "survdiff2" # addWorksheet(wb = wb, sheetName = test.n) # expect <- survexp(futime ~ ratetable(age=(accept.dt - birth.dt), # sex=1,year=accept.dt,race="white"), jasa, cohort=FALSE, # ratetable=survexp.usr) # x <- survdiff(Surv(jasa$futime, jasa$fustat) ~ offset(expect)) # writeData(wb = wb, sheet = test.n, x = x) # # ## coxph 1 # test.n <- "coxph1" # addWorksheet(wb = wb, sheetName = test.n) # bladder$rx <- factor(bladder$rx, labels = c("Pla","Thi")) # x <- coxph(Surv(stop,event) ~ rx, data = bladder) # writeData(wb = wb, sheet = test.n, x = x) # # ## coxph 2 # test.n <- "coxph2" # addWorksheet(wb = wb, sheetName = test.n) # x <- coxph(Surv(stop,event) ~ rx + cluster(id), data = bladder) # writeData(wb = wb, sheet = test.n, x = x) # # ## cox.zph # test.n <- "cox.zph" # addWorksheet(wb = wb, sheetName = test.n) # x <- cox.zph(coxph(Surv(futime, fustat) ~ age + ecog.ps, data=ovarian)) # writeData(wb = wb, sheet = test.n, x = x) # # ## summary.coxph 1 # test.n <- "summary.coxph1" # addWorksheet(wb = wb, sheetName = test.n) # x <- summary(coxph(Surv(stop,event) ~ rx, data = bladder)) # writeData(wb = wb, sheet = test.n, x = x) # # ## summary.coxph 2 # test.n <- "summary.coxph2" # addWorksheet(wb = wb, sheetName = test.n) # x <- summary(coxph(Surv(stop,event) ~ rx + cluster(id), data = bladder)) # writeData(wb = wb, sheet = test.n, x = x) # # ## view without saving # openXL(wb) # ## ----eval=FALSE, include=TRUE,tidy=TRUE, eval = FALSE ,highlight=TRUE--------- # require(ggplot2) # # wb <- createWorkbook() # # ## read historical prices from yahoo finance # ticker <- "CBA.AX" # csv.url <- paste0("https://query1.finance.yahoo.com/v7/finance/download/", # ticker, "?period1=1597597610&period2=1629133610&interval=1d&events=history&includeAdjustedClose=true") # prices <- read.csv(url(csv.url), as.is = TRUE) # prices$Date <- as.Date(prices$Date) # close <- prices$Close # prices$logReturns = c(0, log(close[2:length(close)]/close[1:(length(close)-1)])) # # ## Create plot of price series and add to worksheet # ggplot(data = prices, aes(as.Date(Date), as.numeric(Close))) + # geom_line(colour="royalblue2") + # labs(x = "Date", y = "Price", title = ticker) + # geom_area(fill = "royalblue1",alpha = 0.3) + # coord_cartesian(ylim=c(min(prices$Close)-1.5, max(prices$Close)+1.5)) # # ## Add worksheet and write plot to sheet # addWorksheet(wb, sheetName = "CBA") # insertPlot(wb, sheet = 1, xy = c("J", 3)) # # ## Histogram of log returns # ggplot(data = prices, aes(x = logReturns)) + geom_bar(binwidth=0.0025) + # labs(title = "Histogram of log returns") # # ## currency # class(prices$Close) <- "currency" ## styles as currency in workbook # # ## write historical data and histogram of returns # writeDataTable(wb, sheet = "CBA", x = prices) # insertPlot(wb, sheet = 1, startRow=25, startCol = "J") # # ## Add conditional formatting to show where logReturn > 0.01 using default style # conditionalFormat(wb, sheet = 1, cols = 1:ncol(prices), rows = 2:(nrow(prices)+1), # rule = "$H2 > 0.01") # # ## style log return col as a percentage # logRetStyle <- createStyle(numFmt = "percentage") # # addStyle(wb, 1, style = logRetStyle, rows = 2:(nrow(prices) + 1), # cols = "H", gridExpand = TRUE) # # setColWidths(wb, sheet=1, cols = c("A", "F", "G", "H"), widths = 15) # # ## save workbook to working directory # saveWorkbook(wb, "stockPrice.xlsx", overwrite = TRUE) # openXL("stockPrice.xlsx") ## ----eval=FALSE, include=TRUE------------------------------------------------- # require(openxlsx) # require(jpeg) # require(ggplot2) # # plotFn <- function(x, ...){ # colvec <- grey(x) # colmat <- array(match(colvec, unique(colvec)), dim = dim(x)[1:2]) # image(x = 0:(dim(colmat)[2]), y = 0:(dim(colmat)[1]), z = t(colmat[nrow(colmat):1, ]), # col = unique(colvec), xlab = "", ylab = "", axes = FALSE, asp = 1, # bty ="n", frame.plot=F, ann=FALSE) # } # # ## Create workbook and add a worksheet, hide gridlines # wb <- createWorkbook("Einstein") # addWorksheet(wb, "Original Image", gridLines = FALSE) # # A <- readJPEG(file.path(path.package("openxlsx"), "einstein.jpg")) # height <- nrow(A); width <- ncol(A) # # ## write "Original Image" to cell B2 # writeData(wb, 1, "Original Image", xy = c(2,2)) # # ## write Object size to cell B3 # writeData(wb, 1, sprintf("Image object size: %s bytes", # format(object.size(A+0)[[1]], big.mark=',')), # xy = c(2,3)) ## equivalent to startCol = 2, startRow = 3 # # ## Plot image # par(mar=rep(0, 4), xpd = NA); plotFn(A) # # ## insert plot currently showing in plot window # insertPlot(wb, 1, width, height, units="px", startRow= 5, startCol = 2) # # ## SVD of covariance matrix # rMeans <- rowMeans(A) # rowMeans <- do.call("cbind", lapply(1:ncol(A), function(X) rMeans)) # A <- A - rowMeans # E <- svd(A %*% t(A) / (ncol(A) - 1)) # SVD on covariance matrix of A # pve <- data.frame("Eigenvalues" = E$d, # "PVE" = E$d/sum(E$d), # "Cumulative PVE" = cumsum(E$d/sum(E$d))) # # ## write eigenvalues to worksheet # addWorksheet(wb, "Principal Component Analysis") # hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", # halign = "CENTER", textDecoration = "Bold", # border = "TopBottomLeftRight", borderColour = "#4F81BD") # # writeData(wb, 2, x="Proportions of variance explained by Eigenvector" ,startRow = 2) # mergeCells(wb, sheet=2, cols=1:4, rows=2) # # setColWidths(wb, 2, cols = 1:3, widths = c(14, 12, 15)) # writeData(wb, 2, x=pve, startRow = 3, startCol = 1, borders="rows", headerStyle=hs) # # ## Plots # pve <- cbind(pve, "Ind" = 1:nrow(pve)) # ggplot(data = pve[1:20,], aes(x = Ind, y = 100*PVE)) + # geom_bar(stat="identity", position = "dodge") + # xlab("Principal Component Index") + ylab("Proportion of Variance Explained") + # geom_line(size = 1, col = "blue") + geom_point(size = 3, col = "blue") # # ## Write plot to worksheet 2 # insertPlot(wb, 2, width = 5, height = 4, startCol = "E", startRow = 2) # # ## Plot of cumulative explained variance # ggplot(data = pve[1:50,], aes(x = Ind, y = 100*Cumulative.PVE)) + # geom_point(size=2.5) + geom_line(size=1) + xlab("Number of PCs") + # ylab("Cumulative Proportion of Variance Explained") # insertPlot(wb, 2, width = 5, height = 4, xy= c("M", 2)) # # # ## Reconstruct image using increasing number of PCs # nPCs <- c(5, 7, 12, 20, 50, 200) # startRow <- rep(c(2, 24), each = 3) # startCol <- rep(c("B", "H", "N"), 2) # # ## create a worksheet to save reconstructed images to # addWorksheet(wb, "Reconstructed Images", zoom = 90) # # for(i in 1:length(nPCs)){ # # V <- E$v[, 1:nPCs[i]] # imgHat <- t(V) %*% A ## project img data on to PCs # imgSize <- object.size(V) + object.size(imgHat) + object.size(rMeans) # # imgHat <- V %*% imgHat + rowMeans ## reconstruct from PCs and add back row means # imgHat <- round((imgHat - min(imgHat)) / (max(imgHat) - min(imgHat))*255) # scale # plotFn(imgHat/255) # # ## write strings to worksheet 3 # writeData(wb, "Reconstructed Images", # sprintf("Number of principal components used: %s", # nPCs[[i]]), startCol[i], startRow[i]) # # writeData(wb, "Reconstructed Images", # sprintf("Sum of component object sizes: %s bytes", # format(as.numeric(imgSize), big.mark=',')), startCol[i], startRow[i]+1) # # ## write reconstruced image # insertPlot(wb, "Reconstructed Images", width, height, units="px", # xy = c(startCol[i], startRow[i]+3)) # # } # # # hide grid lines # showGridLines(wb, sheet = 3, showGridLines = FALSE) # # ## Make text above images BOLD # boldStyle <- createStyle(textDecoration="BOLD") # # ## only want to apply style to specified cells (not all combinations of rows & cols) # addStyle(wb, "Reconstructed Images", style=boldStyle, # rows = c(startRow, startRow+1), cols = rep(startCol, 2), # gridExpand = FALSE) # # ## save workbook to working directory # saveWorkbook(wb, "Image dimensionality reduction.xlsx", overwrite = TRUE) # # # # # ## remove example files for cran test # if (identical(Sys.getenv("NOT_CRAN", unset = "true"), "false")) { # file_list<-list.files(pattern="\\.xlsx",recursive = T) # file_list<-fl[!grepl("inst/extdata",file_list)&!grepl("man/",file_list)] # # if(length(file_list)>0){ # rm(file_list) # } # openxlsx/inst/doc/Formatting.html0000644000176200001440000034027614155741776016711 0ustar liggesusers Formating with xlsx

Formating with xlsx

Alexander Walker, Philipp Schauberger

2021-12-13

Formatting with writeData and writeDataTable

## data.frame to write
df <- data.frame(Date = Sys.Date() - 0:4, Logical = c(TRUE, FALSE, TRUE, TRUE, FALSE),
    Currency = paste("$", -2:2), Accounting = -2:2, hLink = "https://CRAN.R-project.org/",
    Percentage = seq(-1, 1, length.out = 5), TinyNumber = runif(5)/1e+09, stringsAsFactors = FALSE)

class(df$Currency) <- "currency"
class(df$Accounting) <- "accounting"
class(df$hLink) <- "hyperlink"
class(df$Percentage) <- "percentage"
class(df$TinyNumber) <- "scientific"

## Formatting can be applied simply through the write functions global options
## can be set to further simplify things
options(openxlsx.borderStyle = "thin")
options(openxlsx.borderColour = "#4F81BD")

## create a workbook and add a worksheet
wb <- createWorkbook()
addWorksheet(wb, "writeData auto-formatting")

writeData(wb, 1, df, startRow = 2, startCol = 2)
writeData(wb, 1, df, startRow = 9, startCol = 2, borders = "surrounding")
writeData(wb, 1, df, startRow = 16, startCol = 2, borders = "rows")
writeData(wb, 1, df, startRow = 23, startCol = 2, borders = "columns")
writeData(wb, 1, df, startRow = 30, startCol = 2, borders = "all")

## headerStyles
hs1 <- createStyle(fgFill = "#4F81BD", halign = "CENTER", textDecoration = "Bold",
    border = "Bottom", fontColour = "white")

writeData(wb, 1, df, startRow = 16, startCol = 10, headerStyle = hs1, borders = "rows",
    borderStyle = "medium")

## to change the display text for a hyperlink column just write over those
## cells
writeData(wb, sheet = 1, x = paste("Hyperlink", 1:5), startRow = 17, startCol = 14)


## writing as an Excel Table

addWorksheet(wb, "writeDataTable")
writeDataTable(wb, 2, df, startRow = 2, startCol = 2)
writeDataTable(wb, 2, df, startRow = 9, startCol = 2, tableStyle = "TableStyleLight9")
writeDataTable(wb, 2, df, startRow = 16, startCol = 2, tableStyle = "TableStyleLight2")
writeDataTable(wb, 2, df, startRow = 23, startCol = 2, tableStyle = "TableStyleMedium21")

openXL(wb)  ## opens a temp version

Use of pre-defined table styles

The ‘tableStyle’ argument in writeDataTable can be any of the predefined tableStyles in Excel.

Date Formatting

# data.frame of dates
dates <- data.frame(d1 = Sys.Date() - 0:4)
for (i in 1:3) dates <- cbind(dates, dates)
names(dates) <- paste0("d", 1:8)

## Date Formatting
wb <- createWorkbook()
addWorksheet(wb, "Date Formatting", gridLines = FALSE)
writeData(wb, 1, dates)  ## write without styling

## openxlsx converts columns of class 'Date' to Excel dates with the format
## given by
getOption("openxlsx.dateFormat", "mm/dd/yyyy")

## this can be set via (for example)
options(openxlsx.dateFormat = "yyyy/mm/dd")
## custom date formats can be made up of any combination of: d, dd, ddd, dddd,
## m, mm, mmm, mmmm, mmmmm, yy, yyyy

## numFmt == 'DATE' will use the date format specified by the above
addStyle(wb, 1, style = createStyle(numFmt = "DATE"), rows = 2:11, cols = 1, gridExpand = TRUE)

## some custom date format examples
sty <- createStyle(numFmt = "yyyy/mm/dd")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE)

sty <- createStyle(numFmt = "yyyy/mmm/dd")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE)

sty <- createStyle(numFmt = "yy / mmmm / dd")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE)

sty <- createStyle(numFmt = "ddddd")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 5, gridExpand = TRUE)

sty <- createStyle(numFmt = "yyyy-mmm-dd")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 6, gridExpand = TRUE)

sty <- createStyle(numFmt = "mm/ dd yyyy")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 7, gridExpand = TRUE)

sty <- createStyle(numFmt = "mm/dd/yy")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 8, gridExpand = TRUE)

setColWidths(wb, 1, cols = 1:10, widths = 23)

## The default date format used in writeData and writeDataTable can be set
## with:
options(openxlsx.dateFormat = "dd/mm/yyyy")
writeData(wb, "Date Formatting", dates, startRow = 8, borders = "rows")
options(openxlsx.dateFormat = "yyyy-mm-dd")
writeData(wb, "Date Formatting", dates, startRow = 15)

saveWorkbook(wb, "Date Formatting.xlsx", overwrite = TRUE)

DateTime Formatting

The conversion from POSIX to Excel datetimes is dependent on the timezone you are in. If POSIX values are being written incorrectly, try setting the timezone with (for example)

Sys.setenv(TZ = "Australia/Sydney")

dateTimes <- data.frame(d1 = Sys.time() - 0:4 * 10000)
for (i in 1:2) dateTimes <- cbind(dateTimes, dateTimes)
names(dateTimes) <- paste0("d", 1:4)

## POSIX Formatting
wb <- createWorkbook()
addWorksheet(wb, "DateTime Formatting", gridLines = FALSE)
writeData(wb, 1, dateTimes)  ## write without styling

## openxlsx converts columns of class 'POSIxt' to Excel datetimes with the
## format given by
getOption("openxlsx.datetimeFormat", "yyyy/mm/dd hh:mm:ss")

## this can be set via (for example)
options(openxlsx.datetimeFormat = "yyyy-mm-dd hh:mm:ss")
## custom datetime formats can be made up of any combination of: d, dd, ddd,
## dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy, h, hh, m, mm, s, ss, AM/PM

## numFmt == 'LONGDATE' will use the date format specified by the above
long_date_style <- createStyle(numFmt = "LONGDATE")
addStyle(wb, 1, style = long_date_style, rows = 2:11, cols = 1, gridExpand = TRUE)

## some custom date format examples
sty <- createStyle(numFmt = "yyyy/mm/dd hh:mm:ss AM/PM")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE)

sty <- createStyle(numFmt = "hh:mm:ss AM/PM")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE)

sty <- createStyle(numFmt = "hh:mm:ss")
addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE)

setColWidths(wb, 1, cols = 1:4, widths = 30)

## The default date format used in writeData and writeDataTable can be set
## with:
options(openxlsx.datetimeFormat = "yyyy/mm/dd hh:mm:ss")
writeData(wb, "DateTime Formatting", dateTimes, startRow = 8, borders = "rows")

options(openxlsx.datetimeFormat = "hh:mm:ss AM/PM")
writeDataTable(wb, "DateTime Formatting", dateTimes, startRow = 15)

saveWorkbook(wb, "DateTime Formatting.xlsx", overwrite = TRUE)
openXL("DateTime Formatting.xlsx")

Conditional Formatting

wb <- createWorkbook()
addWorksheet(wb, "cellIs")
addWorksheet(wb, "Moving Row")
addWorksheet(wb, "Moving Col")
addWorksheet(wb, "Dependent on 1")
addWorksheet(wb, "Duplicates")
addWorksheet(wb, "containsText")
addWorksheet(wb, "colourScale", zoom = 30)
addWorksheet(wb, "databar")

negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE")
posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE")

## rule applies to all each cell in range
writeData(wb, "cellIs", -5:5)
writeData(wb, "cellIs", LETTERS[1:11], startCol = 2)
conditionalFormatting(wb, "cellIs", cols = 1, rows = 1:11, rule = "!=0", style = negStyle)
conditionalFormatting(wb, "cellIs", cols = 1, rows = 1:11, rule = "==0", style = posStyle)

## highlight row dependent on first cell in row
writeData(wb, "Moving Row", -5:5)
writeData(wb, "Moving Row", LETTERS[1:11], startCol = 2)
conditionalFormatting(wb, "Moving Row", cols = 1:2, rows = 1:11, rule = "$A1<0",
    style = negStyle)
conditionalFormatting(wb, "Moving Row", cols = 1:2, rows = 1:11, rule = "$A1>0",
    style = posStyle)

## highlight column dependent on first cell in column
writeData(wb, "Moving Col", -5:5)
writeData(wb, "Moving Col", LETTERS[1:11], startCol = 2)
conditionalFormatting(wb, "Moving Col", cols = 1:2, rows = 1:11, rule = "A$1<0",
    style = negStyle)
conditionalFormatting(wb, "Moving Col", cols = 1:2, rows = 1:11, rule = "A$1>0",
    style = posStyle)

## highlight entire range cols X rows dependent only on cell A1
writeData(wb, "Dependent on 1", -5:5)
writeData(wb, "Dependent on 1", LETTERS[1:11], startCol = 2)
conditionalFormatting(wb, "Dependent on 1", cols = 1:2, rows = 1:11, rule = "$A$1<0",
    style = negStyle)
conditionalFormatting(wb, "Dependent on 1", cols = 1:2, rows = 1:11, rule = "$A$1>0",
    style = posStyle)

## highlight duplicates using default style
writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE))
conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates")

## cells containing text
fn <- function(x) paste(sample(LETTERS, 10), collapse = "-")
writeData(wb, "containsText", sapply(1:10, fn))
conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains",
    rule = "A")

## colourscale colours cells based on cell value
df <- read.xlsx(system.file("readTest.xlsx", package = "openxlsx"), sheet = 4)
writeData(wb, "colourScale", df, colNames = FALSE)  ## write data.frame

## rule is a vector or colours of length 2 or 3 (any hex colour or any of
## colours()) If rule is NULL, min and max of cells is used. Rule must be the
## same length as style or NULL.
conditionalFormatting(wb, "colourScale", cols = 1:ncol(df), rows = 1:nrow(df), style = c("black",
    "white"), rule = c(0, 255), type = "colourScale")

setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07)
setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5)

## Databars
writeData(wb, "databar", -5:5)
conditionalFormatting(wb, "databar", cols = 1, rows = 1:12, type = "databar")  ## Default colours

saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE)

openXL(wb)

Numeric Formatting

numeric columns styling can be set using the numFmt parameter in createStyle or a default can be set with, for example, options(“openxlsx.numFmt” = “#,#0.00”)

options(openxlsx.numFmt = NULL)
wb <- createWorkbook()
addWorksheet(wb, "Sheet 1")
df <- data.frame(matrix(12.987654321, ncol = 7, nrow = 5))  ## data.frame to write
df[, 6:7] <- df[, 6:7] * 1e+06

## Set column 1 class to 'comma' to get comma separated thousands
class(df$X1) <- "comma"

writeData(wb, 1, df)
s <- createStyle(numFmt = "0.0")
addStyle(wb, 1, style = s, rows = 2:6, cols = 2, gridExpand = TRUE)

s <- createStyle(numFmt = "0.00")
addStyle(wb, 1, style = s, rows = 2:6, cols = 3, gridExpand = TRUE)

s <- createStyle(numFmt = "0.000")
addStyle(wb, 1, style = s, rows = 2:6, cols = 4, gridExpand = TRUE)

s <- createStyle(numFmt = "#,##0")
addStyle(wb, 1, style = s, rows = 2:6, cols = 5, gridExpand = TRUE)

s <- createStyle(numFmt = "#,##0.00")
addStyle(wb, 1, style = s, rows = 2:6, cols = 6, gridExpand = TRUE)

s <- createStyle(numFmt = "$ #,##0.00")
addStyle(wb, 1, style = s, rows = 2:6, cols = 7, gridExpand = TRUE)

## set a default number format for numeric columns of data.frames
options(openxlsx.numFmt = "$* #,#0.00")
writeData(wb, 1, x = data.frame(`Using Default Options` = rep(2345.1235, 5)), startCol = 9)

setColWidths(wb, 1, cols = 1:10, widths = 15)

## Using default numFmt to round to 2 dp (Any numeric column will be affected)
addWorksheet(wb, "Sheet 2")
df <- iris
df[, 1:4] <- df[1:4] + runif(1)
writeDataTable(wb, sheet = 2, x = df)
writeData(wb, sheet = 2, x = df, startCol = 7)
writeData(wb, sheet = 2, x = df, startCol = 13, borders = "rows")

## To stop auto-formatting numerics set
options(openxlsx.numFmt = NULL)
addWorksheet(wb, "Sheet 3")
writeDataTable(wb, sheet = 3, x = df)

openXL(wb)
openxlsx/inst/doc/Formatting.Rmd0000644000176200001440000002767014155600364016452 0ustar liggesusers--- title: "Formating with xlsx" author: "Alexander Walker, Philipp Schauberger" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Formating with xlsx} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Formatting with writeData and writeDataTable ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} ## data.frame to write df <- data.frame("Date" = Sys.Date()-0:4, "Logical" = c(TRUE, FALSE, TRUE, TRUE, FALSE), "Currency" = paste("$",-2:2), "Accounting" = -2:2, "hLink" = "https://CRAN.R-project.org/", "Percentage" = seq(-1, 1, length.out=5), "TinyNumber" = runif(5) / 1E9, stringsAsFactors = FALSE) class(df$Currency) <- "currency" class(df$Accounting) <- "accounting" class(df$hLink) <- "hyperlink" class(df$Percentage) <- "percentage" class(df$TinyNumber) <- "scientific" ## Formatting can be applied simply through the write functions ## global options can be set to further simplify things options("openxlsx.borderStyle" = "thin") options("openxlsx.borderColour" = "#4F81BD") ## create a workbook and add a worksheet wb <- createWorkbook() addWorksheet(wb, "writeData auto-formatting") writeData(wb, 1, df, startRow = 2, startCol = 2) writeData(wb, 1, df, startRow = 9, startCol = 2, borders = "surrounding") writeData(wb, 1, df, startRow = 16, startCol = 2, borders = "rows") writeData(wb, 1, df, startRow = 23, startCol = 2, borders ="columns") writeData(wb, 1, df, startRow = 30, startCol = 2, borders ="all") ## headerStyles hs1 <- createStyle(fgFill = "#4F81BD", halign = "CENTER", textDecoration = "Bold", border = "Bottom", fontColour = "white") writeData(wb, 1, df, startRow = 16, startCol = 10, headerStyle = hs1, borders = "rows", borderStyle = "medium") ## to change the display text for a hyperlink column just write over those cells writeData(wb, sheet = 1, x = paste("Hyperlink", 1:5), startRow = 17, startCol = 14) ## writing as an Excel Table addWorksheet(wb, "writeDataTable") writeDataTable(wb, 2, df, startRow = 2, startCol = 2) writeDataTable(wb, 2, df, startRow = 9, startCol = 2, tableStyle = "TableStyleLight9") writeDataTable(wb, 2, df, startRow = 16, startCol = 2, tableStyle = "TableStyleLight2") writeDataTable(wb, 2, df, startRow = 23, startCol = 2, tableStyle = "TableStyleMedium21") openXL(wb) ## opens a temp version ``` ## Use of pre-defined table styles The 'tableStyle' argument in writeDataTable can be any of the predefined tableStyles in Excel. ![](tableStyles.PNG) ## Date Formatting ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} # data.frame of dates dates <- data.frame("d1" = Sys.Date() - 0:4) for(i in 1:3) dates <- cbind(dates, dates) names(dates) <- paste0("d", 1:8) ## Date Formatting wb <- createWorkbook() addWorksheet(wb, "Date Formatting", gridLines = FALSE) writeData(wb, 1, dates) ## write without styling ## openxlsx converts columns of class "Date" to Excel dates with the format given by getOption("openxlsx.dateFormat", "mm/dd/yyyy") ## this can be set via (for example) options("openxlsx.dateFormat" = "yyyy/mm/dd") ## custom date formats can be made up of any combination of: ## d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy ## numFmt == "DATE" will use the date format specified by the above addStyle(wb, 1, style = createStyle(numFmt = "DATE"), rows = 2:11, cols = 1, gridExpand = TRUE) ## some custom date format examples sty <- createStyle(numFmt = "yyyy/mm/dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) sty <- createStyle(numFmt = "yyyy/mmm/dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) sty <- createStyle(numFmt = "yy / mmmm / dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) sty <- createStyle(numFmt = "ddddd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 5, gridExpand = TRUE) sty <- createStyle(numFmt = "yyyy-mmm-dd") addStyle(wb, 1, style = sty, rows = 2:11, cols = 6, gridExpand = TRUE) sty <- createStyle(numFmt = "mm/ dd yyyy") addStyle(wb, 1, style = sty, rows = 2:11, cols = 7, gridExpand = TRUE) sty <- createStyle(numFmt = "mm/dd/yy") addStyle(wb, 1, style = sty, rows = 2:11, cols = 8, gridExpand = TRUE) setColWidths(wb, 1, cols = 1:10, widths = 23) ## The default date format used in writeData and writeDataTable can be set with: options("openxlsx.dateFormat" = "dd/mm/yyyy") writeData(wb, "Date Formatting", dates, startRow = 8, borders = "rows") options("openxlsx.dateFormat" = "yyyy-mm-dd") writeData(wb, "Date Formatting", dates, startRow = 15) saveWorkbook(wb, "Date Formatting.xlsx", overwrite = TRUE) ``` ## DateTime Formatting The conversion from POSIX to Excel datetimes is dependent on the timezone you are in. If POSIX values are being written incorrectly, try setting the timezone with (for example) ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} Sys.setenv(TZ = "Australia/Sydney") dateTimes <- data.frame("d1" = Sys.time() - 0:4*10000) for(i in 1:2) dateTimes <- cbind(dateTimes, dateTimes) names(dateTimes) <- paste0("d", 1:4) ## POSIX Formatting wb <- createWorkbook() addWorksheet(wb, "DateTime Formatting", gridLines = FALSE) writeData(wb, 1, dateTimes) ## write without styling ## openxlsx converts columns of class "POSIxt" to Excel datetimes with the format given by getOption("openxlsx.datetimeFormat", "yyyy/mm/dd hh:mm:ss") ## this can be set via (for example) options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") ## custom datetime formats can be made up of any combination of: ## d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy, h, hh, m, mm, s, ss, AM/PM ## numFmt == "LONGDATE" will use the date format specified by the above long_date_style <- createStyle(numFmt = "LONGDATE") addStyle(wb, 1, style = long_date_style, rows = 2:11, cols = 1, gridExpand = TRUE) ## some custom date format examples sty <- createStyle(numFmt = "yyyy/mm/dd hh:mm:ss AM/PM") addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) sty <- createStyle(numFmt = "hh:mm:ss AM/PM") addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) sty <- createStyle(numFmt = "hh:mm:ss") addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) setColWidths(wb, 1, cols = 1:4, widths = 30) ## The default date format used in writeData and writeDataTable can be set with: options("openxlsx.datetimeFormat" = "yyyy/mm/dd hh:mm:ss") writeData(wb, "DateTime Formatting", dateTimes, startRow = 8, borders = "rows") options("openxlsx.datetimeFormat" = "hh:mm:ss AM/PM") writeDataTable(wb, "DateTime Formatting", dateTimes, startRow = 15) saveWorkbook(wb, "DateTime Formatting.xlsx", overwrite = TRUE) openXL("DateTime Formatting.xlsx") ``` ## Conditional Formatting ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} wb <- createWorkbook() addWorksheet(wb, "cellIs") addWorksheet(wb, "Moving Row") addWorksheet(wb, "Moving Col") addWorksheet(wb, "Dependent on 1") addWorksheet(wb, "Duplicates") addWorksheet(wb, "containsText") addWorksheet(wb, "colourScale", zoom = 30) addWorksheet(wb, "databar") negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE") ## rule applies to all each cell in range writeData(wb, "cellIs", -5:5) writeData(wb, "cellIs", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "cellIs", cols=1, rows=1:11, rule="!=0", style = negStyle) conditionalFormatting(wb, "cellIs", cols=1, rows=1:11, rule="==0", style = posStyle) ## highlight row dependent on first cell in row writeData(wb, "Moving Row", -5:5) writeData(wb, "Moving Row", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Moving Row", cols=1:2, rows=1:11, rule="$A1<0", style = negStyle) conditionalFormatting(wb, "Moving Row", cols=1:2, rows=1:11, rule="$A1>0", style = posStyle) ## highlight column dependent on first cell in column writeData(wb, "Moving Col", -5:5) writeData(wb, "Moving Col", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Moving Col", cols=1:2, rows=1:11, rule="A$1<0", style = negStyle) conditionalFormatting(wb, "Moving Col", cols=1:2, rows=1:11, rule="A$1>0", style = posStyle) ## highlight entire range cols X rows dependent only on cell A1 writeData(wb, "Dependent on 1", -5:5) writeData(wb, "Dependent on 1", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=1:11, rule="$A$1<0", style = negStyle) conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=1:11, rule="$A$1>0", style = posStyle) ## highlight duplicates using default style writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE)) conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates") ## cells containing text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "containsText", sapply(1:10, fn)) conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains", rule = "A") ## colourscale colours cells based on cell value df <- read.xlsx(system.file("readTest.xlsx", package = "openxlsx"), sheet = 4) writeData(wb, "colourScale", df, colNames=FALSE) ## write data.frame ## rule is a vector or colours of length 2 or 3 (any hex colour or any of colours()) ## If rule is NULL, min and max of cells is used. Rule must be the same length as style or NULL. conditionalFormatting(wb, "colourScale", cols=1:ncol(df), rows=1:nrow(df), style = c("black", "white"), rule = c(0, 255), type = "colourScale") setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07) setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5) ## Databars writeData(wb, "databar", -5:5) conditionalFormatting(wb, "databar", cols = 1, rows = 1:12, type = "databar") ## Default colours saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE) openXL(wb) ``` ## Numeric Formatting numeric columns styling can be set using the numFmt parameter in createStyle or a default can be set with, for example, options("openxlsx.numFmt" = "#,#0.00") ```{r include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE} options("openxlsx.numFmt" = NULL) wb <- createWorkbook() addWorksheet(wb, "Sheet 1") df <- data.frame(matrix(12.987654321, ncol = 7, nrow = 5)) ## data.frame to write df[ ,6:7] <- df[ ,6:7]*1E6 ## Set column 1 class to "comma" to get comma separated thousands class(df$X1) <- "comma" writeData(wb, 1, df) s <- createStyle(numFmt = "0.0") addStyle(wb, 1, style = s, rows = 2:6, cols = 2, gridExpand = TRUE) s <- createStyle(numFmt = "0.00") addStyle(wb, 1, style = s, rows = 2:6, cols = 3, gridExpand = TRUE) s <- createStyle(numFmt = "0.000") addStyle(wb, 1, style = s, rows = 2:6, cols = 4, gridExpand = TRUE) s <- createStyle(numFmt = "#,##0") addStyle(wb, 1, style = s, rows = 2:6, cols = 5, gridExpand = TRUE) s <- createStyle(numFmt = "#,##0.00") addStyle(wb, 1, style = s, rows = 2:6, cols = 6, gridExpand = TRUE) s <- createStyle(numFmt = "$ #,##0.00") addStyle(wb, 1, style = s, rows = 2:6, cols = 7, gridExpand = TRUE) ## set a default number format for numeric columns of data.frames options("openxlsx.numFmt" = "$* #,#0.00") writeData(wb, 1, x = data.frame("Using Default Options" = rep(2345.1235, 5)), startCol = 9) setColWidths(wb, 1, cols = 1:10, widths = 15) ## Using default numFmt to round to 2 dp (Any numeric column will be affected) addWorksheet(wb, "Sheet 2") df <- iris; df[, 1:4] <- df[1:4] + runif(1) writeDataTable(wb, sheet = 2, x = df) writeData(wb, sheet = 2, x = df, startCol = 7) writeData(wb, sheet = 2, x = df, startCol = 13, borders = "rows") ## To stop auto-formatting numerics set options("openxlsx.numFmt" = NULL) addWorksheet(wb, "Sheet 3") writeDataTable(wb, sheet = 3, x = df) openXL(wb) ``` openxlsx/inst/doc/Introduction.html0000644000176200001440000026542414155741777017262 0ustar liggesusers Introduction

Introduction

Alexander Walker, Philipp Schauberger

2021-12-13

Basic Examples

write.xlsx

The simplest way to write to a workbook is write.xlsx(). By default, write.xlsx calls writeData. If asTable is TRUE write.xlsx will write x as an Excel table.

## write to working directory
library(openxlsx)
write.xlsx(iris, file = "writeXLSX1.xlsx")
write.xlsx(iris, file = "writeXLSXTable1.xlsx", asTable = TRUE)

write list of data.frames to xlsx-file

## write a list of data.frames to individual worksheets using list names as
## worksheet names
l <- list(IRIS = iris, MTCARS = mtcars)
write.xlsx(l, file = "writeXLSX2.xlsx")
write.xlsx(l, file = "writeXLSXTable2.xlsx", asTable = TRUE)

write.xlsx also accepts styling parameters

The simplest way is to set default options and set column class

options(openxlsx.borderColour = "#4F80BD")
options(openxlsx.borderStyle = "thin")
options(openxlsx.dateFormat = "mm/dd/yyyy")
options(openxlsx.datetimeFormat = "yyyy-mm-dd hh:mm:ss")
options(openxlsx.numFmt = NULL)  ## For default style rounding of numeric columns

df <- data.frame(Date = Sys.Date() - 0:19, LogicalT = TRUE, Time = Sys.time() - 0:19 *
    60 * 60, Cash = paste("$", 1:20), Cash2 = 31:50, hLink = "https://CRAN.R-project.org/",
    Percentage = seq(0, 1, length.out = 20), TinyNumbers = runif(20)/1e+09, stringsAsFactors = FALSE)

class(df$Cash) <- "currency"
class(df$Cash2) <- "accounting"
class(df$hLink) <- "hyperlink"
class(df$Percentage) <- "percentage"
class(df$TinyNumbers) <- "scientific"

write.xlsx(df, "writeXLSX3.xlsx")
write.xlsx(df, file = "writeXLSXTable3.xlsx", asTable = TRUE)

Workbook styles

define a style for column headers

hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD", halign = "center",
    valign = "center", textDecoration = "Bold", border = "TopBottomLeftRight", textRotation = 45)

write.xlsx(iris, file = "writeXLSX4.xlsx", borders = "rows", headerStyle = hs)
write.xlsx(iris, file = "writeXLSX5.xlsx", borders = "columns", headerStyle = hs)

write.xlsx(iris, "writeXLSXTable4.xlsx", asTable = TRUE, headerStyle = createStyle(textRotation = 45))

When writing a list, the stylings will apply to all list elements

l <- list(IRIS = iris, colClasses = df)
write.xlsx(l, file = "writeXLSX6.xlsx", borders = "columns", headerStyle = hs)
write.xlsx(l, file = "writeXLSXTable5.xlsx", asTable = TRUE, tableStyle = "TableStyleLight2")

openXL("writeXLSX6.xlsx")
openXL("writeXLSXTable5.xlsx")

write.xlsx returns the workbook object for further editing

wb <- write.xlsx(iris, "writeXLSX6.xlsx")
setColWidths(wb, sheet = 1, cols = 1:5, widths = 20)
saveWorkbook(wb, "writeXLSX6.xlsx", overwrite = TRUE)

Workbook creation walk-through

create workbook and set default border Colour and style

require(ggplot2)
wb <- createWorkbook()
options(openxlsx.borderColour = "#4F80BD")
options(openxlsx.borderStyle = "thin")
modifyBaseFont(wb, fontSize = 10, fontName = "Arial Narrow")

Add Sheets

addWorksheet(wb, sheetName = "Motor Trend Car Road Tests", gridLines = FALSE)
addWorksheet(wb, sheetName = "Iris", gridLines = FALSE)

write data to sheet 1

freezePane(wb, sheet = 1, firstRow = TRUE, firstCol = TRUE)  ## freeze first row and column
writeDataTable(wb, sheet = 1, x = mtcars, colNames = TRUE, rowNames = TRUE, tableStyle = "TableStyleLight9")

setColWidths(wb, sheet = 1, cols = "A", widths = 18)

write data to sheet 2

iris data.frame is added as excel table on sheet 2.

writeDataTable(wb, sheet = 2, iris, startCol = "K", startRow = 2)

qplot(data = iris, x = Sepal.Length, y = Sepal.Width, colour = Species)
insertPlot(wb, 2, xy = c("B", 16))  ## insert plot at cell B16

means <- aggregate(x = iris[, -5], by = list(iris$Species), FUN = mean)
vars <- aggregate(x = iris[, -5], by = list(iris$Species), FUN = var)

add write group means

headSty <- createStyle(fgFill = "#DCE6F1", halign = "center", border = "TopBottomLeftRight")
writeData(wb, 2, x = "Iris dataset group means", startCol = 2, startRow = 2)
writeData(wb, 2, x = means, startCol = "B", startRow = 3, borders = "rows", headerStyle = headSty)

add write group variances

writeData(wb, 2, x = "Iris dataset group variances", startCol = 2, startRow = 9)
writeData(wb, 2, x = vars, startCol = "B", startRow = 10, borders = "columns", headerStyle = headSty)

setColWidths(wb, 2, cols = 2:6, widths = 12)  ## width is recycled for each col
setColWidths(wb, 2, cols = 11:15, widths = 15)

add style mean & variance table headers

s1 <- createStyle(fontSize = 14, textDecoration = c("bold", "italic"))
addStyle(wb, 2, style = s1, rows = c(2, 9), cols = c(2, 2))

save workbook

saveWorkbook(wb, "basics.xlsx", overwrite = TRUE)  ## save to working directory

Further Examples

Stock Price

require(ggplot2)

wb <- createWorkbook()

## read historical prices from yahoo finance
ticker <- "CBA.AX"
csv.url <- paste0("https://query1.finance.yahoo.com/v7/finance/download/", ticker,
    "?period1=1597597610&period2=1629133610&interval=1d&events=history&includeAdjustedClose=true")
prices <- read.csv(url(csv.url), as.is = TRUE)
prices$Date <- as.Date(prices$Date)
close <- prices$Close
prices$logReturns = c(0, log(close[2:length(close)]/close[1:(length(close) - 1)]))

## Create plot of price series and add to worksheet
ggplot(data = prices, aes(as.Date(Date), as.numeric(Close))) + geom_line(colour = "royalblue2") +
    labs(x = "Date", y = "Price", title = ticker) + geom_area(fill = "royalblue1",
    alpha = 0.3) + coord_cartesian(ylim = c(min(prices$Close) - 1.5, max(prices$Close) +
    1.5))

## Add worksheet and write plot to sheet
addWorksheet(wb, sheetName = "CBA")
insertPlot(wb, sheet = 1, xy = c("J", 3))

## Histogram of log returns
ggplot(data = prices, aes(x = logReturns)) + geom_bar(binwidth = 0.0025) + labs(title = "Histogram of log returns")

## currency
class(prices$Close) <- "currency"  ## styles as currency in workbook

## write historical data and histogram of returns
writeDataTable(wb, sheet = "CBA", x = prices)
insertPlot(wb, sheet = 1, startRow = 25, startCol = "J")

## Add conditional formatting to show where logReturn > 0.01 using default
## style
conditionalFormat(wb, sheet = 1, cols = 1:ncol(prices), rows = 2:(nrow(prices) +
    1), rule = "$H2 > 0.01")

## style log return col as a percentage
logRetStyle <- createStyle(numFmt = "percentage")

addStyle(wb, 1, style = logRetStyle, rows = 2:(nrow(prices) + 1), cols = "H", gridExpand = TRUE)

setColWidths(wb, sheet = 1, cols = c("A", "F", "G", "H"), widths = 15)

## save workbook to working directory
saveWorkbook(wb, "stockPrice.xlsx", overwrite = TRUE)
openXL("stockPrice.xlsx")

Image Compression using PCA

require(openxlsx)
require(jpeg)
require(ggplot2)

plotFn <- function(x, ...){
  colvec <- grey(x)
  colmat <- array(match(colvec, unique(colvec)), dim = dim(x)[1:2])
  image(x = 0:(dim(colmat)[2]), y = 0:(dim(colmat)[1]), z = t(colmat[nrow(colmat):1, ]),
    col = unique(colvec), xlab = "", ylab = "", axes = FALSE, asp = 1,
    bty ="n", frame.plot=F, ann=FALSE)
}

## Create workbook and add a worksheet, hide gridlines
wb <- createWorkbook("Einstein")
addWorksheet(wb, "Original Image", gridLines = FALSE)

A <- readJPEG(file.path(path.package("openxlsx"), "einstein.jpg"))
height <- nrow(A); width <- ncol(A)

## write "Original Image" to cell B2
writeData(wb, 1, "Original Image", xy = c(2,2))

## write Object size to cell B3
writeData(wb, 1, sprintf("Image object size: %s bytes",
                         format(object.size(A+0)[[1]], big.mark=',')), 
          xy = c(2,3))  ## equivalent to startCol = 2, startRow = 3

## Plot image
par(mar=rep(0, 4), xpd = NA); plotFn(A)

## insert plot currently showing in plot window
insertPlot(wb, 1, width, height, units="px", startRow= 5, startCol = 2)       

## SVD of covariance matrix
rMeans <- rowMeans(A)
rowMeans <- do.call("cbind", lapply(1:ncol(A), function(X) rMeans))
A <- A - rowMeans
E <- svd(A %*% t(A) / (ncol(A) - 1)) # SVD on covariance matrix of A
pve <- data.frame("Eigenvalues" = E$d, 
                  "PVE" = E$d/sum(E$d),
                  "Cumulative PVE" = cumsum(E$d/sum(E$d)))

## write eigenvalues to worksheet
addWorksheet(wb, "Principal Component Analysis")
hs <- createStyle(fontColour = "#ffffff", fgFill = "#4F80BD",
                  halign = "CENTER", textDecoration = "Bold",
                  border = "TopBottomLeftRight", borderColour = "#4F81BD")

writeData(wb, 2, x="Proportions of variance explained by Eigenvector" ,startRow = 2)
mergeCells(wb, sheet=2, cols=1:4, rows=2)

setColWidths(wb, 2, cols = 1:3, widths = c(14, 12, 15))
writeData(wb, 2, x=pve, startRow = 3, startCol = 1, borders="rows", headerStyle=hs)

## Plots
pve <- cbind(pve, "Ind" = 1:nrow(pve))
ggplot(data = pve[1:20,], aes(x = Ind, y = 100*PVE)) +
  geom_bar(stat="identity", position = "dodge") +
  xlab("Principal Component Index") + ylab("Proportion of Variance Explained") +
  geom_line(size = 1, col = "blue") + geom_point(size = 3, col = "blue")

## Write plot to worksheet 2
insertPlot(wb, 2, width = 5, height = 4, startCol = "E", startRow = 2) 

## Plot of cumulative explained variance
ggplot(data = pve[1:50,], aes(x = Ind, y = 100*Cumulative.PVE)) +
  geom_point(size=2.5) + geom_line(size=1) + xlab("Number of PCs") +
  ylab("Cumulative Proportion of Variance Explained")
insertPlot(wb, 2, width = 5, height = 4, xy= c("M", 2)) 


## Reconstruct image using increasing number of PCs
nPCs <- c(5, 7, 12, 20, 50, 200)
startRow <- rep(c(2, 24), each = 3)
startCol <- rep(c("B", "H", "N"), 2)

## create a worksheet to save reconstructed images to
addWorksheet(wb, "Reconstructed Images", zoom = 90)

for(i in 1:length(nPCs)){
  
  V <- E$v[, 1:nPCs[i]]
  imgHat <- t(V) %*% A  ## project img data on to PCs
  imgSize <- object.size(V) + object.size(imgHat) + object.size(rMeans)
  
  imgHat <- V %*% imgHat + rowMeans  ## reconstruct from PCs and add back row means
  imgHat <- round((imgHat - min(imgHat)) / (max(imgHat) - min(imgHat))*255) # scale
  plotFn(imgHat/255)
  
  ## write strings to worksheet 3
  writeData(wb, "Reconstructed Images", 
            sprintf("Number of principal components used:  %s", 
                    nPCs[[i]]), startCol[i], startRow[i])
  
  writeData(wb, "Reconstructed Images", 
            sprintf("Sum of component object sizes: %s bytes",
                    format(as.numeric(imgSize), big.mark=',')), startCol[i], startRow[i]+1)
  
  ## write reconstruced image
  insertPlot(wb, "Reconstructed Images", width, height, units="px",
             xy = c(startCol[i], startRow[i]+3))
  
}

# hide grid lines
showGridLines(wb, sheet = 3, showGridLines = FALSE)

## Make text above images BOLD
boldStyle <- createStyle(textDecoration="BOLD")

## only want to apply style to specified cells (not all combinations of rows & cols)
addStyle(wb, "Reconstructed Images", style=boldStyle, 
         rows = c(startRow, startRow+1), cols = rep(startCol, 2), 
         gridExpand = FALSE)  

## save workbook to working directory
saveWorkbook(wb, "Image dimensionality reduction.xlsx", overwrite = TRUE) 




## remove example files for cran test
if (identical(Sys.getenv("NOT_CRAN", unset = "true"), "false")) {
file_list<-list.files(pattern="\\.xlsx",recursive = T)
file_list<-fl[!grepl("inst/extdata",file_list)&!grepl("man/",file_list)]

if(length(file_list)>0){
rm(file_list)
}
openxlsx/inst/doc/Formatting.R0000644000176200001440000002756314155741775016146 0ustar liggesusers## ----include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE---------------------- # # ## data.frame to write # df <- data.frame("Date" = Sys.Date()-0:4, # "Logical" = c(TRUE, FALSE, TRUE, TRUE, FALSE), # "Currency" = paste("$",-2:2), # "Accounting" = -2:2, # "hLink" = "https://CRAN.R-project.org/", # "Percentage" = seq(-1, 1, length.out=5), # "TinyNumber" = runif(5) / 1E9, stringsAsFactors = FALSE) # # class(df$Currency) <- "currency" # class(df$Accounting) <- "accounting" # class(df$hLink) <- "hyperlink" # class(df$Percentage) <- "percentage" # class(df$TinyNumber) <- "scientific" # # ## Formatting can be applied simply through the write functions # ## global options can be set to further simplify things # options("openxlsx.borderStyle" = "thin") # options("openxlsx.borderColour" = "#4F81BD") # # ## create a workbook and add a worksheet # wb <- createWorkbook() # addWorksheet(wb, "writeData auto-formatting") # # writeData(wb, 1, df, startRow = 2, startCol = 2) # writeData(wb, 1, df, startRow = 9, startCol = 2, borders = "surrounding") # writeData(wb, 1, df, startRow = 16, startCol = 2, borders = "rows") # writeData(wb, 1, df, startRow = 23, startCol = 2, borders ="columns") # writeData(wb, 1, df, startRow = 30, startCol = 2, borders ="all") # # ## headerStyles # hs1 <- createStyle(fgFill = "#4F81BD", halign = "CENTER", textDecoration = "Bold", # border = "Bottom", fontColour = "white") # # writeData(wb, 1, df, startRow = 16, startCol = 10, headerStyle = hs1, # borders = "rows", borderStyle = "medium") # # ## to change the display text for a hyperlink column just write over those cells # writeData(wb, sheet = 1, x = paste("Hyperlink", 1:5), startRow = 17, startCol = 14) # # # ## writing as an Excel Table # # addWorksheet(wb, "writeDataTable") # writeDataTable(wb, 2, df, startRow = 2, startCol = 2) # writeDataTable(wb, 2, df, startRow = 9, startCol = 2, tableStyle = "TableStyleLight9") # writeDataTable(wb, 2, df, startRow = 16, startCol = 2, tableStyle = "TableStyleLight2") # writeDataTable(wb, 2, df, startRow = 23, startCol = 2, tableStyle = "TableStyleMedium21") # # openXL(wb) ## opens a temp version ## ----include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE---------------------- # # # data.frame of dates # dates <- data.frame("d1" = Sys.Date() - 0:4) # for(i in 1:3) dates <- cbind(dates, dates) # names(dates) <- paste0("d", 1:8) # # ## Date Formatting # wb <- createWorkbook() # addWorksheet(wb, "Date Formatting", gridLines = FALSE) # writeData(wb, 1, dates) ## write without styling # # ## openxlsx converts columns of class "Date" to Excel dates with the format given by # getOption("openxlsx.dateFormat", "mm/dd/yyyy") # # ## this can be set via (for example) # options("openxlsx.dateFormat" = "yyyy/mm/dd") # ## custom date formats can be made up of any combination of: # ## d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy # # ## numFmt == "DATE" will use the date format specified by the above # addStyle(wb, 1, style = createStyle(numFmt = "DATE"), rows = 2:11, cols = 1, gridExpand = TRUE) # # ## some custom date format examples # sty <- createStyle(numFmt = "yyyy/mm/dd") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) # # sty <- createStyle(numFmt = "yyyy/mmm/dd") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) # # sty <- createStyle(numFmt = "yy / mmmm / dd") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) # # sty <- createStyle(numFmt = "ddddd") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 5, gridExpand = TRUE) # # sty <- createStyle(numFmt = "yyyy-mmm-dd") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 6, gridExpand = TRUE) # # sty <- createStyle(numFmt = "mm/ dd yyyy") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 7, gridExpand = TRUE) # # sty <- createStyle(numFmt = "mm/dd/yy") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 8, gridExpand = TRUE) # # setColWidths(wb, 1, cols = 1:10, widths = 23) # # ## The default date format used in writeData and writeDataTable can be set with: # options("openxlsx.dateFormat" = "dd/mm/yyyy") # writeData(wb, "Date Formatting", dates, startRow = 8, borders = "rows") # options("openxlsx.dateFormat" = "yyyy-mm-dd") # writeData(wb, "Date Formatting", dates, startRow = 15) # # saveWorkbook(wb, "Date Formatting.xlsx", overwrite = TRUE) # ## ----include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE---------------------- # # Sys.setenv(TZ = "Australia/Sydney") # # dateTimes <- data.frame("d1" = Sys.time() - 0:4*10000) # for(i in 1:2) dateTimes <- cbind(dateTimes, dateTimes) # names(dateTimes) <- paste0("d", 1:4) # # ## POSIX Formatting # wb <- createWorkbook() # addWorksheet(wb, "DateTime Formatting", gridLines = FALSE) # writeData(wb, 1, dateTimes) ## write without styling # # ## openxlsx converts columns of class "POSIxt" to Excel datetimes with the format given by # getOption("openxlsx.datetimeFormat", "yyyy/mm/dd hh:mm:ss") # # ## this can be set via (for example) # options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss") # ## custom datetime formats can be made up of any combination of: # ## d, dd, ddd, dddd, m, mm, mmm, mmmm, mmmmm, yy, yyyy, h, hh, m, mm, s, ss, AM/PM # # ## numFmt == "LONGDATE" will use the date format specified by the above # long_date_style <- createStyle(numFmt = "LONGDATE") # addStyle(wb, 1, style = long_date_style, rows = 2:11, cols = 1, gridExpand = TRUE) # # ## some custom date format examples # sty <- createStyle(numFmt = "yyyy/mm/dd hh:mm:ss AM/PM") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 2, gridExpand = TRUE) # # sty <- createStyle(numFmt = "hh:mm:ss AM/PM") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 3, gridExpand = TRUE) # # sty <- createStyle(numFmt = "hh:mm:ss") # addStyle(wb, 1, style = sty, rows = 2:11, cols = 4, gridExpand = TRUE) # # setColWidths(wb, 1, cols = 1:4, widths = 30) # # ## The default date format used in writeData and writeDataTable can be set with: # options("openxlsx.datetimeFormat" = "yyyy/mm/dd hh:mm:ss") # writeData(wb, "DateTime Formatting", dateTimes, startRow = 8, borders = "rows") # # options("openxlsx.datetimeFormat" = "hh:mm:ss AM/PM") # writeDataTable(wb, "DateTime Formatting", dateTimes, startRow = 15) # # saveWorkbook(wb, "DateTime Formatting.xlsx", overwrite = TRUE) # openXL("DateTime Formatting.xlsx") # ## ----include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE---------------------- # # wb <- createWorkbook() # addWorksheet(wb, "cellIs") # addWorksheet(wb, "Moving Row") # addWorksheet(wb, "Moving Col") # addWorksheet(wb, "Dependent on 1") # addWorksheet(wb, "Duplicates") # addWorksheet(wb, "containsText") # addWorksheet(wb, "colourScale", zoom = 30) # addWorksheet(wb, "databar") # # negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") # posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE") # # ## rule applies to all each cell in range # writeData(wb, "cellIs", -5:5) # writeData(wb, "cellIs", LETTERS[1:11], startCol=2) # conditionalFormatting(wb, "cellIs", cols=1, rows=1:11, rule="!=0", style = negStyle) # conditionalFormatting(wb, "cellIs", cols=1, rows=1:11, rule="==0", style = posStyle) # # ## highlight row dependent on first cell in row # writeData(wb, "Moving Row", -5:5) # writeData(wb, "Moving Row", LETTERS[1:11], startCol=2) # conditionalFormatting(wb, "Moving Row", cols=1:2, rows=1:11, rule="$A1<0", style = negStyle) # conditionalFormatting(wb, "Moving Row", cols=1:2, rows=1:11, rule="$A1>0", style = posStyle) # # ## highlight column dependent on first cell in column # writeData(wb, "Moving Col", -5:5) # writeData(wb, "Moving Col", LETTERS[1:11], startCol=2) # conditionalFormatting(wb, "Moving Col", cols=1:2, rows=1:11, rule="A$1<0", style = negStyle) # conditionalFormatting(wb, "Moving Col", cols=1:2, rows=1:11, rule="A$1>0", style = posStyle) # # ## highlight entire range cols X rows dependent only on cell A1 # writeData(wb, "Dependent on 1", -5:5) # writeData(wb, "Dependent on 1", LETTERS[1:11], startCol=2) # conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=1:11, rule="$A$1<0", style = negStyle) # conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=1:11, rule="$A$1>0", style = posStyle) # # ## highlight duplicates using default style # writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE)) # conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates") # # ## cells containing text # fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") # writeData(wb, "containsText", sapply(1:10, fn)) # conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains", rule = "A") # # ## colourscale colours cells based on cell value # df <- read.xlsx(system.file("readTest.xlsx", package = "openxlsx"), sheet = 4) # writeData(wb, "colourScale", df, colNames=FALSE) ## write data.frame # # ## rule is a vector or colours of length 2 or 3 (any hex colour or any of colours()) # ## If rule is NULL, min and max of cells is used. Rule must be the same length as style or NULL. # conditionalFormatting(wb, "colourScale", cols=1:ncol(df), rows=1:nrow(df), # style = c("black", "white"), # rule = c(0, 255), # type = "colourScale") # # setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07) # setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5) # # ## Databars # writeData(wb, "databar", -5:5) # conditionalFormatting(wb, "databar", cols = 1, rows = 1:12, type = "databar") ## Default colours # # saveWorkbook(wb, "conditionalFormattingExample.xlsx", TRUE) # # openXL(wb) # ## ----include=TRUE,tidy=TRUE, eval = FALSE,highlight=TRUE---------------------- # # options("openxlsx.numFmt" = NULL) # wb <- createWorkbook() # addWorksheet(wb, "Sheet 1") # df <- data.frame(matrix(12.987654321, ncol = 7, nrow = 5)) ## data.frame to write # df[ ,6:7] <- df[ ,6:7]*1E6 # # ## Set column 1 class to "comma" to get comma separated thousands # class(df$X1) <- "comma" # # writeData(wb, 1, df) # s <- createStyle(numFmt = "0.0") # addStyle(wb, 1, style = s, rows = 2:6, cols = 2, gridExpand = TRUE) # # s <- createStyle(numFmt = "0.00") # addStyle(wb, 1, style = s, rows = 2:6, cols = 3, gridExpand = TRUE) # # s <- createStyle(numFmt = "0.000") # addStyle(wb, 1, style = s, rows = 2:6, cols = 4, gridExpand = TRUE) # # s <- createStyle(numFmt = "#,##0") # addStyle(wb, 1, style = s, rows = 2:6, cols = 5, gridExpand = TRUE) # # s <- createStyle(numFmt = "#,##0.00") # addStyle(wb, 1, style = s, rows = 2:6, cols = 6, gridExpand = TRUE) # # s <- createStyle(numFmt = "$ #,##0.00") # addStyle(wb, 1, style = s, rows = 2:6, cols = 7, gridExpand = TRUE) # # ## set a default number format for numeric columns of data.frames # options("openxlsx.numFmt" = "$* #,#0.00") # writeData(wb, 1, x = data.frame("Using Default Options" = rep(2345.1235, 5)), startCol = 9) # # setColWidths(wb, 1, cols = 1:10, widths = 15) # # ## Using default numFmt to round to 2 dp (Any numeric column will be affected) # addWorksheet(wb, "Sheet 2") # df <- iris; df[, 1:4] <- df[1:4] + runif(1) # writeDataTable(wb, sheet = 2, x = df) # writeData(wb, sheet = 2, x = df, startCol = 7) # writeData(wb, sheet = 2, x = df, startCol = 13, borders = "rows") # # ## To stop auto-formatting numerics set # options("openxlsx.numFmt" = NULL) # addWorksheet(wb, "Sheet 3") # writeDataTable(wb, sheet = 3, x = df) # # openXL(wb) openxlsx/inst/extdata/0000755000176200001440000000000014155600363014544 5ustar liggesusersopenxlsx/inst/extdata/loadThreadComment.xlsx0000644000176200001440000002613614155600363021066 0ustar liggesusersPK!LK[Content_Types].xml (n0EUb袪* h-R@"ǼCP[&Vl=ɤ7Xk,ceMκY%`-*3-}f a m٠כl`BsV^8GYYVkϸr.f;'. `Bj `**$k:%ƚ3ᜪ/ME=XV KZ`JBaBG^(QG58`gr]h:'_+~m<;-r \ ,VY3-*$?nF->Bib([=K *~kVF낅bAg[SXKPY{&Qv<mĩQ{됚nutH|`}{"5Ϋ Lr㯠PK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~JC礙VyK SdCqf1Omf04c|<X=PK!xdaxl/workbook.xmlU]o8}_i;JG!R;LX`7YcTvBNGlg#bccse6] dcr3D IWw4Ft@_tZ>iK"-0 JʡTv`uhc0xU[ @Y?Lhmq \Kz͂=@nMR((ޱȉhߕܟP {ܽ u \f6zcs0ɽbD[A$)hd+{3US3ž+Xet3B J y:ÍޱR1rB;Ec S`,F/,c K"M]ihFJj7:mub8U"R%E+HSUCl;sAb1={1igoz1CR'gY%D1mM_Ų&B)=UB0N}K6v6$L?]ed꓇Ph)#lP78Rm=LV}:Uo@}CLoL\~\]{rm_t5T=dM5<PK!\W8Ȝxl/sharedStrings.xml4A 0EwS]H.OILEoo\|xټHix@<-6 n%mWjFXH VT,dց+/l^3i]H OЋMZ :U=U Քָ ˤAت_Z)5mM 4 *QzXC{UX[Ů' /;)ŤB}뀧>2wz㔈nf8k<. #a %m"צ?$:SA*_rt˗`nlXϼ4-{ek}$?xevU]lLsE2xrPܲΫzHrPiH0ަEdPH;iYYPK!:2T#xl/worksheets/_rels/sheet1.xml.relsJ1FIi Эɝ:IXۛP]܏9_H4(Y`Cu| 11l &M#B y|Ddۑ7,H$MLL-LK;L?PϘb4? m,mUݣ`]"=E)4Aeˁe/nzѾM c1E<Sm2g"v, PƠbˉ^dNS;l͇t:=܋M#:2cVB*u6E0:**b>1oRN=s ι[JZIl͖ؽa$)+!,d? >(S7\pwj0~ThBKDSƜg~$/vWKt]=;'sB _%$/bo+&İ#yZk,*A @ԧ`U#po':R<-9(d.E%,nýSn${+ytlF~n2\at;-鋾zvυ)1 8E>!* ܱZo =Eq2@ZyѦsNhSj=osTyZqՊ ^pJ&ăT F =H/vx:7â Oo S@@Chsqa!m8$Emp i\2fE5cBo mjpiJf[]Jj>6%QH8ث8} tSi6>#qprOTL7>i ]qz~>q OZP':Açs̻;8a~J %43ez;DF6ۖwC$V,Aż=Zx#i0}FOܔDA/(1gcbFI7(捱}nX`\/1aY=bVPK!N xl/theme/theme1.xmlY͋7? sw5%l$dQV32%9R(Bo=@ $'#$lJZv G~ztҽzG ’_P=ؘ$Ӗk8(4|OHe n ,K۟~rmDlI9*f8&H#ޘ+R#^bP{}2!# J{O1B (W%òBR!a1;{(~h%/V&DYCn2L`|Xsj Z{_\Zҧh4:na PաWU_]נT E A)>\Çfgנ_[K^PkPDIr.jwd A)Q RSLX"7Z2>R$I O(9%o&`T) JU>#02]`XRxbL+7 /={=_*Kn%SSՏ__7'Ŀ˗:/}}O!c&a?0BĒ@v^[ uXsXa3W"`J+U`ek)r+emgoqx(ߤDJ]8TzM5)0IYgz|]p+~o`_=|j QkekZAj|&O3!ŻBw}ь0Q'j"5,ܔ#-q&?'2ڏ ZCeLTx3&cu+ЭNxNg x)\CJZ=ޭ~TwY(aLfQuQ_B^g^ٙXtXPꗡZFq 0mxEAAfc ΙFz3Pb/3 tSٺqyjuiE-#t00,;͖Yƺ2Obr3kE"'&&S;nj*#4kx#[SvInwaD:\N1{-_- 4m+W>Z@+qt;x2#iQNSp$½:7XX/+r1w`h׼9#:Pvd5O+Oٚ.<O7sig*t; CԲ*nN-rk.yJ}0-2MYNÊQ۴3, O6muF8='?ȝZu@,Jܼfw+zLdrQ J<.?| .xIOjB*2ǕdZs i4}0ozWey+k/PL״fࣗ1f`ίO֤@ - :%29hޒ.jk: 8B%? aXl"z^h8쯼+Q=$ 3 1v8!RȤdL1k籽Qs`09βCl ?sap4s7>9O{wy^TN>cdrɺ]wc8vQ^_g5%?ZPK!IudocProps/core.xml (QK0C{s"@e`d[H`$ۿ7mZ|Lι_ι$T:Y Ah^ z_9ϴ`U@GphVP^[Xڀ\HQn P߁b. Mmh0Ŷ3B DtB > ͷ:*Pi_ՁN9G:ق>89II#OK*s)|m`:^0+!Z=x9*+"l}#A<N\]!QIJge=yz^QiwIF g _SSJȈx9(PK!Mpxl/persons/person.xmldQK0Cmnֱv@sܮ&)qnz9dvDO TC:mFAZ%g3ۛi ,",UЇ0.9G#)5\Ni( <قWB]m +Ƭy#xbƍ~Liy~&Di'5}V||Zm'I^6DiI6{"+L| ѿ(&]ީ `wG.wm$PK!ɝxl/comments1.xmlRMo0  t-'uċA(vCуjӱP};G/ ֛^+8ҚVs{d0P`N٦N֥M@ 8eZضh覶N@s:oV|$ 4쌐3 ZCV. X t=u]ލֲt:m]oR(eXXǠe;]cy-eMWnlϚ[1 x҇ 8s0cpm7Kܻ D:`OB cotܸ{5ld,Fz:!Ac;$|aN$3P I`=TYS;B-A E (mf0x˓6\)i>f8Id5Om̦vלg]E *\-d|PK!8F'+(xl/threadedComments/threadedComment1.xmldMn0Fz{&J baMU%ihi͛oaauhbCc,h o秼fh:Jg ^!)ԷGflg]~l:`NT0p-M`]M|%4E_f5rZc#\.kβXfqӢٗN/Rm+"TUdEԺH)wnj+ F5+վjGTI,䖤;.Y%*aL&8>ל:K7PK-!LK[Content_Types].xmlPK-!U0#L _rels/.relsPK-!mg7xl/_rels/workbook.xml.relsPK-!xdaV xl/workbook.xmlPK-!\W8Ȝ xl/sharedStrings.xmlPK-!̓qW xl/drawings/vmlDrawing1.vmlPK-!:2T#Exl/worksheets/_rels/sheet1.xml.relsPK-!3{ xl/styles.xmlPK-!w:sxl/worksheets/sheet1.xmlPK-!N xl/theme/theme1.xmlPK-!^YbdocProps/app.xmlPK-!Iu(!docProps/core.xmlPK-!Mp#xl/persons/person.xmlPK-!ɝ$xl/comments1.xmlPK-!8F'+(&xl/threadedComments/threadedComment1.xmlPKW(openxlsx/inst/extdata/read_failure_test.xlsx0000644000176200001440000002267114155600363021155 0ustar liggesusersPK!A7n[Content_Types].xml (Tn0W?DV[$xX$(}'fQU%Ql[&<&YB@l.YO$` r=HEV5 ӵLb.j""%5 3NB?C%*=YK)ub8xR-JWQ23V$sU.)PI]h:C@im2 3 1 g/#ݺʸ2 x|`G㮶u_;ѐUOղwj s4ȥ-ZeN xe|o, 1ysi޺s V788wa:  CrhݝAPK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~xl/_rels/workbook.xml.rels (RMK0 0wvt/"Uɴ)&!3~*]XK/oyv5+zl;obG s>,8(%"D҆4j0u2jsMY˴S쭂 )fCy I< y!+EfMyk K5=|t G)s墙UtB),fPK!Ddxl/workbook.xmlKo@V{~(ZH\؇.3C%U/هόJ`0:Q/47л_oF8tΤѐ8z}0>'.U 4*qkw,ܕ^ aд#$= SZĂdwܙ{p}]p*DlB)Qk2qx;@ҧt4?mM S;]nb6~.3d3CAl"а6e'0%,VvxΠ,` ֦ h3}}"KH41vk!j7Cg1uBML? zkjj+)7Uaн6 "UǭQ+.WO%/N;OIdIy0Ьjm_/N,}W:=RY}H9EbAwk}m PK!xl/theme/theme1.xmlYn7;{O,ْcK6qbJ˘\݊X@Ѵ@o=m$@/ӸMѦ@^Cr%ۉlq83ܫd !)ϛQr%B$I3^ZT8`sҌDF6*^W)\(UX_Z1 cy$gC.2H܌--W*Kyr! iDx\I=3Eo!Dz:Ĭ:~#T 46.rS :槜WN,5Eҟ.ZW y\iwSyvjuqeֺkDe+J;WtnZz 5 6_6=Y|}_km۫ހ,~u߽XxJڡn)} r|Rg(it%ApB£|@̓;~ųguڳE2ƌܔnp< 0h UN"e$t?Uӂ&HTp 609!۴ {өucCbvxUbL皘~xЊpVbUB[ )z[n|85ZXy aFE/}.)Gz>'MbeF95طX,Nr-Xn⽷ҤٞyItd,GGͨQ_G(E3B .uYwU6OMf3o6aYk {= ס P !6|r.νP38;YnuN2MOu0߬F=[PwoŤm Vy+nF:_*BEJ㮀k5-p= !~P9ge>S J!@K&NV-.+LD9ª'4=B)a d 'V>6[XvkCQ}ksv\?Q[5.+ٗ%@=V>ly /i؇`ҢlRV^F yYNׅ,}Jƞgr^.NmGT7LgFTdr7ƨ1D+IS ~}4f݊RB6)^SO})VTLT?҂ojMIX%(F La*S@џ׵W(Q3uXbzJpH O+jTeWU ğ%AZKn#KG@8D/~H"h+_hFb~Ʒ-DtT}|ϵ ŕFLtCKpH7^t%ijV`S22m~|T"{b5g1~ ]FИ+H~N m#KmƟڃڜE+<]vd딋ٌ|6%V?+&µ>ofi FAOFu,D b wDhX.=ao0$qmbZixK[`cX„ް'SƣkAйͳA{׿)<"#^= %D,K =FyZlw;g䰳p3T۬3*:Hwp 5kwMf#8 <!@G94g ~Pp⿢D}S"J^J"AMNUKS[Z}8K{UǦD*GST%crk#ac1wH0>0RBUEe3j|iT{+W3 +UI!$:un%s.HA|r:5cjYX>CjRQ:y[Oe\}@ַw2J ZWj+uVRC8תԫWkjusr}'ׇ  /B[*DX/B$[lblPHnP{{q*"-zK}ftr܉qG;WpVbowt 'j+:V|)0Ld$2& !9CNLH%)G'"ʇivxam]atGqhtgPK!edocProps/core.xml (|]k0{M^Pccw!9me%IgmuAn!XhW*A4 %*]?G\I^T tU,j&* ]ՠms$ef)+n+]rzk.pH .r--Я" A@ 4KkAφ2R=.-Ź8&MMp)~]?>uQ\X &4p[t{0W6ƣv7vVAޝ1K7:WdfN}wYF).oC_/rg =![G|!h& 69ޏ6X^K3}e0w(PK! N1p'xl/printerSettings/printerSettings1.bin``P`aHd(fHe(b%@C T`gb /YX0Ls0ۄ#!HG01IGyP@4sOP% SݩVS*ɋf=0jԠ 6 GǦ&dG3DsB+h썆hhQ!S1  9!PK!udocProps/app.xml (KO0H{̌Pu$MA,=Mcڑ%j8()bwGǟ-fMhxqg,ARP;X_C D )+*ŵ҇؆ ei4̽~ނ#>I_v>qwM [>uw][[kG_Rg >\H e*+,\FcY* @,@- (EC4yMX_!8kT0Qje}նF '~ؕC6?X qqRx<$z|ۛ|q|<)W=\x<J(bۚ\Vmx|^o~l99J_XPK-!A7n[Content_Types].xmlPK-!U0#L _rels/.relsPK-!>xl/_rels/workbook.xml.relsPK-!Ddxl/workbook.xmlPK-!F4 xl/sharedStrings.xmlPK-!;m2KB#H xl/worksheets/_rels/sheet1.xml.relsPK-!J xl/theme/theme1.xmlPK-!Ɨh, xl/styles.xmlPK-!Js-_xl/worksheets/sheet1.xmlPK-!edocProps/core.xmlPK-! N1p'xl/printerSettings/printerSettings1.binPK-!udocProps/app.xmlPK &}"openxlsx/inst/extdata/einstein.jpg0000644000176200001440000014613514155600363017076 0ustar liggesusersJFIF,,C (B+(%%(Q:=0B`Ued_U][jxjqs[]gɺǙ (6!1A"Qa2q#B3Rb$Cr?[v0L/acH5YQؘ}!1Ƭ=5 Wb +tU!6$جk`"vRPʴ-0]P!XXLb_#ij 7]Ɲ1Nػmo`=lkz>lkk@߀] ߀}4TqJOFGm-UPvgbCN;!VbLĀH$4Պ]IP o L} J !Kd8:7%Mhl Hy)o8%:aLC_Bn÷@0ŋ=Wؚ7~T lvʺ@!WU"~C#ziJÖA,OV Ke^HiwHMZɡ2ƛ[%lv bv ]%RD›cZlMx[Ml:aؓ.]/!%;.lc*(Z=tC~`ҽ - ݂mU !h4:޴AQ&픗؝h./$O"KocKM}#LXѲ{cP N#).%o>B-]blF cKc vK[4 b/T:4Ay݊N!*XðbVB.A~99y2Tx"T 4AI:oUL).)6ؖbj*> $!CL-li[7$i)RJ{дn'օ.V-}v{tVD7",ށm{`¶ KȘ !&:=Wؕx BOk= B5Vz7@e]4hsҫ2@Ф6[О?A&T_Cqծ;`+ aav;vZҮ’T WA}^ɢ'F jE69KI b6rЛɫB%Xr"lD$f_CC=ؒ 4ջX&Sw^& T%i$ mPkb}1V ]>(: ;5LrlMݐ/J7to} }ACZK4ñLchCMt1կbBLr[Hzzm1oh|l* ]m eoFR>l2B*hӍ[ТS薪vm1"N쑦&'bڢ?] n4=Vd+11Г4-I  I aa"_ȗ` x%4d,zs nHa!JbƁ;my%BƭX[zB[ci|WbRC?ckv:{&Ѽ˔p[<잧&I[ٓ)vSzNnx%$.rjuI-.U *oL;T#XT/[:6oH錱瓨U%:_6?ik!?ͯK݆XN3VD.|?ANo#7EY2ÒL卭ko`xz V.Zk,i:%ݔɪtƶ銸h+MJ--? _ ,Mߑ7b db&ʰ_`{)*X* ^F A6 "LI^SHmay&B -/η)}dɺoI>d~Ź|ax֌ybuTT ǭˋZZ솚dr{e(NZj: mkil3l^͖.zbxc{ao䌹b$cwെYfW-EG>Yucr,*WM'.55\R=C,3"VYBOFˑl 1d_dqF :DZT_+'iR[-4)M7M; :$ Aw8(t7T`A%O@خod& ,b1l*ɭiW`HKe_ *ZAC]X$:  %0mV褪< kQH(Mo˄ur42{|TRL߁JMtO$T/#m%&X1TKV>pRM*YeF8I*t;r6R+WfLX#z׍/3>SnˌikHp;<_,49KwTo48zlf$4e/K,ѧ Ų,29idy\F|Kr)|5}Wՙ2bދ։n5cpnZ!u{4QIWVhHrW'xꦿwbpM4jqOS>NU'8G>cƾK#56ܥf{N8㵦_(|1oɇ$(zZ)b}<\ͮ1eA9= Q-uMxid&4ɔ% :"qOqtV +EcVX.qɜdՉS㶄'`Ɲ"hfuL$q_m6ErV:  $KtE~4ؔ7W8tάj/'8aSr&Rl1p~^Y}rQS'xꝾۥ.%^|crвO⾌%Ӧl$o9\PI7)bLǁR1|7y*2TMV)F70 #G^,QVӎyIԧnyOJ} 2BTezU$e|qݣ/zt>>k%MQ#(9mӯ6&KB] !iV:Щ B_7cD8!qq% B݉*} c&Sj=ʵƅ<ܢQnkcڧЕ[gJ- ʓY- 38J2d薫EQZq^)4&nѤckfѩC^ ¢:^nےO*MiydR nSfN m0Ʌ⌴N9BEF*gF,q)md)nNO/z1Wp4$˷%,>0]!O.8*HM >p,\TѶy!'Fb1xuj3PJM;Cħ gwUIvy.'n6\W(k=7p|$3_Q% ' | A5qFmyXܛKcOBH/`ݱ7 ,h0;V6G&; +l\\~ݲZ‡l?WBm}/Z"smk@۽w!oeFIvDn@GTɊ.?ُ ުХ J6'%rU5,ji1cF\2Ļ[2%GfXNo$tiZl\Sv,̒uqtb6U,'N^ͭFxB8ղeEtEn #41vT:9Md~<$dd-W0>kMRIMu* 'DKdkχ_LNxԭee,Ytyr u98SvfvzOP}^5pZg,׹ɮv&T%ٮ%o#ة&BlV ` Gz֘J[ -D4o]iʓJt 46q_ɝܾi%DKi%d"^M-qY#TNO0j^M,[BŚpUz4ܞB94M%1ǹ.^N3$1.-6* 4m;9%VOnN)|2e|7rS7X}s,.teVԠ䌓m` ٧[Y#^*r5iUE X_ -Y[2|[ޟL&')oᑭ=5/)ʞqmٟ)A۔R伕 zz4bwDm2ɼ~֍1ʴMm_Lrδ+FY"ņHߒ񦩗&0ktWQYLZfj9ZM5)v?jP%bZͥxތ:InWx%jNi( ҝf?/b6)cmo\dYqmy)Ҥ1w R?r?Lɰ jC†v_J+5Dz+!jM ;tKt'kD`jpRJ$'UMIZdF]J-ۖJ2[];$srBҦ&4wR\^Zuຮ^}xM*OGSԄrmm^lk}$x3_S1wќStTe$/|r%q`Y* TLӾ*ۭ$ Td y}*윙cc~5I&8VbCQ~I%eFRͺh+卪amwũ9v';vT٪oA>M'+_MjwKD:Q t'N8 jF;1C4PK6KbXiq/ݣH~iH[Dy)c4xڴe|ĭAy'$wdcnɮFUQęzfIt9ʒ3䥐|S]kWQ\4TeH0Kd.P$rʴ'?܂ .4|txq9G. M{]4\3R 蒆|mk//tIņ2]TLŵtg.2e%N{HB`؄0= Փ>UmwI.yvE/Z=Unީ T_nWz-RRʜHPH8JrB^/OC\S2:MhiFV/5-2$)qvT_b;4I֊]躽6a$wfpc6MF_dq^L#XKݏ^\PUbt Q-t:]v6[q*ѭkfM;$I+aɫﲡHkr)I7- E%ePR}VGxI4Nȯm$ R9AHOcu Uq&M{)J1Iiı˗e[zBQsZM:Ǎ5D~DE;{U'JNXwb'4VYj 48fr5dtrFSm|IqQn1qmΚ9狋얟D6}ZX  ;ЊM`КNZkWcK[&Iv R"_c݉t*TS/Vdۡ9ŮȌ9ONb):vT."* B4Rm&IKZ8T$tEIx-OѤg}C8ZFR[hѪ_]<Ś+Q6_%ŵ*%]񤭄cZ1Cg^ t8vRz} 6%l*h18 -lM$KV<>We'$CRirԤthuCi7 oqU]aRq0,q PWpcmRX?"kV?l2ssǴaeUlݭUI'_k9y|6W"Bt0X4B!7h}=;bwO-6ļV$5$IO@v;>DeZ%Ҫ%_XeI(/5[O)7oE);z/]*d%-jSy-66&;9_9EMWu'ÿZ~ &`kVǕ{ZI=|ҩg+rVVrrF:k_/Sd9d.a#Bxx 2F=9Ek%؟)?n;\Mrdi=n]#5etXmeE[A!eqVG>roLnR)fIlR)Zbki-r頓5hNj!A&qm*KEb-Xih߇uIR7zi^eWá7J6Oȕ*\t)Ъ!WN/tR5U6CtjW?W+^•t-n62%QԐw!/hT[̗l.VBZjWݙʐ.ԬyRjSS~AYGSg\\rOg'û_s5y8eXJ)m1WeSDrXx@1C=9̥="^ mNqIW&f&H5WBr{&M:T(LR ofT3gUcTc6j\?EƄi1m-쨵2NjyѬ^履N"Ckd@e%Z? TZ}Xm7B )*Itm8MtOМ_k+R} _*@t6=J j.aS]DN٧vk"qjL`SVі<1g^SjNM&r{.Z׆t)_Ɇ].[}3pF/ROߏ ^iR_OU] %iPb v #;%إVƗ%aE-7_@IHYq^~Gw%n@"ߚ*y9I*f-VIU7~Io,nǶti ߿>JRӫlA-I!Ew{cl>ƺŇ mIPqQ~ I@Kv- 2Chm$ SO}&4D1j8L w$T㠄vҍih.7aqƥsdzbj-Uz9'&~k cJ{g7)䌱ʤɪ1]Tq hh< ?Ӓ'DIj^>E]=y܊i6/v ;wD*wmE%\B)M6>*.\VF^٤"ˢeucbSKš[T+Aɷ&CIq+LMUC*bi%'v ] _Q-Y(.KvK┕27RJƝ4!g.LSG.L0R%=(熚o96Х;GReEj$:`؃l4CIue{ @{^ T=|_cQ|] ZWIC[d̒m\{U٪|pfNQzxHxofmҤf EQNŏƊ V*)c4 RЭ_`V;Mw~闠۱-ΐ]Z%ޅŭIx8IW..gmi46М*ݢZTOb߁Ih%1-6(%KDՕ4l6.0Ʈ*)tp'SGy9*9}L%h̼fp+%5`'%'=i잙/.­5x)UblIN?jћܗHܾfkc}_I$58EZDy+JW-}XJ8ދÅF۳e_G;xfяODEt.cQBrAo65H.»CIiPm vwR~6`-h2qtZk_%.Z8M/[K,:do)>RI(+K[設ZqiCD{Lσq8ޟ^,ϻ 65]e)bt5nj.ϒ<'m0]t Avu@ g y^Hbv tm OzhڷaqKpN;֨#j=lL#jZ:⚌e/܋r4ƣߟN(NkK[rgF8mB/'J 4J/+N&=TqѢ])cIi K*qTRv;aK7z 8п/Ƕ )zO}tġ@bd Ǐ]8=j/*OhN5N)dOaGRG8)ꆘ&)'flq[EIvˋkD8Grϒ5mvtIOg1)v^DJ lJSNƲNT'_?$h~g5䖐,Y^OӟVK1̮y(?$m+BsH.%6)B5_8wLG)=9*F2ml3 j[6դtYI'`GˋWi)-('Z0́I]mGΙos$rřIS!F Lv&4 )3Ӗ!=*N3k&GH#;W\ZwbO)(ziRi웽>OfEʦe9'&!*9ިq4 wo˳|oT^9V|>_[uKze.6En6#z'S}(%%SJË)[}kxQT (V $Bh|mq[~}bib$+G-}9B*LOjcYh5^8+FڦLG&ThSTL]c_u.vZ2Zi\VK pbi_FlW .<إ&׵zqjQ=CX AhaC^Z &؜tKA$VLRWdmdI()8IlH`oKdOӸ`6loCpeF;i)qRޢ'6=I0)vT,DK2"JrJlcPW_Rcq-5B>?aLL8KJ[ P~ -yO;OQqbqtf<$Q(w5Xȧ%%3Ul wz.#锛lMxbEyOBE'h[)4[m?¶ʙqz:6xO6Tgc%ܹD.LNOd1B 4zTҲ5%Y-:t$Qch\i ޅ(cqvqޟLqd$w7,J5f{.]7U䜜/쌹c zrDqq#GF<:c='ԭu&lirEr_A r% RjVWanEΟCR$4)K&I{]Z+FM|&tBJBɏF/HћHWL|Gi(fi88D}QM3ƨ1XSJTp*ǖ힄3+PY=8(?x8Z#á`:cHl@R=WIIrv?mbĻqF{NﲖYREERG¾t !j+q[KN] ֆ}O#ԁfe,ɍ4Տ\U!]%MY*..H]IkFn5)śɎUN>2T]ɥFm;Hm8ԓXKmԩU;QҦdӋY1qi5yq^R8ȆLy_˖ͩsdߚ9jHA z^Bd(ћ蔒N)VV`Kn5ԥeB7%(8nj6#ܥ$h&bZRa5mnI쬳㊑B.Wz8+]rׇU\f|>qVU$ +5O=*#WBv5T&ՂOoCUeZV1zJ,: #H@] S:q{5bp7\wDM;[FOx]CѮ3N:v3i [fsR%%Ѷ9VqRW"1ރdEI Q9"pUW.3'tԽ)Ԣבe9Sļ}3/FB] h6@ R[t*-TRV':Q]*j+^ơMd];eazG[5\{1y$QV[ 7rm8WlߓHF7.5%RKȱGo}dQ{85v+y'J#l;19r=t\c>EOZON¬KHjm`U1UhN/q<ǕsYEF4! kE7hV6ЀyJz9v?+JKQw9Q1j,m1Q{*{k+k MEc Ѵ`ƾK$ͿNL7W4fӓFLO:O|%KYYG"1+}x/l-:Dҿ+u@2B@-^]߂)l9?AO7+^=r9APEjiZHdJ6gƝˆWm)]?}Ӄ4䉓h*T$[I |t[-+[TP}HגU3KR(B8q1/ޢZ<`&ek,NQ}c|?cgQIh46@;=gO'z&~Dd6Ǖ$?'|NN phXREZCF2BϓkfE$,i_frK}.UQn0.S}zL|՟/Ĝ8;gZđBZ̒HpʚMZCSm_4ƨ} ХM>Dع@ݡ+QV=t4)=V&Έi_BqOD6q>ji8ٛM !ŵ(Mׂ䔺F+%'kOܬּŦ]'DJ<]}x l|t]4Jќnfbu_c4Os÷]9Kfx]W8.Wt9- O8ٗOFO.G[ Msrǜ5/190@Q,<۲UKd6Jo9vKm]9G\3NҶ6hܵZ:>7K;&~}88dWT|PK&f%V5C-6⨘Xkσ j*(ƫ1E9K}`+&Xڝ7)MLb.8Z4CQH$aȞ ئNT4b{TT1=eR)IߋIH97KH=1[c!M2m'f̤erF/iFn%݄_E7bWDd+Ve8O5:ڲ[RКI1.;"q(Z4}dN_J'6~X"MR^6VlrzqMmx2qЖ݄d}AK:?"S]4%mi: @\h`د "RhJؓ~|yV:FMuJ]:0v߹RC pY4W'crNJXmlF[BqM%)^Ͱ7biꞎ|RNkGZ|]`6-Mc Ǎ/t5A%hMh-4iid4)TJKW@t?iybu1KI JcrbȜ%iDT6'|bG&גeFHq4B.K@7k}KXqo2vkW'qT$t+V4j;jOV&ڒ֙I-m5$ʟFqk9Zz:3c1ǬsBXSTdI= T5)F(>-0ˎX4@mGrWAU:݄%/I?El":xs}#r<[)ENLx'z|j1iOj],0#'QZ3㞓iXR妎d'QC6WBnwkDʬ@cOi!)/,Փ*M+ؔE,hq".Ik*Ze&(_64eKbUteRxG%)N䇏_T?Y4Xr_I'%'(>~Iz17vLnM>P㳮6ϣ2ʑIqmG?KOG`d۴GԹsfxe/O\xZv]2DIt!{"I)K^LEeqǨȷ2'h\.G_rRT,ۓ &]T\e4% Wɂ9]v40P =N]+KD=މ} ~Vެ)/v)>=\Avل*#Z’tz-G}?UL =4\pJjy#'%KKonU7>* V> OkKG,UUG?Rj׏ҥ7UA,@xej) E=G*ƕ$ɫAފQZ[&Q)^/] G4]b)v16_#КDハe+G_O%,5>gD-ޑ*ы khk͚:D*+RVqbi~ ZD5nN-W5fq^ :@Wj;5}j$\I HLݪ &ȸwAm2VqƸdA7Ļ٤ZUfriʉ밖!*htZi&ɢtD|!䩃JٗsqbZZfq>fė8NɏShIN==<:%3+Z8F',~*e,i)j[9*bh0#Ӫ>T$@Iɜ"5Mm%e&)ڔuDzhzw5vR-+;3:1ƗObQGȸI=#S4#Eܴvu~NMR"Z~sD[GKT$Ѧ<{s. 7Kz5LmhO˺+}GRВ4cn:kfɏv;ބwL/ȓ\|ŠI7\D➅q`ֿ(-2?<'&ZO)?u4iiPb I+;u-'iiєϿ"w`KJ9<4/ޯ~T45v/Gc̞L.^蜙!orF^3 -$5NiFM!.K#ab`gл{' v/ sII_Fg\z4inpݭ匡'KǪ&D7Oa{_ػBKǑoʻ`O-ҰNĿq^@KZe$鉻2qhɷtiLz V+$ QN]:bS)1y#6M}Kq;5jVFGjUJ?eM>/c2 /tHǕ+Sh͏'1ʟɦYj_'Ry1'6u,KF#O]4^Jv*Dy%+vVGk1Sk\deo3lYIx^7ޙqYgGʓGFX'-<ъ!6o#b~B vvbF-(>(ͳ~t8>;ZCm!r_+zhJ{.x[%:4k|ݫe&L(,c[rL+7 )KCSNF$mN(qiOبB; #HSy4mɶ[H^ЦoEUjɞӊ~Gqؤv=(Sz/fh$_pS z!ݏ9}N-|㓜_fji]8$xL/Kѯr/͍:}4}/^'%NFUSp%=_#Slc2? -/&tO5,Y!DcmlLάKJ5FQ^ |ůNyRdsAU:n|H3IE|gqf~t_Q_&э6DL$5+oDJN;?oɬ2I ]k5@y4Sgfft.:X7Dɪ3L?"%q陿U̒\HfRfwF~ Uę&tEވji >E"ɓJ=(S[Ë/줿cbLOi,O&N|! `h7zcuh.Q1xO$?m?x2iexdY/ssy֍OzTF@%| |rxihxOD瓾N6bܴtN0JNI0U9NSarY%a6Eɕ.voMUmNg=SoWSflhJ[.(̮6'$X츴> 3ZN"2R5$)E"ubhI+3f2ɤGG;t?CӶkӭԩ2%&/۸:_NIQъzii'tM9SFEt1m88elhkiY>~K{IV27' 4~֊䌲A'2M}q'}|(JTTq93aJ|":z:jHY+i(یg>6G'ҊnOg`x5aVX)Gᑖ{/璩|Ƥ՜kBd]XӱJZ>=$V\6c#4n+%*/ug[K0Rj3Ui$U8Vkƍ1ucmJ34&CFz /mTs\]"ӎ:ms9Mtj'qiZwe-vkFyB1si4ŕ&ݎV)Kx%yP&6VGi"d"xf:g,]= =+VUV^63>=X[FسqMIGjrGW[%x3Zm#UQOqb_cCtњW虤,rvki5j颖je5%9.˞>J0dk' 'Z[97 %.җB3IIc#'SfA8zWk$Uהpz'"4mTLhRt_Q.z%;Fm.KvKd2\DMԍ9vLqN>TBY%4sw~LMۦDM;3=WVl_szm[2Cj1R?oʢsʪ 6t ExT}dP&φjf5'm;: -&5pe\g'tw`GJvMZ"[&(tCv U ^dQtcեi/nRtBX-,PoRM83j3(i]5pvuzl{dIfde_rnѢV\[}|XocR+%ZO _CZhƜȓ^G~-lƉ=9Bl39EJ-y9\wuR#$g3J)%ƽy.ѮWm"<3Xktğ6LGsؐIDU %biKIgw=I:Wg>Y/ڙXGL\!Ɯ%Z:߶qr8/GC_lSQ?mIL~&~Mt9l^ _d"1iі%<+.k|KʥFgHRŧY%qLwD@ٜ߀JM&9&e)hJ}%})19O\]Fn9rjeQ_ɤK͎-VsG~?\r>OcKxɤ458EM?$)LItfݮ/e5MݲҢQzWk4){cؚˋ%Z%[2 JU+%&mg8R9Q\e(?S^cm53? ..\m=nٶu^ 룼yїxÖ=K9TE&R=.7tGC '{"dr V]vtzw8$=/K lҾQZiJ;Mtk1{Ѷ:viRlq>\da?ڌ+>bL͋h*=L^clɐƻ-F)Zwdn_GV4zޚ8ʻ9bgo(5$;zE!Wk NX_ vcvl+vDO訴ݶ 8{*vhL%عCrkN1mjQd._& RZt/QўERٟ:W}3'%F/HRb Ck8~c:{qtMdR]^%)c'\ǒ-ūd厜^d"Sx1A lM葂c&ZFL>Z_ɯe7?HʜyD]IEgI~N0&qf5]3QF /s`!rv_68_#JQѮ)z%(P|G^F2}265o/&җEE+H矵&B>2N2u9$8\NsګbD}R;tG`z2/ vK ƴ3dlOʗgN,*|z~OG=kQQITmw͑Km5zwO\a .F|o'$wU|nM?\Az92ee&Xch9"MrN }ϖz[!z9Z=oM 2vhz#,E[TF Of-Jd҅&^3ЊK֊Wb@)/fftlQfڨ/"w]^ͱ.dփU6;!*~%D]R֙L^8N9WFHv^IVGFsTH4МPfm"&zLtC[ );NsŇ|qӷ/ՖG(NMU=vk^޵5>YR\ѴqM1iF䕝m58\D'cRȓ;E8Q1)3dqII,aE-r_,.0N?jQ^IˑEof/$άxҊE$l%th@ /Ǵ)]1hI/)VNN) Rz2݉XJ6 /b8R%ʤvZϥjZN}eWx&W)6֊E-I^ZCJ*k}(.Ƙ>ɚMwד9v.\]t:|[#λ-5/D<3qP,2tFSr|:N՗)TRKR5JYݳz:.3FvvF+j"i؞cD訪lηeVoF.1(Kd7$%br䟃.=lNxV 僃]^lOGJvsx䤻:\.(qtRjG{f3"+A.i$8G̍9JuzKT"쌯Jɝ?Mj<;5<+Gekg:iE8]ׂS%/ʩb'$ߔwzvg͚cMR:'Di{4D3jq񡷿F9Ů]3(;uBqe 2W(H<%:#\9䑆x,8j3˔%3h#)2e[jNqGO>ܩl18NjU:1b~m=Q oNǕ򦇆[J6-h :8{Nuvbѫ_D7Jr٢QE~IsIvDcmMݜv=KZ:1‘i D^Hk99NM׵MN;j&Q4c8SցIVk #D<o/8"e0e8\6MA%**1M׎]RUDŨ=KUhg ɤ* }ГVg7A5$)E5MS#,}HGC%xvyy?]d}օ%jAg*N;L)qoFG RPL슓] XO ^^#2dH٬zI ղm"QBFޚDbk&Io_0IE/ iuq^7\7~EvuBJ݃\aoolR~NLօ<{Gd}Dz{LU8OD/e)SD)ƙOF$<ծyrt-5(-;^Q{Gb㗞94qK/q9jШ铦{/| m[+7t.WӗkkXtG9y1 *5ΒˣD2[$y64ٜ#EZQ%Eݞzo|E+Rbۓl")]I.rMrd~^BӊEY}2Ry\S"(w+}өIAPPFG)t8q9+9-Pk{I"8rd$M{<W՜09Q߆tPw[ZD+w.'eR7,mh-q"ڵE-6RUg8rvYi+l|%i*^Iԭ3H0g7Wo*{6*:.[vJVgɷuQ1ʪ\Ӷ 9J-? tٜx#OIT_.iCm9x8a.96Ý8KÕcGkJ[Kأ/p%q Kq}x3WvIrî/Qd.<̑pȚ4P9WeٶOvrl!p{Z * .m󕾖^,ΞsaiXm輯9הTd|KM&q):)t$wz)qNVz^*rF4ZKLUQV_6EoϒiRg6;a%h\ZHZ=++E'h4Wui j+J"cucJՅ"d*М-;?Z&qf^tCVզd*)-t 9YP|S!ɹmRf&h%qLSjz-iE/ɮ-T!Joю)#E:cpUќaˋ#Jjg"+Tߨzgi ~߃oS\(.3;n[_`BJ)*d\Z rNQVV)nPh%~YC}DjM/'|-8ʙ'B+'*) JQQc,R:F8a/wmӓkQ\+ \|>* DaގGs/f-Ulf-3|m hP:7*tgFQnCx *-fmf~G[S^HrL0OnM|r%liV{EFܒw9rl&9*DzLגLy6ХކoGœ$MMwQ<تan*${XrF?&ʬmニlWQtذ![KGZt>&nZ]^'coPVtAwli/$ Ćve qm3hlG)lM6 Gm^?`R~]Mџ6OThmc{BLS2de*H"}qߑ_LM;2$ŻCwgdWo qKNJ<F|V\Qi>̥"|tIT5x6E\NHOOi#Tg'SBIMG`OWG9gJO5iџ:.7H=<\dټW)r펮bOG"/K xrKFhVG# K%K -}Ǝ\ݾLKG!774%k/O }|u5(Kbmq'*4wfKa<&.I\(8mmr ;%xa9l`]zwR=o*,+E1%RkdyѤarzv=rmvzk~i bqV.5Tg-d9*~kt77Jͪd5(w M}aB=Sv0kd9Ul˲n 6,״udwOMOTGm0K3*~LMYfj.\[ 뛤qfjk-w:5yЌHF_GW X&kG4я"{2/uѝ%4]'IvؔQHiCKc'e{tRgq;q$ 7ɴQm͇lq~7-xG?Gf\vZE%4Lٜ:fi>KඨqJ:qIGИ0J0xbM)SC'x%hO]L̴%3}O}oc]Wwf')%5wl:"jMtˊDe`0l!fߵ5Ư Ɗjy__C0N;G_qLh엹c{lB)~k_&zιPo暷_]NdQnh[zhB yRT!Kln[)EA_9I&5m&R{My7it\c):R}l$xw4k+wm91꼔.Vޞĺc}W2V87tyY66==L.ݝJV\dE^ Q23Fm~v(({ќϏfUvo|͓@b^AObhO8r[+nòga& lw-×٤W%m[iQ*k%u%CY A"')h}"%)F,$֙j.<5ꡩ*?;}6EwzPU*ݳ,"Q*+LPko<,G(8 o=[O5T"5.NRm$x*+M%B⼍bW&4F-=fe^X?zJ)ŶgeUhi|y<_u %v|[;qh:ѢV ȗ'34ɒ䊌iG9EidZ'[*k;j؀ kv*˭ = Ih- SkDݙdoM7*6%ɓV/)U7dɮ:\o2ɪ$ure58=ك#q_'Lʒhud|U#͖% .=ծӦzq%NCzvGGv^#LO˼yRJ*= ?^:ToɌJRqEu_BfۣO 9>_dJ*PfSJgn)OGv9ڢX9;rn_D=dٗ?U)W@C'ҜmYPjvc*_gdvTBQRM4so3M!%'zv)lM Bз['+ɔKє{LwFwjV-m2=WXU8i}JfJ$G'&m֍(N82)ʍ$Eh__O9۔risSg7QrZ53gzY?kFJDn6<<쪮Q{LE;MשVz)sA9Tr?b>o~"y#ѿG!`PIꉶeGnKr5_-e.1'wF1O._'fG R~ =Fyrs?iDZ:2.3Ed?#-2y&RdN K9]1ɋ$!Yl=<Զ;'}K%e;NvC!(]Bȣ$sB'&Dc,GT1F*fJT8L,.,3ltŦ¾,V/"AiP8Jε$IIO}Rh%DM69*tL-PCyNpѺ:F9!5nYV8 WtRscvq9O&8r5~ތe&kaUtnh SѴje,M]eSi(UrĤ&3=F}6)igOY<9piJ'Lrthy58"Ƙ(i5D)4_)7c} aMvj ־ }kKqmK{Fku!7hWK,SeNSk}&I7an4mx5ɛioaRކHKeHۣJ(:|qQcSkyK.:kKnFq.lJ4q;['"[힦Et(3:HLpIA6k9r}qS~ MxG" %O^LGfrg^WN%2_j~:j9'-}c]hkJt5.odNMW:fTɒ!mw3 ubEQ1*[0R⌥nVӓ,a a9I?&$ TI$c3Ǟ)Kzb{TɝDI7G?WnNzJXOity>#ygT3Y\B`RE\Z_f]6TvW$WΈ$8Yхr[SN.6L׵63Rd<}Eвq_/ec㽦kghR+7%0[{1Ix+F+两[(Zf*O\jV.іLr[&RB_(MXE'سQp'(]/6 BEE;&OdFrlWɪmI_)R1Yф.M4* ϗnϓD1++NMJƜ.J/x+:؜]wtiZ$U'd5JwT8&ǃGm=4)Zz;QqdKˋ0h\<1R26|^4if b<.D"lV\ߤm3MORٌ.LkNENU(doH%^#ԦHڥ'S;H֮Q=ΗzHe/):WBĥ(iTg7.=YqU]'('{G/8nQP\~̥٣B8i>Ny#QrMf?TmxNi|"8߸mT+0ʢ椹[Ѵr%ة_a C4َDZvG{Z~KecJ_}15GGǑJ=f T'fy8Gϛ1^I_O8df=,[RkONZ%^CHjR}-sin1$l>mx2pt3Jqj:͓$`a4j9Qɽ<.RU0ВK(kGj zS)F"QUdM" 'zޚ|Ywu-|!NMiWK5qznѦU[uk VPi: ǎ1Rwؿ"QEj3?f<1jG+j+Y*Q}2?'ˊQȡɪDF0[^+D礔ͱMm|.lT,("%%.x՚ 9SlT,[Gi?/;.m[%:؀ca/ = ˆ6/}J%L쎴޶̧q}^va~<kZfY']1U"|lX8Q~˙n{2ÓOmsqrp&,(z2U:*x(m2sRr=B9rj7S+j(2J.Ή5-C9K~O4J=7e,TJ9GkˊM/qW7{^}KDCӨ0Xg7/P䤑ڸx I3W lG؜,|$"YfUvOjKc/t >oFYTbH6V8)7gKIKhm;|jZ}*%=4$ффUG^5QZufSkc&ÚuM)OGab` [ę;ʐ+cT$6b d݈RB€t%gw{QiדXPgu$#~Nbvc867GK,kognbR7A*]$׀$%5=W@ݦ)%JѲN+UL\_ h\\~"Xۓ`\G7KǛ,d2ѷǒ2MI/׏9q[oOON@ύx2Q՜ rW+-KhP_&)^ƭJ ]h>lUl ]4mZ"Vy~li8?$HCikjҊt><&zr䞉+#Lꕉ6B4˯dd=D,G4ܣJDmنFrzS!Im<8gƱZ5xԤ.RЧLR]FٛV4hRi<뜸Yׂ ^̜|IqVx4l΁!lCKcހh^@IևTƛB] Xt*~Ail:&}iIXa??dbd ^/ȓa4dxݪ1ނN Eloz*gix6.\8cHV64ӽ4ȓM% ,9{5rI#.{|n2ܤxgN8.bYxrٵ }۲mKDٛIY{ҋG69d12zW#tz'Y yyV6Hz^A cVptk RSjviy\R%6cylB`0*ݞ&(0JߓLg9c5.PMcUh-ei+RL"#iղRVɯpL1:Z '](]X\]ŞmΔj %+?vͱX4UՖEB[/[2:1!:R5Jʭ諤.VrTT}&>8W͒e8g$m&l&2HiKh((i鏱 4+ yPw~ Ud'I"Oab5tWA 8KK^n)9yL40:=w*z=TVuvsz\:[D~xD&x³7~(Ÿg_ќ-6NRcߺ͸9QTmNWQ#&XwV7j5^)R߃"pr^{;1EZߔJ4} 7mjVEEٜ4Wk<"m4qNfP;ƢR<[#4>WnFC`!4V*-лҴ> Z+H*)=i4?Bb:1MF\U#E/.^>~Md(A]'4S[&!Vh@  w=_AJ-:Fkd/lW% LŞ9[>t:e4I(ʛ)ɷօ$ZQN(}"!(|['dӍkfҸhgXI9r\[nzVƞͱƎ) $%f.#hEvN7oh|Ϟ\i~{\He|̬}@ t]*%ؒI'"nu}z *:*1>"Md5_fܘ&TWm4ix T;% ,a(La$jG'xȻ7S;:ՃZG>9,+ϣƮFZ_GZ؟9&qCW$\k9nT&ع&HIy4GFyfʛgoW-vuBuj)t)Wdalޭ&qcC}l1#d+"L,V/I?zXܚޣdr.iXبhw -n6含U+L+Vǫ)uZ&]:E&74̲q +xGf޸BIIN\u/rM.[)FƢ hMO02FTeŧ}'Χ4ZgZKkr=\oI\"7z"R4aRTL}~ xG$cO]]T4qM#1X^&-K zh:TRuixExCi4啾Ψ|SO76Ye);>\l  ?54C46*:IO #tJZ0cj09rגreK#zD\ٌFl1W_0ܭ헑42$k6I>Y'ycHrI.Ѭ1qT\*Dt]{B+l0@14FW zeqtL$b}$ڋ& t@ MnHn*&;_'c(U`E鷤9- wgQZOǃLv/Kw'rJ)J+tm$dI+8V$-G( `hKb!Cx`hg鞥MEJqN,$UJvFM>gErpξt- J.IǛ_PTrfqI* Л۶+⟑zzJ:z-7.J#oD_Ihilby9ME>٬#)'ўIqGmB]vy;lO1ha"lmWb]-OHO]CZbTi=IIEe)v&oOKtZ2S\5 Zsv_t\"E⒗FYVb/U*o M=$ )`o^J`0y:huKk{x|Uv) ;CA%+<`$V4Jt+ta_!/kx >U'5^MIъT[GTf%C;3^L~5D]5iq5wo,d]W튫1JM&4OҦ&$Dh&Hc!1 w~98KI=Yzq~qM0T8S0WGN.2i쿏7bȥL2mJ˔=*BjeN0ތd:m!9]1x|]+xy tLnIgF(FҶ?RA$cG99;b ;%Aӡt=@hQ- !z e(虠Q)Y6>Z&9-Ho$vGǔG;DelOq6Xo[[f< -lOD:!bD1 iݤrM?D<=?$>2qk,⩭hww͒MhK߀vKo)N0qA kv 7J%tx_/Dž|wdЂ?춭Z'cvC{d4l)Qudanᔺ:oZz?t.pCttQ'F[YAA]"/eXNIb#<)d䛽 )jQIʨJmiR˕kDJ_@`Hh@|+H*zܞmk{{7 'A`ō X!lm]領$W׀rV?ne>:![ʼnsu*/HqmE_L5䜙TdBYF >ǹ4fpjٝ-FEbT)$ׄBqȟ]18!+`MRZi0KՑ5H5$C؀:BhP&ӵ$_sF-wGeͦx3k{$.,QoEN5Tg9qF.W.=Y(BBc)v1 {2%m_8Ŭn3eYIfiV؄ Lw}IZBJvԸFfnNˇFD7>S uߋ:&&}&mԢ>ZꋂmTLŭҌڮ)sNQ2J*.%%-Ķw9YK٣9eiңGJP(;} }*3d%b9" / 0!y `L:)vCxqNiI_ꞕJ? Л]itJt;НUy *V׃-;/e%nz]8MWA˛ٴ񂚭"}Nu4=?&)OupKM$iEZ\c ,{V&[?ufjS`wd{ci_9{taU]ȟtJN/==Jb bh:(v1Liӳ;rzmҜ\Z]7RRr*9g7]cϛ#zF2Z|?U1x\#޺~]lmj ؀JËTGlƘCZ ]qօ/c>Utc]B-(m:djM/)lt^H'^ q+8~Z3o-:tzI8ƦJ.Fs,X$EaS*Lm[}ȥ>J5DJ)[J+3kgoo@MhM'Cq2v"'Vg-dB a`/@ 60bkD!oԓY rJV)%cb˺ekI:z\X:"tW]/V-X 0o])%ۣC\`G<;ًzb]C)]4+$[F67IhWCGwIQ7ХAzܜUХP t)/|9%qiB r{CyU 1u%vA0U3r77OJ-~JPJ?''WYrn/ki#m?=qؚծZ6ECttnb{Frܺ32!o`$1=!Lb:=.Yczl&3t\gZfZKɪ?WA`K&ZoQ#Jm\b4Fwx} mض1YOh}iYw†RC&M&qBZ'k{FhŤ7Ӗƣ*[yI;}J\W-< M+4JK_ Mq29^P:M&Os|pJYr7OFpz0M~r˓WHdȫi}^;*sN}tLflRX0y !I]3ÑJ6i%'qf3ԣ뮁Y<ȎD敔ﰴiU|Vp_]Jx^<ܤ^"5LLy L؂VU֐q_"T*t- ֐nÎnJMtöJv\1wmxXFԜ5NJp2ҏxA۲r*mE_fe߃S7Wќqs9d,1+t8䌵-PD W oKLфH c   i @wz?\3\VzXBq:47uG"q%qK#N>:#P밭]EEU%V)fmvy_~qቿy3)H[ [@  tP>JzMĮt8x4P_nʎ9ʗGV57#'* 4ӡ㋌-7vr{z}~h"QOYV/.sG]21}8&dQJ-hs[MWєVkitcN%4Wg|1ZmВ|hj\zFr&).dbl`1 <;5 @&T.t\'7Q;khuOx挢v5>J|vyyΊҏgr?2ں{ak@l(Cy P4/"!j6ػ{*؝XӿC_(M*0 lɝKGb5=mN]x qܜtZ&iJWĜڪk8JpOǓ)J_]uCqR W(I)?j1rС}djQn+''5Mӓڱ?0Kt˩i'9:%6岪k&MEo~/0`,CBh:B4y'oVT=sN֎^8&kU5%f5-2i'9zG<|rO$/@l1/^aaߑx 7lG$KZnDi[R&[@ȘWWqwz\*Թ7Kpsr۔LSJdMy14 H<'(CܧO=Biwbfk+fN+bIԛ1K^c?nPofy;HMZ8&ιEAbQՍڡ8ȧ8lɑٓb@؁Z&>6`AcJʌ}#Jn\2x99Ӳ87ɉ/0i 1%)S;dJ_ Zvu+P/:5GJ~*\B `  ;'6-1UaH-_#N,M;Bm;+h']y5itMT-?|tr}М}ܫA%jm5ǡJo6}]߷pŻqɗF1($t9~Hso72qb+^QT$ϓ(Dj;d%ɨG; >P4앷Cix iGG>LyI@ C$B} ,CPЂiFqJ)-YPMFRtVHr7&h8:gOX?'>5[[+'HԢy>nMl3 ؞W5M 6KOg}n+<x`A[- eGl%O o .@Ӗ:>%t;!%&)8tPCX^ɾNO$:o JRVi7ta.KuKLte mKdr + FY186كiʝ|t\(ʹ]0Kd ҹv5Ě 5nM쥤"Rϗ;ia /#BC@&5]WJg8'~ P^G ^ =mt[%ETZ*I({~ T]ӯ lHkGoRiy"}brx55SEo,|y'80O@4<(X kRv='C&.65&MmWBz|nTtZ8-ΜP$Ǔ|oM5e=Ti$e}8ozcɏ{ٔŶjp] czBI7+fQ4ħ8-+'&3,你kfi*-6h~]Y [\/Q$Q)vg#i'mQ%EٴWKGBT1C6/!V10%ackG@&KZC[y:`dܭ(-G"ڒ-ťj}aU+W=vx/IM*>b\drkӋm;c79Rijrؼ@ky aK`&K!גdExi%zh)7i||%[:JOTrߒoZtt%h K蜓+] }E͞o^p63Sr~Ei$TqRRo\~2ġ)=>%(5w sޑ,{Ғ>=+n1ݿފvtB;L@*gc4* ,t1P`0>ƗѦ5O/%7\U\N|eS^2ӟ*⻏B|]-n]Ba鄷+!}h$;'&65?%M&4X *Se.NSm*Z{/-XT*"t} _>b92*e OHqOɚAI9s2ӌɼ+}9RqMm\Qtsr|*Q8)8/&O>Pf]Ч*Za&*)/}*5ie=KW[vgopenxlsx/inst/extdata/inlineStr.xlsx0000644000176200001440000001176414155600363017444 0ustar liggesusersPK!8>[Content_Types].xmln E Ķ2$]TU'>mP(3I/HWE7eTz {'TPǣg{Y>H=–UrZQ˕7(Ŗ rgF.߀m{>d J&i e֋.bDz-@L%$|C3j$D8GŘ@q7=A|w6'_!->CX*eN[g.PK!IK _rels/.relsN0 @|EnH &4>$nă@ iqgE}'3 "b˲UJ'gdw(+x~ AVЫyrP䬯:h>XLia]NV[ꍤn*AkhEmKriR8I16=psP1զ7Uj7z뺳@N_Iؐhϥ2vO/ͧw:}zM⒯ PK!~Mޟxl/worksheets/sheet1.xmlSMk0W rvIeCP6עǶ1l$$z1f>4X>rr45u㕀5ʒJ vr"zD(B%z汐22IK~Pd=f& VnqbQ(hP7:^D:|g\mϳfR8 }B1VKO WPK!j%H&xl/workbook.xmlQN0ij#Z5KTBDi&4V;=T)p3z:6}uI ui>67Zpe4pBj{4h^jEƆiQKel=Q\k W#FEigQåT,ޔ]ڟM,*i{WA9mZ)ES?N@f 2j;+i:CVb~eǝ9Sid X?&! 9̳,/~cezpB'l]dpJJJИNgX)uGګ~6|0CcPK!?!Sxl/theme/theme1.xmlYMF+FY&j7Pq{cLvɭcJUiKz" 5RT/GfdaAg~ǹrA!m9j"mΠe!p`ҶDZW.\*"1A@Mܶ"Mۖ>lcy${#.b`)B;̮jM;4Pcz{4>A5cc(mLD"c'Sebֶ@N䁲RU?uŞ1V矒$N9w7. ˸^9s~9>X,a~xjrw*^Xot:oo,Uk ]e;nyV9(b4/x#38nޚ%ekU'jU>}Ŋ&HMS2>8 3x`N˥Lj[*byWϟWϟ?|vGl P'|)w/eKOfҁ/~dz'/uD[l3 Cq6AiG4{*oM13:꼻 xmrA$&7u0s#3IBp1qdwO7I!e7"5D$! e=J+~ݥGQSKtDi qPW|{u83!U$f&Ux OțXE&%¯8\*tHGHi-uo`Dưi\E E&M̹nԨ3M"CbǕQ ^l qpߥD02'Hvg"ʮ]1MNkƌB7Ќgmx4Jd ^{${rCwǾksq/^9$(cjMf J}9|&O#,Up5\}BUt8PCR.$`')y3 Aφs6*F`]ao')kJs<4TiMӬ!c0#A,,"န1r85z4iNtq q9D%{YR]#ʫ{qڶF0Ie?5 ¤m4|`sZ:WDB,*5{u,{n1ЍӢrC-쓡%ՊŲ'(8BC6v gF}Pnx/+:0K#\_uWz Д9⽿d ck#T0m[\CJ# , AYd*!ot%U(\}"AөHJ;_̩Օi;$ mf[(u9dlSu xqWL> AYfWkڣ`T8㣶nM/hTl1>D'JxU|s:42VAkEsԜXŽ=]m/dO|xdAi”,&=fw[PK!]R xl/styles.xmlTߋ0~_sz(B[n }50I$&F]h9C_73piyeMӻgԶQ+giX# >KϟK/%2b0=9I|K-!KkH?8)4$ph /g+PPY)Oʼ`C:-Wr* gX*Z20JGn%(9V4#wĜfV5)ߠ2"8D;$h5 5 D[K efGߚZ7 ZHheS-:}?ahcQ3Zn-Meu\ȸSwz3ea 3 2q6e\EE& q4#i7"W=,KzTpy 7PK!K$PdocProps/core.xmlj0} -)ہ@.- iZ?Hj}e'q)4.*;%4B4#(͍GJQӂuFChYߕ8xsƂ |AVh-0| ,:t7)յ2Z9!XA`G`jg": u@p (cQ|p_-3apzg}/&kO}5z\T %>u̇u\Fx~0ȾDGsҬP}CIcˋPL</?APK!^wdocProps/app.xmlN0E|E=uR!T9FXDXgX8IՐ+3su}2ڵ6 d,g8+%{ܝcrAJU",%8,YCnU8Kc&T-um4xނ#>K;WAu@vH\t7(otknwӡHAk^OK2ZYT2hс&34iks*dF9bۡ HQ csST Y $N|Iqc>+bJ<0 +~OmP.-7| [Content_Types].xmlPK!IK o_rels/.relsPK!D,xl/_rels/workbook.xml.relsPK!~Mޟxl/worksheets/sheet1.xmlPK!j%H&cxl/workbook.xmlPK!?!Sxl/theme/theme1.xmlPK!]R  xl/styles.xmlPK!K$PdocProps/core.xmlPK!^wdocProps/app.xmlPK >openxlsx/inst/extdata/loadPivotTables.xlsx0000644000176200001440000006324414155600363020571 0ustar liggesusersPK!*RY [Content_Types].xml (̖n0+;D]SV+B/mpX$(}'נb1=d8O";m8Oÿ"ARVYP\ j(/)1/R:\0^5vd,b #5-)3矗$Jrb }irEL*gVrR^`a<~g !;6X!WnUr^&M" n429hO+@>XPUqL+e8e[_ޓw"?ND8.73G7_ r}i71n{6ylxx+Ck;l-WxxU,? ̖Dž(>6BЏnR\Ew p ws>JA8G8mo7@yV0yB{5;rBݨj 26ƃWPK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~a- 0esskhڀJm+FGQOho 4}2/3om]2ZPB HP \;eŔ p9,|0SyQоvPJ%6:p U~aLf&{M@/j晭|/MgX ŋr8(Y<~ɸ!?;$O{>2PK!JRPP xl/workbook.xmlVmo8~+5&@J* UjWQm?F8[9cT;o[e[3g?3NӦ,g*jƫ/mӠU3V-ibLH+:0_hm~GӜ'z`RB4%/V$bꕠ$sJeY Ƕ}TV[P 1OVr "hA$_lUfe\ĜLhP(zYqAp;һJ ^h ]FPyyH.3G #2֪^CۅZ/>LlCĸ*CJ8)JQqĉ=ƖIFb+JrG㮣Cu߅@ۨt0aMiP 3{k;q:x8o=X6=qvr; v8t= P⬢@Ik#f)r0Uc(9j$))肭reTݛp_W8Q *tS@J٨澇m'PiGQ8KR~uԥe&zM-}xC;a +W];p-;x vkApxG2u߇]-PdDȩ XWq^dw D7w-ot.b/9X^|!MlVǡz&b;I * !i`7H"?=gI3.x$^(Vk@;C+dl!2Xz}'YWq_ Ǒ@GsgbEb X7&t=$8uSjpvP]Lݓث hoEk h7euתN]8:ˎVe-iu\+Ui{X 8r-'%e }ZX kM@̋X5ugrwxgVc, m9;?v,E%N1Af,p8+6CҼr~(jt8()7g½d:Ru0 ߀n3&o n*?PK!"G)xl/pivotTables/_rels/pivotTable3.xml.rels 0 PrDd.ۊ[Z:7cȟW֏i3hkXq^rZ@Ą4<)B]-FL(G)5 )R 4aM„)W {Rتɀ)VCh5luљD~T(og;Rgپ2COIٍ:R}W/PK!"G)xl/pivotTables/_rels/pivotTable2.xml.rels 0 PrDd.ۊ[Z:7cȟW֏i3hkXq^rZ@Ą4<)B]-FL(G)5 )R 4aM„)W {Rتɀ)VCh5luљD~T(og;Rgپ2COIٍ:R}W/PK! A G)xl/pivotTables/_rels/pivotTable1.xml.rels 0 Pr<:UBm--m}{mcȟW_ ub˝ڂ 1ixS}\T0[EpЧwJEӈQ:O7 #]:`~He| % RN?:JPK!m#4#xl/worksheets/_rels/sheet5.xml.rels 0DnRDi/"xkmmQͱmv}S֯iOlӰrƷi ]wM u\1#l`)5 )Rl8acU@sǞԦ(v*P}1ŹD9?w5t1K?"TO{Jy2*WPK!N xl/theme/theme1.xmlY͋7? sw5%l$dQV32%9R(Bo=@ $'#$lJZv G~ztҽzG ’_P=ؘ$Ӗk8(4|OHe n ,K۟~rmDlI9*f8&H#ޘ+R#^bP{}2!# J{O1B (W%òBR!a1;{(~h%/V&DYCn2L`|Xsj Z{_\Zҧh4:na PաWU_]נT E A)>\Çfgנ_[K^PkPDIr.jwd A)Q RSLX"7Z2>R$I O(9%o&`T) JU>#02]`XRxbL+7 /={=_*Kn%SSՏ__7'Ŀ˗:/}}O!c&a?0BĒ@v^[ uXsXa3W"`J+U`ek)r+emgoqx(ߤDJ]8TzM5)0IYgz|]p+~o`_=|j QkekZAj|&O3!ŻBw}ь0Q'j"5,ܔ#-q&?'2ڏ ZCeLTx3&cu+ЭNxNg x)\CJZ=ޭ~TwY(aLfQuQ_B^g^ٙXtXPꗡZFq 0mxEAAfc ΙFz3Pb/3 tSٺqyjuiE-#t00,;͖Yƺ2Obr3kE"'&&S;nj*#4kx#[SvInwaD:\N1{-_- 4m+W>Z@+qt;x2#iQNSp$½:7XX/+r1w`h׼9#:Pvd5O+Oٚ.<O7sig*t; CԲ*nN-rk.yJ}0-2MYNÊQ۴3, O6muF8='?ȝZu@,Jܼfwϭ.aBN5(m}g޲cpa팭Aٗ|LƃVߝ/ZLb(#yT\GTovݩRuN0 /C4 OF l ﰤoo8MX{)ZQPER` !*XPb_,kć͛=ةZlC?9B "Ť 5]i eӏ>1Kv^`2a=xQ!cP=O%B׃&{h=l`~ښR`>_ р_=9 p ߞszkѩ:@2~ x!Y/럖2 zny,&Hgwͦ\FItqm }VNBs`O!>^l9LFtSߗ[ߧe:=}e{8:P\oV DDxQPK!K`Xxl/sharedStrings.xml|S=O0ܑ' %J,-b%~Ni=,v=J8Ft2S2mN?6/w3Js]si4HUI՘{V(#cA1Nq>]:5v^I6BSR^p^tF Eb K.c~}:*_ZT<&=:Zi\>_ &⭐KF k]6 ya٦;chQnhBd"žV&Q>$[I:7mW<%SK#{iȥ.u*rPK!4#xl/worksheets/_rels/sheet4.xml.rels 0DnR=HS/"xkmmؿ7 Nyz(NV Xiח't ޑNzUhE8ЧJiD>N)S;Rثd@FC6[ro[kk$~D`'j| ;J\yw2*WPK!e xl/pivotTables/pivotTable1.xmlVێ8}_i{:p0 ѴZ{gM‵N9N7he@ ̨w_ .OU/w*&eR4P䊢`燮LG eQ<3 Wh4܌74Y?U/eb#RG[nL-0WBwĩԍ QFpJzWv QNYZtBQ[Z!ʬF Iyg MZ%:*2E*P( ˠ`YY*t݁{̺lb5) iZp.:7&V)#62_ًiMD;|>Ot8amBm_M ?3 gAҴ0֖EejmYgшc6#KϷֹ YiABI/Y28șq,K*"?Wv8Vp"V, uȮI[BEW O/o}FBe,R|~cU5Qv:Fix `x'z?wVn(0睄 {mY3-\?N@';D^iM8zNЙ5V E1#*F[tQ+E/42«:rTb Yw:N>TA!MhX$9=IISzZoy2]/YRpKlZ,E2A8>Jii+;iRUq lUqyROrC\o)+Օc!y˾:s~٢sU._Z7 -O, كZyycv9> 3VZIk,IU@}x;hRS؄]Q0opUc\I'ek+f0=-]\ѧ";'Ξ׬xJ㩆 Κg i@B01 :ެ]`\bvXY%lU6|P=@ƞd0@"[ e0@G%@hwܡ&sێ;#@&_k r!o9fb~k4;8ف0; 윖5MvN }P[3<~Y~a;0u ݖ:BW|k,]%y~Y&+ 9ٹaKռWAѪfhy$vDH TDH NĢMB@sj+-'YՓɂ0>h{8Y2Tm i)LNE0>PO_&ލf  AlAx xs3THQ'U&e0&\uP&_P  mw]Q+K48YzIz|TPX k:,1T{4]2ha@RQ!xaɁm`%G=rHC)GQe&j Ѱ1Z;cH+VSG1㑳G?BRK878) #HB_+-(e@SMT..ULuW^+Y4EQ?ePK!0=? xl/pivotTables/pivotTable3.xmlVQo8~?w N (ɊMRuv{{+LbȘ6 !dW0|yyOBj.&wFLe 8!FeF, Z۲/R?ӝ`[k@V+|кZnXA;YRTn])F.;[P^aQ)T|o "]Q]4¡ 20@FU"#dDx3g~%yNJK%_*'17GW+ G+A,E/]NWC.@y`@Ii󬗩 hcl-:>IꏱX~ 9%~VGQ@NKM6۶fLҮøѶc~pXvہMmF KD}ӊWI(.]qS9kmQ܌> 6hl?' iőo<{a/n^N857PK!Sx. xl/pivotTables/pivotTable2.xmlVn8}_@]hK Ų"hǂ(XJ(*QwHɶ|IݾępFϻBj.9&wFLe8cjMˌ Y9޳^|cVZo[[VNV\jƭ+hVoӅp%nEIeQQ\pXaSJe:pS%k;re]G|WnPjlV> CǏ'|GIV!^@8QI X!F)<FU%6ŚC^:}Z3U^#7engzF0ʨKZw*h:j*P+!T_YX5u>@~M͢F6m\IR?s-t)kk -XvFh^ p&  = !s F9Wc|VebR c.Z[5Jec KFt 0Z5pDb2>v 3mN`Y3S̜:y:.L]b$c MI4||;0yJ@dlxEoXWĶ"D}3avA(U 2wJ5SוVD:5<)[ue#(ٴFUbϋ܊6S1ʀZ5G1pf5+zjAmʧۚ .!Gz{7/݂j *5􎗿F`̆d_oN{eyZRCk79/Vf#z !Ok*Պi$RAc(Ok kg6G9o("<$@ ߌj1aoH; XFӔ5E W'Aw1!A)Q@>$(JZ{/1ium%Qd>h Œ #MmUZ .:18vNTN ]j̆]x4:|I~'zg F3к FgIkz GкNymrk4 I8  Fa7=gEJ;(2_ƶֽp_1*C0}H_زZZ%-ک{⍞u \vG=\7z/PK!^@z*xl/worksheets/sheet1.xmlZmoF^=`Jr ਧҩgq6.NU{g/3^Ԟ}<;wm7{Q7e=gb[.V󏧛[jl6ծ>kZvͽڶ6k;@UZ~q}]d'ʝz]EZobעd-o^}{cm&{p\n{Զˮ f|?迦ߴ-jug9ݹOrx[梁GWQ/,AgӃ3\\z, & &~ oṭċwU{[,#v,>-UU{&)6E"H:?W;t>O?u1H}{eUM{KQ0:`YECW_W ZR6[A.='iJ˶򷦭7Pw9|/dꃓ+ G7s{GmOoݨ4k`"5Le o==WT^N#0="Ӱ g@О ZP32 +HaQt"+VH?\ىd']Ɗ[ ^<q#@l5>Kkl")"?[ H"I 8m?>Mt녏T !T/C<yc),cB'd7:y"4SED4'H$ݦE_*:-H&w:2 dj99'BN;WP~IAyI׎bqqbJ4,O&@fEF҆\(31<а< 0#H~kBl I$^ݗuF`¥iD2d$C%g"pIFىH%o'}cج$ ) ! 5FJ@!T1D8Ѧd/]%D 9Q  NI00F0u{]_0!;ODDXa&:\ORJ!]m]R4Z*R37U)2B!U~|RV^ 0%`20H0}yz (&@?mHfW"PGeQc& Ƹ }VNpFNRv4ΠoƊMzKW*CuD:™hx}[YVG)K͎1,&;I{ dTt{64V0FYG} @<:L {EG'Jpu+M4fh`<|%@v$7%sVΎу^XOqbp:::,+n7H|Mi7ΆJS6dc}KM6D ^+Ѱ<_Dgx !@ uLS&^zI@_qSBFnd"`ūA} @Eކ]!_> 4C!V)DH3.5Q\JeJRki'CU9 w[$y~#@K(XP o2؞Z}qR?8cu`QrjS̹O:WrTkX2 / uoڸZuRH~Ixb6,Nq3JaH©wd@XD:jWk )ap\rt#g/ 4eaUNMuUhG26IP >&=ej"gkħ,dMrJ+PK!O#txl/tables/table1.xmlQo0'? `TlR5Mk=IFiSM3I6M{k{umhl?H]~=_$ ꗪ'=^ݷ: q68xa݇uխqpZ-Ǎ־k<0t}ݠ7O{fvTE;;xggG4FLb4T 2:0l;=B rXքRWQŸAԫ.4w7hiơUO^^Ag" |oj/vcMpAPwp4KVk~&1A APPj:眠g/l1jsa9_zO'ҜVfSHR 8YdFerjVnt7'a.#9XbF ePJR2RZT©3,t'"%Ģ I )G%$TV( g\drNPsA'$.\d oqID03VA4 ȋ$M /XEΦ<Ƅ?=Xy7OW6ubvRzդ,JԚ􄅝0M]+}>vu4UM)aۧʨ6 JU-~m |SՕ5l< 7[Zy;IRAv<&d$=,ea~xۚ=>p\|x_~͍G1-1.8McgEmX IV 3^|n}CBR1&1PGQ~4 x2 1]8=$ !4ub>!x8`$ ;rBߩ4tqr>C3{ +Mqe63E9ř&Um>ކ9tGJ5=Q=!ҫ }PK!+{32xl/pivotCache/_rels/pivotCacheDefinition1.xml.relsj0D{-;PimZUB1a̴2;% 4U ehAIp@Vuf%$ %)؉# EQGWIoz+7:9PKloPTXrrRF~YMUZv{PK!ʂa#32xl/pivotCache/_rels/pivotCacheDefinition2.xml.relsA0!MaYx$m6/Ţ~sT8 LϓZ(``[ՠ(Xv> d 'dAnj4a.!}U(A 9ǝbGQ*s1@/н0H'uy?[: ˼3YNN @Te:/W?PK!ފz 'xl/pivotCache/pivotCacheDefinition2.xmlV[6~xb0!DIVI&Rjw#lmD=ܙt- f]y\T PW秝7u(HY poT?4٥=248&=k]ς@"'ʯj*`XIN4ZRR3A&a&_Q,wUp*t"iI ufx8NCS{yk8[ :Y.+U@AGn$0sב3V,\/ӣb}[R"߉dy.Tkj) T0FII#Br>șй3VbWTPVC lmkӊwpvP} 1{_`)Rغ@΀HB}&eodrO3I#Ia{]d?z5%~6Y&5w?ģ^j0ֲS߂|ilP{mv ZEZ~TGR?tZXc_{)4ՓEf|%>T9u }Q&6cö3m&cq4 2A@{GQ,%:2&_'7b|דr ߝBӫMi$26Zm g{xR?fAݗ+³b_۲ ^R[ PK!U{C$xl/pivotCache/pivotCacheRecords1.xmlW]o0} æI{S8r~b{R%~suׯn Um3*Wz߰߿?=ijU%7C6uӺ.ΪSJT5vh%jYu7{ˬ~qSk횃mylYQ1EO"T~*eZ'Z9usVSܕsrU֝X){tv8ϲȵjԾ坣ئ8VpTl%:o،uu=%K^LߠSYލEo`n05Ԝ4(E; `)'Z0 S:1)Ksc`B"nИ gg'B/seSyr225W7:IX>8oS'ri᰷wlN .jA$"yJցHd$G {99Twu W[zOo(e^QfHر6Y!%( 9#o$5S@O8fYxf-A{\R1o(iyi?2 "c N\Oۘ4qrj̚ )/=E`D#Vrō) zgpM}RcY၁yx 2'=p7fp ,lu||SJ+66DMĚ9 %51hY/%Q~#?0k簤qD2GH oSv1@;IDׁQ@Jc;ق>X9|yR Lp 0WHVrAGrʒY[Hǫ.FWDCҾYyO T$q$ Yt2 g _SɌNSލg@ዏRPK-!*RY [Content_Types].xmlPK-!U0#L _rels/.relsPK-!+}3=0xl/_rels/workbook.xml.relsPK-!JRPP  xl/workbook.xmlPK-!%   xl/worksheets/sheet4.xmlPK-!h#hn /xl/worksheets/sheet5.xmlPK-!"G)xl/pivotTables/_rels/pivotTable3.xml.relsPK-!"G)xl/pivotTables/_rels/pivotTable2.xml.relsPK-! A G)xl/pivotTables/_rels/pivotTable1.xml.relsPK-!m#4#xl/worksheets/_rels/sheet5.xml.relsPK-!N xl/theme/theme1.xmlPK-!L"ҨJ r xl/styles.xmlPK-!K`X#xl/sharedStrings.xmlPK-!4#q%xl/worksheets/_rels/sheet4.xml.relsPK-!e p&xl/pivotTables/pivotTable1.xmlPK-!h1*xl/worksheets/sheet3.xmlPK-!0=? 0xl/pivotTables/pivotTable3.xmlPK-!Sx. -4xl/pivotTables/pivotTable2.xmlPK-!ȡ4#F8xl/worksheets/_rels/sheet2.xml.relsPK-!tEH E9xl/worksheets/sheet2.xmlPK-!^@z*<xl/worksheets/sheet1.xmlPK-!%#9Exl/worksheets/_rels/sheet1.xml.relsPK-!wҰ6FdocProps/app.xmlPK-!O#tIxl/tables/table1.xmlPK-!$Kxl/pivotCache/pivotCacheRecords2.xmlPK-!+{32Nxl/pivotCache/_rels/pivotCacheDefinition1.xml.relsPK-!ʂa#32-Oxl/pivotCache/_rels/pivotCacheDefinition2.xml.relsPK-!ފz '>Pxl/pivotCache/pivotCacheDefinition2.xmlPK-!U{C$Sxl/pivotCache/pivotCacheRecords1.xmlPK-!TTcVb '[Wxl/pivotCache/pivotCacheDefinition1.xmlPK-!o1LuZdocProps/core.xmlPK y]openxlsx/inst/extdata/load_xlsx_testing.R0000644000176200001440000001227614155600363020431 0ustar liggesusers# nolint start require('openxlsx') unzip_xlsx <- function(fl){ wd <- getwd() d <- file.path(tempdir(), paste(sample(LETTERS, 10), collapse = "")) unlink(d, recursive = TRUE, force = TRUE) dir.create(d) new_fl <- file.path(d, basename(fl)) file.copy(from = fl, to = new_fl) setwd(d) unzip(zipfile = new_fl, junkpaths = FALSE) cmd <- paste("start", d) shell(cmd) unlink(new_fl) setwd(wd) return(d) } zip_xlsx <- function(d){ wd <- getwd() setwd(d) zipfile = "a.xlsx" files <- list.files() flags = "-r1" extras = "" zip = Sys.getenv("R_ZIPCMD", "zip") args <- c(flags, shQuote(path.expand(zipfile)), shQuote(files), extras) res <- invisible(suppressWarnings(system2(zip, args, stdout = NULL))) setwd(wd) } ## Get loading files # devtools::install_github("awalker89/openxlsx_testing_files", force = TRUE) ## To install from CRAN # detach("package:openxlsx", unload=TRUE) # install.packages("openxlsx") test_file_dir <- system.file(package = "openxlsx.testing.files") ################################################################################################################ ## All Features wb <- loadWorkbook(file.path(test_file_dir, "All_Features.xlsx")) openXL(wb) ################################################################################################################ ## Budget Template wb <- loadWorkbook(file.path(test_file_dir, "Budget.xlsx")) openXL(wb) openXL(file.path(test_file_dir, "Budget.xlsx")) ################################################################################################################ ## Chart Sheet wb <- loadWorkbook(file.path(test_file_dir, "Chart_Sheet_Test.xlsx")) openXL(wb) ################################################################################################################ ## Chineses Characters wb <- loadWorkbook(file.path(test_file_dir, "Chinese_Characters.xlsx")) openXL(wb) ################################################################################################################ ## Excel Diet Template wb <- loadWorkbook(file.path(test_file_dir, "Diet.xlsx")) openXL(wb) openXL(file.path(test_file_dir, "Diet.xlsx")) ################################################################################################################ ## Empty Workbook wb <- loadWorkbook(file.path(test_file_dir, "empty.xlsx")) openXL(wb) ################################################################################################################ ## Encoding Test wb <- loadWorkbook(file.path(test_file_dir, "Encoding_Test.xlsx")) openXL(wb) openXL(file.path(test_file_dir, "Encoding_Test.xlsx")) ################################################################################################################ ## Libre Office Test File wb <- loadWorkbook(file.path(test_file_dir, "libre_test.xlsx")) openXL(wb) wb <- loadWorkbook(file.path(test_file_dir, "libre_test2.xlsx")) openXL(wb) ################################################################################################################ ## Load Example Workbook wb <- loadWorkbook(system.file("loadExample.xlsx", package = "openxlsx")) openXL(wb) openXL(system.file("loadExample.xlsx", package = "openxlsx")) ################################################################################################################ ## Loading Pivot Tables wb <- loadWorkbook(file.path(test_file_dir, "pivotTest.xlsx")) openXL(wb) wb <- loadWorkbook(file.path(test_file_dir, "pivotTest2.xlsx")) openXL(wb) wb <- loadWorkbook(file.path(test_file_dir, "pivotTest3.xlsx")) openXL(wb) ################################################################################################################ ## Excel Template (Sales call log and organizer1.xlsx) wb <- loadWorkbook(file.path(test_file_dir, "Sales call log and organizer1.xlsx")) openXL(wb) ################################################################################################################ ## Whitespace - maintain whitespace wb <- loadWorkbook(file.path(test_file_dir, "Whitespace_Test.xlsx")) openXL(wb) ################################################################################################################ ## Weight Tracket Excel Template wb <- loadWorkbook(file.path(test_file_dir, "WeightTrackerTemplate.xlsx")) openXL(wb) ################################################################################################################ ## Schedule Excel Template wb <- loadWorkbook(file = file.path(test_file_dir, "Schedule Template.xlsx")) openXL(wb) ################################################################################################################ ## package Example File wb <- loadWorkbook(file = system.file("loadExample.xlsx", package = "openxlsx")) openXL(wb) ## write jsuts a date wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, 1, as.Date('2014-01-01')) openXL(wb) wb <- loadWorkbook(file.path(test_file_dir, "pivotTest.xlsx")) writeData(wb, 1, iris[,1:3]*100, colNames = FALSE, startRow = 2) openXL(wb) openXL(file.path(test_file_dir, "pivotTest.xlsx")) # nolint end openxlsx/inst/extdata/cloneEmptyWorksheetExample.xlsx0000644000176200001440000001615314155600363023021 0ustar liggesusersPKUO _rels/.relsJ1>E{7 "^DM>}{XYdo47go`][vo <Vw8'J'QrJTfd wZ'ф@>4'|hlIoFǟ 8f3wn jt tu"/ %~Ed2Ɩ!ъr]]yrpt+!8! A<8=,۬`de谗]vW$LO/8t1/jA00 `iE7A0߁YY/q}"_NA*@kT\ 6ŧ"Y:\ܶ;$VnJ*ɦ]Y]NyPK>ZPKUOxl/_rels/workbook.xml.relsj0_E콖PJ\J!6}!-[߾"`r;bg>Ym^|b.xUQ@o|}rX{Q{y\IdO s|AS"ӄ4hcjef[uY>4sO V vcx >1 +=9DΦ: NSe00w0ك-@<\'<h )Tg_PKKlEPKUO'xl/printerSettings/printerSettings1.binWmn Sp>Gq# Iڒ5R'q(L m8F5< L`ݍ-`A2{dp9R#>7Xuꋝ/XHI| r)\VA82dFfqI]]o 5`?<͘6uA ڨZŒXՃ zLuEw9gZY6j"Y7^!YvE$!BԒEu@?>&! ޶쉉Cpk#@ie[; wZa ;⥟{(?@?Akiq9wL_PK](7PKUO'xl/printerSettings/printerSettings2.binWmn Sp>Gq# Iڒ5R'q(L m8F5< L`ݍ-`A2{dp9R#>7Xuꋝ/XHI| r)\VA82dFfqI]]o 5`?<͘6uA ڨZŒXՃ zLuEw9gZY6j"Y7^!YvE$!BԒEu@?>&! ޶쉉Cpk#@ie[; wZa ;⥟{(?@?Akiq9wL_PK](7PKUO xl/styles.xmlMo !$=z%2"`&dJ\n2@^ٚij$Dn{ʚ2л q2P}㎱ qhQF/yR-?pmi&pro3#Ӡ㼰?#SJ 1[v˸Ĉ`` 6=@;X%jM=J|7'~jڶ%r#?: sIXJY5˒,4 MO-ȺG,bO3f{|./q.RSTind#zg9$1b VH4i=)O} ZIcɤ'rOS g$~R_yPKi,PKUOxl/theme/theme1.xmlY_s8OcS) ڛ6m&z[ly$oX` `Ij^OQG4wFNuuUӠ)Mpm%d 2tYu:78$ (g*?TrJ:lj 'm U}7pId[9Ai\9B"R4{ lw}Yz5>t:NZk @ǻ{po6+ Pq~;7){"g?3D[ o? ߋd20D-`?p6L8KM "|XeEF*} crjAMJW zB2@bBc TlFMT YG΅@YLNɍ!iBB!۱S&aSy`? o#֬5 |,ͻiӻuZ&bՄN) (D0C! $[Kb̈ Pb/FlAnbܲ XsO2BrIL0`hE1Fl?ZClbY 1B.Rrc ob,5M𥸟D6Y5{g B۬ƸEʇgX'QSDy7|pKh̒'J*gXS_^`yurM1 ߫{|GUjQ#fZ+pMUeVr.Zier|H2F"_ Ֆ#T$ uJ-`Ŝ_=!XC]&j8dn=Ofda2r- nsiBinIҗB6_ޑ$#=˂j4v2Ec۲tO3[Xk*o#Yt+Rqޓzߌ^fԁ}5өEnXGP5&L]O'yu/p9e[z^.E2~#.X0TqEKΌ$\$F,4Օ5 dX^u$)֥:wȪk+E*K]YidqHTլtHƺgBZ}ya6yeꑝ;f<;PF/c0pew u3L0U\Æsi8>[$I[}]m<壽/ p͵$L'P=PKQPKUOxl/workbook.xmlPN0 wӲ]b7$;gZk*UBBb=?lq䝂|@WzCszvF7ޡ 2W·Ht ۅ\h5|.!VԆ66\#F"jrpUXh_|mūHFdkjnރ0:b=(tr/ŽoVw/Xtq!`giVQ7t[ﻴVNpڦTS}1i{L؉  J@ؘM/&z18m1PKD  PKUO#xl/worksheets/_rels/sheet1.xml.relsN0 _%!l iWh(1=A?icĄHl@ uw7w [vX`Z>`~)}HET=sWG[dLH5ic-2w*Yj;TU?Lk}KرmMto#i|uC6 oeł:nMʁ2 ̬Z˗@$E͊ۜ_:5'PKwGXdPKUO#xl/worksheets/_rels/sheet2.xml.relsN0 _%!l iWh(1=A?icĄHx=܁(l!cj!QT ^zm1!դy\ܩdݫP馹U?L/@ /aǶ 7ѽH|پ׹*ـ?o 긍Mʁ2 ̬Z˗@$E͊ۜ_:5'PK$dPKUOxl/worksheets/sheet1.xmlSn0 Avݖ N1 `eۻ,ѶK(fnЗ}>-BeԳT+M|Uf0B!ղG\9-"LS#)r"0n ]q;/Qt ֵFHB)ٞU|L/Nz.$ٻW|[Œ5O,.?^#,Ӎ'1WQ`&"!ŒPit J-yPu?t!W!nWj1lͦsg AiŦB X- ӧm|'6`|ӲKLa#u2 |3UܚN>hEljfLcuUΨM NĻ'Lk%cJgJ['_aZxqNw;yLPKPKUOxl/worksheets/sheet2.xmlQo1 ǿJrЮ@$ i|w8rh1ov;)trK;)7"}X"KۊYQdbvЋ5-?:(k6k b^\OW=ΦvM+ ULJ4 1k=5}C,ޚ P8gv䜜k]2 b8 ^i0xQH7eK\W缪n/iPK+0PKUOX<t _rels/.relsPKUO#le&[Content_Types].xmlPKUO=docProps/app.xmlPKUO>ZdocProps/core.xmlPKUOKlExl/_rels/workbook.xml.relsPKUO](7'0xl/printerSettings/printerSettings1.binPKUO](7'xl/printerSettings/printerSettings2.binPKUOi, H xl/styles.xmlPKUOQ4 xl/theme/theme1.xmlPKUOD  xl/workbook.xmlPKUOwGXd##xl/worksheets/_rels/sheet1.xml.relsPKUO$d#Sxl/worksheets/_rels/sheet2.xml.relsPKUOxl/worksheets/sheet1.xmlPKUO+0xl/worksheets/sheet2.xmlPKopenxlsx/inst/extdata/groupTest.xlsx0000644000176200001440000001140714155600363017463 0ustar liggesusersPKP _rels/.relsJ1}{wDdЛH}a70u}{ZI~7CfGFoZ+{kW#VJ$cʪl n0\QX^:`dd{m]_dhVFw^F9W-(F/3ODSUNl/w{N([qTu͹3+Y8 *ׅP}{H3YR+,Y[9dZIe:xk:[mċV.7Zʘ9^Q"vP<$TO1]O$ hlnA+,GCݷ$g0]©gzE o:r;OOaKλ*; $f!(`!0nN}TD!l9(? քv.jo@"' [X8G*Ij&)2~w4"f?Oi2Fha >ƓAMS\IOC]/[4Cjϝ\;f>%w c PKҧu|uPKP xl/styles.xmlX]O0}߯>P`JSLh iڃIBïu MV>k[\GT GG!FTf*gF$|~Hm8(S#US _ 20$7$x0 i 8MR̅5(SKiF!|y+TRM8$ҤPrcyFH%ǗyNUDxý+/{ Mjb-rZKX|/%Ԥ&'BJن !3R*Im=LN šk'bYs^ܻb3DWNmy^ H]fK۔tY)}xm^ Y'Ji nnsjY ?_,]4Ed2ooL>,ԜLuoq=|mb9PxQpSh:u>_j+` 3;]KG{&+7}rs뻿ѮU|;}Еr@: wp>qˤm•MIi*i5YsNGOXg#kk3Xr>輦l0IPKˍuPKPxl/worksheets/sheet1.xmlVQs8~_po5NtR|LZg:78IM~}W` \nFo}(wK: }x*2K͕)MxFAⓐ&jZWAJ&\Ȓh\C* $JDaJB2\yNSX.kD#᫂VOb[Nz9e'!0vV֯ZwA93 +|L3apRgOA5#MgIRhղn_i%d;ACo!]JM;`jfXdXdi ڏxsC+Ô2%ViR/f$DK Ca[ۇVC-ywe/ă1[^5(|@$?7G0,|.t  &?4ZKÿpDc6`l-@#z`4e̍YٲfI~,)J?]()yLIt١f-kIKq ;^Zg \ 3Gaצyd8&a &8bkzu5\L97p] zG#v6^1 ]p-5qF.##z zN%)ʛW`.pEOkV { 8Dbaf%(\R:%^h[VΡ:=+ >INNFX>1b=˛ewxIv&N^q֒b+!$EgHpóou$Iow(mݐuE2l-pX9Nk 5o0IbeBNꏕ݄MA;8㠏]/ PK; Ӣ/ PKPxl/_rels/workbook.xml.relsMk0 FIc8AQ6ֵ~.[ eГH4C vV͢h%؀I/Z8Y.b;]JNɺQ/IӜS[n+Psc _{0 r3Rb+/2m=?"#ʯR6w _fz o ۭP~$E|PKOz%PKPxl/sharedStrings.xml1n0 EBmde4d*cQ(dhB#a&zx5 l<]z8?@p649 (=9/{)َ T..`rE <"0צyxaJhu%w{=/wIaLquJfhUηunswb@V, 7PKj?PKPdocProps/core.xmlR]O0}W,}ߺlI jwխkml=xv( oڈJ&!@*2Oz53ɌYzsEyEW `h e)QUiOR?oe;/d*(OPY $Ҷ^fmT0zc5 PK6TzPKP[Content_Types].xmlT;O0+"(vˀJځ(32%1{)TU )ɲラOv6ۨ&YL$ #rK/l:[ >^sR`E {j,h)p*fyF! L( w'/J[Ȃ,Xe88r~8l{|-?^H@,w#^-tCb̹ \a{I8OҦao-_YkP3e) X)PopkڮTqOmޒ y ]&icp!wϝ7;SD^[ j2~wPK6YWPKPf; _rels/.relsPKPҧu|uxl/workbook.xmlPKPˍu Sxl/styles.xmlPKP; Ӣ/ xl/worksheets/sheet1.xmlPKPOz% xl/_rels/workbook.xml.relsPKPj?9 xl/sharedStrings.xmlPKPeM docProps/core.xmlPKP6Tz docProps/app.xmlPKP6YW[Content_Types].xmlPK ?openxlsx/inst/extdata/ColorTabs3.xlsx0000644000176200001440000002361614155600363017447 0ustar liggesusersPK!$k6i[Content_Types].xml (̔J0ߡVlDd.st K=6$_C2-!v`}c)mg{>,(Y(GgfTm`uF,kh$rNB##-LxY Ġ׻lcF;~E7$ vs*RF"K[N a0|o{hVMd! 2ͅss_U.ArP}ivOœH-8mCm)i8Ix_qGn;4ߒ$sԗ.re'}PK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~iy A%SS5k?V`02&w@f%6;?܋叶ȕ-5ee CUH@4OUj$Jj/k^(@̈́|]`)A2^`K!u0\T{Ҕ$dqSR X:U=9p/MŬbMs*^;PU)b)n~.9xՉƜ,OGQ dlȊ_dX2 AkuCLuvҜ<઺ÅT*9"$:%ۑ#oyCsК&>+ Iq4'u#-[ɾ\exCɾ;=-B۰sbh@g-cFU93܈l Tnq;h74fMΟA.㐇]\*-By=xu' a!Ȇ~ɀ X-Y5XI$GžX&#bݬ]y')'ܗ2A2C Cc88IBVw (7[r>ЋqCȩ4EI ҊZtp i;ܰfG(l45ܵ5',gvJӻí ۭ}9F{(L{>G+/jLL홶7SPP}PK! (xl/_rels/workbook.xml.rels (j0 }qne:A[&Q6'o?C@.$}?ЧjU%)Z(8>< ֶҝ`@CqNsD$%襤`)qm.cuy h3"29d4tΌP-= wֵ@NZ7#{pF]M5"6J:@)1x޶։ƺ|&$9:|3ca&QYo r-=0!ϤsW/}).;a3,+zUoLpQ1ղRxñ*.g^|([-tqc.1Ldl "p.cG"H#ԢamoU',YNG{j4.FJbG4Χ '] R"{ANmv@}D$"M(NJuMA-'W JhAA_ӛֺoAB چgyق~j{pΈC0;d4K:'dk%ʵ.!߷pu?(VeRbdvt։Rc;>^)4I&ad|Z5fBsQY󑕾. aq\VUA&mOdt- [#) -r6j*!nqP3ϾQ1l;AO !NIw-h]KǻvcQDGCLL"dPx/P `t.M, jТ>OW<i.)HM-qI,I)/W(U2TR(.H+lA@[b FF?*,g7@QFh"p5LA @ Er;PDNWn]`mB*Nއhliz'!Epwy)=6zߡjۑ/ "Ę&O2ET1Ď_E9b›gJ=LPK!Ν>xl/worksheets/sheet3.xmlKo0J@*4E۪j{wAmQ@R.Q%فҚ&0Ҭ ZEM͕5POa~kn[@P0m]Θ-hG3u|uk;i8eKCB1lHK+L8"oeOpͶ+dPJȟ:RX!pA)"Yo0Bs,?c,]L7JR2=[6!v{ q,ZTeMx-,i|Jxg".L(φyƤoǕ~(xP ;x Zbec0q ;'#54|‹n).)HM-qI,I)/W(U2TR(.H+lA@[b FF?*,g T)HLOM,J+VIM+U23WR(LπK J I%%0^FjbJjg_egPK!N xl/theme/theme1.xmlY͋7? sw5%l$dQV32%9R(Bo=@ $'#$lJZv G~ztҽzG ’_P=ؘ$Ӗk8(4|OHe n ,K۟~rmDlI9*f8&H#ޘ+R#^bP{}2!# J{O1B (W%òBR!a1;{(~h%/V&DYCn2L`|Xsj Z{_\Zҧh4:na PաWU_]נT E A)>\Çfgנ_[K^PkPDIr.jwd A)Q RSLX"7Z2>R$I O(9%o&`T) JU>#02]`XRxbL+7 /={=_*Kn%SSՏ__7'Ŀ˗:/}}O!c&a?0BĒ@v^[ uXsXa3W"`J+U`ek)r+emgoqx(ߤDJ]8TzM5)0IYgz|]p+~o`_=|j QkekZAj|&O3!ŻBw}ь0Q'j"5,ܔ#-q&?'2ڏ ZCeLTx3&cu+ЭNxNg x)\CJZ=ޭ~TwY(aLfQuQ_B^g^ٙXtXPꗡZFq 0mxEAAfc ΙFz3Pb/3 tSٺqyjuiE-#t00,;͖Yƺ2Obr3kE"'&&S;nj*#4kx#[SvInwaD:\N1{-_- 4m+W>Z@+qt;x2#iQNSp$½:7XX/+r1w`h׼9#:Pvd5O+Oٚ.<O7sig*t; CԲ*nN-rk.yJ}0-2MYNÊQ۴3, O6muF8='?ȝZu@,Jܼfwj9 ?hAb?y`{0^90[>q0 `r˒`,A2_,,_=ME xPPhfh4DaPކQ0I4IP&QL'LFܓW!m䓹 NZ{A$rR\7PK!;k xl/sharedStrings.xmldn0 ;!);TJO FjRޞLӄ?;5<;N<DWߧ h.LMX95eKWJc\.<%qIyUUiy&P)fThǙXj.#Oz8DI~&4ܨx$$i7&%PK!]docProps/app.xml (MO0 H(5HLK\&8ݣ"Z;J{ qfٌC/Nr Rzjv|?.nazBYnyI!Y0s-ĵRapbYi) p:Em<ܓz`, 48噸>Ba?E6l6>xWJ >QC/F1;d;ÉKso. r&ίU]3PK-!$k6i[Content_Types].xmlPK-!U0#L _rels/.relsPK-!T xl/workbook.xmlPK-! ( xl/_rels/workbook.xml.relsPK-!\iQ xl/worksheets/sheet1.xmlPK-!強0xl/worksheets/sheet2.xmlPK-!Ν>xl/worksheets/sheet3.xmlPK-!N cxl/theme/theme1.xmlPK-!4r= xl/styles.xmlPK-!;k xl/sharedStrings.xmlPK-!a43docProps/core.xmlPK-!]G"docProps/app.xmlPK l$openxlsx/inst/extdata/cloneWorksheetExample.xlsx0000644000176200001440000001625114155600363022001 0ustar liggesusersPKTTO _rels/.relsJ1>E{7 "^DM>}{XYdo47go`][vo <Vw8'J'QrJTfd wZ'ф@>4'|hlIoFǟ 8f3wn jt tu"/ %~Ed2ƖYm^|b.xUQ@o|}rX{Q{y\IdO s|AS"ӄ4hcjef[uY>4sO V vcx >1 +=9DΦ: NSe00w0ك-@<\'<h )Tg_PKKlEPKTTO'xl/printerSettings/printerSettings1.binWmn Sp>Gq# Iڒ5R'q(L m8F5< L`ݍ-`A2{dp9R#>7Xuꋝ/XHI| r)\VA82dFfqI]]o 5`?<͘6uA ڨZŒXՃ zLuEw9gZY6j"Y7^!YvE$!BԒEu@?>&! ޶쉉Cpk#@ie[; wZa ;⥟{(?@?Akiq9wL_PK](7PKTTO'xl/printerSettings/printerSettings2.binWmn Sp>Gq# Iڒ5R'q(L m8F5< L`ݍ-`A2{dp9R#>7Xuꋝ/XHI| r)\VA82dFfqI]]o 5`?<͘6uA ڨZŒXՃ zLuEw9gZY6j"Y7^!YvE$!BԒEu@?>&! ޶쉉Cpk#@ie[; wZa ;⥟{(?@?Akiq9wL_PK](7PKTTO xl/styles.xmlMo !$=z%2"`&dJ\n2@^ٚij$Dn{ʚ2л q2P}㎱ qhQF/yR-?pmi&pro3#Ӡ㼰?#SJ 1[v˸Ĉ`` 6=@;X%jM=J|7'~jڶ%r#?: sIXJY5˒,4 MO-ȺG,bO3f{|./q.RSTind#zg9$1b VH4i=)O} ZIcɤ'rOS g$~R_yPKi,PKTTOxl/theme/theme1.xmlY_s8OcS) ڛ6m&z[ly$oX` `Ij^OQG4wFNuuUӠ)Mpm%d 2tYu:78$ (g*?TrJ:lj 'm U}7pId[9Ai\9B"R4{ lw}Yz5>t:NZk @ǻ{po6+ Pq~;7){"g?3D[ o? ߋd20D-`?p6L8KM "|XeEF*} crjAMJW zB2@bBc TlFMT YG΅@YLNɍ!iBB!۱S&aSy`? o#֬5 |,ͻiӻuZ&bՄN) (D0C! $[Kb̈ Pb/FlAnbܲ XsO2BrIL0`hE1Fl?ZClbY 1B.Rrc ob,5M𥸟D6Y5{g B۬ƸEʇgX'QSDy7|pKh̒'J*gXS_^`yurM1 ߫{|GUjQ#fZ+pMUeVr.Zier|H2F"_ Ֆ#T$ uJ-`Ŝ_=!XC]&j8dn=Ofda2r- nsiBinIҗB6_ޑ$#=˂j4v2Ec۲tO3[Xk*o#Yt+Rqޓzߌ^fԁ}5өEnXGP5&L]O'yu/p9e[z^.E2~#.X0TqEKΌ$\$F,4Օ5 dX^u$)֥:wȪk+E*K]YidqHTլtHƺgBZ}ya6yeꑝ;f<;PF/c0pew u3L0U\Æsi8>[$I[}]m<壽/ p͵$L'P=PKQPKTTOxl/workbook.xmlPN0 wӲ]b7$;gZk*UBBb=?lq䝂|@WzCszvF7ޡ 2W·Ht ۅ\h5|.!VԆ66\#F"jrpUXh_|mūHFdkjnރ0:b=(tr/ŽoVw/Xtq!`giVQ7t[ﻴVNpڦTS}1i{L؉  J@ؘM/&z18m1PKD  PKTTO#xl/worksheets/_rels/sheet1.xml.relsN0 _%!l iWh(1=A?icĄHl@ uw7w [vX`Z>`~)}HET=sWG[dLH5ic-2w*Yj;TU?Lk}KرmMto#i|uC6 oeł:nMʁ2 ̬Z˗@$E͊ۜ_:5'PKwGXdPKTTO#xl/worksheets/_rels/sheet2.xml.relsN0 _%!l iWh(1=A?icĄHx=܁(l!cj!QT ^zm1!դy\ܩdݫP馹U?L/@ /aǶ 7ѽH|پ׹*ـ?o 긍Mʁ2 ̬Z˗@$E͊ۜ_:5'PK$dPKTTOxl/worksheets/sheet1.xmlSn0 AFqnK`(t òȴ-J|h;2|H?Ϯh2ͥo)_O7IJw衐/CH$>M), xHN'Ԩt5NˉaIp`][+4{>M$N>63su_EIz.$+_ 0bfI)jߜ\#i nk;^FR8\7Io;,L ڼC'3",/w Iŧ?X-n몐l#=c7끍IvՀU Sq?HTP}~bl&.gN {AY!x3>Yݦ >j>IAS1N^-Z ЀwiDhcH;)VnXd --oGXPKooPKTTOxl/worksheets/sheet2.xmlSn0 AFqnK`(t òȴ-J|hiS|Dwh2ͥo)?W_IJw衐O]H$>M), x>N Q1jLr?+Ò.Vh|H:X}lm'"tϝI\M'/|٧w|ˆutwP7}:s0iWLlz5Jrx$=X6g*Nf*E^Y6wAP>wǫ,|0[U!y|nc .3-V?L$3T3RHTP}~cl&YC˜4L!=C5 /w6%OMQtPs|E *q0FRl1%t'Բ@FL'& }A= ),O8 HMLGnl!oW&kwhi1ZWףi/PK+KPKTTOX<t _rels/.relsPKTTO#le&[Content_Types].xmlPKTTO=docProps/app.xmlPKTTO'docProps/core.xmlPKTTOKlExl/_rels/workbook.xml.relsPKTTO](7'/xl/printerSettings/printerSettings1.binPKTTO](7'xl/printerSettings/printerSettings2.binPKTTOi, G xl/styles.xmlPKTTOQ3 xl/theme/theme1.xmlPKTTOD  xl/workbook.xmlPKTTOwGXd#"xl/worksheets/_rels/sheet1.xml.relsPKTTO$d#Rxl/worksheets/_rels/sheet2.xml.relsPKTTOooxl/worksheets/sheet1.xmlPKTTO+Kxl/worksheets/sheet2.xmlPKopenxlsx/inst/extdata/conditional_formatting_testing.R0000644000176200001440000000644014155600363023165 0ustar liggesusers# nolint start wb <- createWorkbook() addWorksheet(wb, "cellIs") addWorksheet(wb, "Moving Row") addWorksheet(wb, "Moving Col") addWorksheet(wb, "Dependent on 1") addWorksheet(wb, "Duplicates") addWorksheet(wb, "containsText") addWorksheet(wb, "colourScale", zoom = 30) addWorksheet(wb, "databar") negStyle <- createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") posStyle <- createStyle(fontColour = "#006100", bgFill = "#C6EFCE") ## rule applies to all each cell in range writeData(wb, "cellIs", -5:5) writeData(wb, "cellIs", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "cellIs", cols=1, rows=2:12, rule="!=0", style = negStyle) conditionalFormatting(wb, "cellIs", cols=1, rows=2:12, rule="==0", style = posStyle) ## highlight row dependent on first cell in row writeData(wb, "Moving Row", -5:5) writeData(wb, "Moving Row", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Moving Row", cols=1:2, rows=2:12, rule="$A1<0", style = negStyle) conditionalFormatting(wb, "Moving Row", cols=1:2, rows=2:12, rule="$A1>0", style = posStyle) ## highlight column dependent on first cell in column writeData(wb, "Moving Col", -5:5) writeData(wb, "Moving Col", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Moving Col", cols=1:2, rows=2:12, rule="A$1<0", style = negStyle) conditionalFormatting(wb, "Moving Col", cols=1:2, rows=2:12, rule="A$1>0", style = posStyle) ## highlight entire range cols X rows dependent only on cell A1 writeData(wb, "Dependent on 1", -5:5) writeData(wb, "Dependent on 1", LETTERS[1:11], startCol=2) conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=2:12, rule="$A$1<0", style = negStyle) conditionalFormatting(wb, "Dependent on 1", cols=1:2, rows=2:12, rule="$A$1>0", style = posStyle) ## highlight duplicates using default style writeData(wb, "Duplicates", sample(LETTERS[1:15], size = 10, replace = TRUE)) conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates") ## cells containing text fn <- function(x) paste(sample(LETTERS, 10), collapse = "-") writeData(wb, "containsText", sapply(1:10, fn)) conditionalFormatting(wb, "containsText", cols = 1, rows = 1:10, type = "contains", rule = "A") ## colourscale colours cells based on cell value df <- read.xlsx(system.file("extdata","readTest.xlsx", package = "openxlsx"), sheet = 4) writeData(wb, "colourScale", df, colNames=FALSE) ## write data.frame ## rule is a vector or colours of length 2 or 3 (any hex colour or any of colours()) conditionalFormatting(wb, "colourScale", cols=1:ncol(df), rows=1:nrow(df), style = c("black", "red", "white"), rule = c(0, 100, 255), #If rule is NULL, min and max are used. Rule must be the same length as style or NULL. type = "colourScale") setColWidths(wb, "colourScale", cols = 1:ncol(df), widths = 1.07) setRowHeights(wb, "colourScale", rows = 1:nrow(df), heights = 7.5) ## Databars writeData(wb, "databar", -5:5) conditionalFormatting(wb, "databar", cols = 1, rows = 1:12, type = "databar") ## Default colours fl <- tempfile(fileext = ".xlsx") saveWorkbook(wb, file = fl, overwrite = TRUE) openXL(wb) wb <- loadWorkbook(fl) conditionalFormatting(wb, "Duplicates", cols = 1, rows = 1:10, type = "duplicates", style = createStyle(textDecoration = 'BOLD')) openXL(wb) # nolint end openxlsx/inst/extdata/namedRegions2.xlsx0000644000176200001440000002404514155600363020166 0ustar liggesusersPK!n[Content_Types].xml (Ĕn0EUb`QUE\{ ~c(}'JAnb%xxmMڻ+I}\ޱpJf@6]_ XPÚ5){Q6`V>V$zs\9Aw˥w \*SFGIӚ>oI"d6h)S\ʝCE90thw6RiVPLDL/_EuXfZriBaZYݞFAe9đKe^gc΍R8OCt'Npzf & ui ]vhu\blL9CuPK!^e _rels/.rels (MK1!̽;*"l/EMd1`7FAtzwyfx{vE fӻVKrFH"l3*>⢄.%uGVł=\i8XrZJ%\P4H;s>67Mizoɥ#+DΐYB5V$~"c'ZkRRF%8EsܙF|02Xn/ɢ1=cW7޲PK!JaGxl/_rels/workbook.xml.rels (j0 ѽqP:{)0Mlc?y6У$41f9#u)(ڛε ^OW 3H./6kNOd@"8R`ÃT[4e>KAsc+EY5iQw~ om4]~ ɉ -i^Yy\YD>qW$KS3b2k T>:3[/%s* }+4?rV PK!"d2 xl/workbook.xmlmo8ߟt߁VWL$ar6V*U.8*qT~azw/jc߿piU3eLxg:2;Ȩ$)S Ч?N\<=pd@YP&<*hA>%̸(x$2Je[mVAX KhēEAK4'ͫFHt "s3$XK-" Ə%!W3V|6\':`GcxoVcZ>35[*"İjBռ-ud> 9d2IjC/ ,ZŎpΗH,r9@n䡡wO璊H:䥄81Wk3n|. W$ %y ;vGO#I8Y?: <"hBR <$T5:Vlߗ~m͎7j8jG<~pf~ɪv`k+\Lppi+vehnX%˺憥2!e({qU GQ`ӉL/Fwھ鶢aGqMcԛ&`wZmPIBaVox$՛Xyvdcm N=MwXISAgj%$yT6 ?}{jh bzXOdPޥ@o3~Z_m{ ʺQgnYg"'i|=|4â6'sBªlRYkmC\(6_S|%M :#};PD?.e}{xqGNٚ9FkcHT\\ht%':18kv5nC7rbxD}G_gpAѤ3"T >ـT\BP[UPK!; xl/styles.xmlTo0~;5А%P5M*mӤv^0U@Mw$uڦszJXU#J]qק"adUZ 7wu9fx\ Ė[& SpRk#F+a8%r{,DRkRˆ:悻C, ] FZ6#yiյ\뚗%9ZH:(!a|{k^4!i;@L KxV*w@{*a A:7n _;c >qp΅8}$p)Ql~:4GAK0ݽzdZ ZxHSjD ltߵvdӊӍVTTzLG Fj'  "LHhxz7â ~.w\8@0v(Ap~ƺ✣@%*VӝpO 'VϷvDW*u-hgx/?WE,\$'ULU1h0Ô0A +50d)> 6=GGs.cKa"))v٨u)Ku7a93p~_P$E7–e rm5[l ׳4 :ܦMxy%q}V@]Ї${SVC|p/ǝ~ ÄHЂA8>m9Vϑ9D, !к/qxZA|ideV`Mb Bnw$-4T<5M߁u[yuZ#Oa#fи᎗'ڞg~=9QΎ Iλr+N਴{^vPQbFNް0_0@:L?PK!N xl/theme/theme1.xmlY͋7? sw5%l$dQV32%9R(Bo=@ $'#$lJZv G~ztҽzG ’_P=ؘ$Ӗk8(4|OHe n ,K۟~rmDlI9*f8&H#ޘ+R#^bP{}2!# J{O1B (W%òBR!a1;{(~h%/V&DYCn2L`|Xsj Z{_\Zҧh4:na PաWU_]נT E A)>\Çfgנ_[K^PkPDIr.jwd A)Q RSLX"7Z2>R$I O(9%o&`T) JU>#02]`XRxbL+7 /={=_*Kn%SSՏ__7'Ŀ˗:/}}O!c&a?0BĒ@v^[ uXsXa3W"`J+U`ek)r+emgoqx(ߤDJ]8TzM5)0IYgz|]p+~o`_=|j QkekZAj|&O3!ŻBw}ь0Q'j"5,ܔ#-q&?'2ڏ ZCeLTx3&cu+ЭNxNg x)\CJZ=ޭ~TwY(aLfQuQ_B^g^ٙXtXPꗡZFq 0mxEAAfc ΙFz3Pb/3 tSٺqyjuiE-#t00,;͖Yƺ2Obr3kE"'&&S;nj*#4kx#[SvInwaD:\N1{-_- 4m+W>Z@+qt;x2#iQNSp$½:7XX/+r1w`h׼9#:Pvd5O+Oٚ.<O7sig*t; CԲ*nN-rk.yJ}0-2MYNÊQ۴3, O6muF8='?ȝZu@,Jܼfwǡ`{*79mcd,+کP|ޛsЛ)!\R㫁VZR S3hN1Iv$ ;"DH- Uׂb{;A4$')9b':a_G(FM4uƔ'l3_$ʨ@&k+"ͤk7a˜h~o}dfEo;aw3Nz+oY&?^ B1)0L֏.J@+yr1&E6Owd+81rSja)p*w;F顳_MkQ]u 26"ꄠx` ](u"c.X@y,Abg/dzR:ІAG7K8ݪy\VˬA{Z){oPK!USxl/sharedStrings.xmllA1w״"2 I\ x=@tzRբ7" y4,0-Xc4Tˋ[ʍ'g, '=B,-(Q\X[IjeHܨvrho;pUҠښM')K(kCe fl]˷o[4$J-8$&"5Plۢ ozJ91t  hnU/o.sV@V[Pi| nkTq+ *\FaY@P@"@ ECY;6cldP+˟?rz&:B,{oPc9bvcxq M <e>Gһ\$4Ipbڇ0#MOB/.=X:8WX7QN/Yځ6m >PKq۸jjEegk}/@N}4PK-!n[Content_Types].xmlPK-!^e _rels/.relsPK-!JaGxl/_rels/workbook.xml.relsPK-!"d2  xl/workbook.xmlPK-!; s xl/styles.xmlPK-!s 67xl/worksheets/sheet2.xmlPK-!N xl/theme/theme1.xmlPK-!2j)R"xl/worksheets/sheet1.xmlPK-!USSxl/sharedStrings.xmlPK-!k3, :docProps/custom.xmlPK-!WEodocProps/core.xmlPK-!Ly2"docProps/app.xmlPK %openxlsx/inst/extdata/loadExample.xlsx0000644000176200001440000132174514155600363017734 0ustar liggesusersPK!KT)[Content_Types].xml (X0W?DV춪 mK?8qql8 6KbԽd=mJX8Ɇi831&QS$-nh`v鯄XV@Im4H|P͒hVt v8LG:}}.k&s.dZWAS:$J2?ł3{,uf"ӆ#yp`6%5,O@yY/m ۍ.Z؂k )4</I\UvIpf%r LwܑxhVnJl-V.y҄ђ9 S&ף!zVg^,_F'+7JcV+FG`]qzl:&PK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~A͸}EiHc-ޝPK!xl/workbook.xmlUn0?Y%ˋ Kl'"0^ 4E[)R%%(Q=E Ζ*ͤBDfLbp R=S.; )ܘryBTR0ӥ89^ju3jz\.I6Qc뜕VX7KdQĂqf-(r 2Txaz]0KsP^_~M9..\TppU bS+ڔ aB^r,LyP%c) "Jbǹ8) uIdzMlx0prO^YoK%Qbv9Tx Cj=+I2zѝ>WLb71YL 3eĨua[zP I pak>UL.Zu7Ui &jY ׳kա^1D;3'h -.Ya/]I #tCzA0'3Te ' W %;2TY.mRyK6FcѕFMSal2vnxO~ES:"/:<{?hہωf28AMFOҎv;Þ'W(v8$TT}a1PK!X xl/worksheets/sheet4.xmlVr0}L=2ƙĞyLYa4Hr[;W2 {O%J3t"7[Q`;W3kˉNBa/@iqt zep6FJ,4LYb闋Qࣘt$8 P:F]^:n叼}W#鈾qQ%46hDBCu'[ċtg-1\.F0n[ i8Ii>;, (W`PKeWSA!lWâѺjVi2KƐl4z͘ن&;:0z͘ن&;Cz͘ن>[F+[ &Cd%Ϊv W*8h`AeJ:>w??p*Sة's;%TN? )9N|`{89|N~No˿PK!ʱ7#xl/worksheets/_rels/sheet1.xml.relsN0 HC;q;i.iWT nQ{{:1*Ƹ'۫Ŗ"*(eq}=Nʵj*úX=ѨR.qCJaz X@.g:J{ JpQz@=iA4;hz͒KGZ`R/#eC{JH9)<=̰9hz5}p<'M0[_9Ȍ$XwIjoq~'<X&8PK!;/ )0xl/worksheets/sheet2.xmlے:jށj"`nv  \N9`]6~, Z$ZIKv< NOCsd i[7ppi.?Ti o_(D8]/Ms~/ۗ_Fչ8_U}~_uFØ;>i"<}bT}-X.yy)ן_o !>ۇT<7η"v ,uuٍg"Mw%<M.O÷acMEy>b;xOL$6au=м. <}iqB>!̈84Ҷ:cygA륩hhg7䱮9!gw[JiDw\)3\aFD^~O|yb7do!"}y=_˄LbM˰8^؝x/cdn7H+mze ;I(/L3-'7V7LVgx6 BOKJ/kmXX2su( rI!e+mmYngޑeAג}7 s ~tƽxo幮A78-خE<Ӽ+zIHK@f7H+i;qr-۲@vY`ĺct,\Q̂RB(fl`丁eЉLB_ǖa` 7`p"A4ŽO_4u%=`iw'cVJ{݋gw,j30 큲~A\Mpo[m[r; Ca)3\Q ]Lbx8&JWЖ{.C!G Г$1`,80a a~F^A{f7aD] x 9 \ W&c.Pk(fA )!WZtn wiG`,bڵQE@@NL*H!%y1 ?ZcB,AJU:OG"jȪ@rmEڛVbN駡R2A)@ k˙N:1׉ND:ԉX'Hu"ӉNubÄx Vwdş\!&D1 DY"&FL1bVY#f.ޓVe{F+6æ\!&D1 DY"&FL1bVY#f޷4]ڕ)]u&q$3犌@L%bb$I!f5b6\eb&[Cu$ծLQ3!$9Wdb",#&AL 1+ĬaV-t[F+ST2E΄V"N/u;2ۖ KIb6LFSnt_ڞvS6!vwsJʮ<!XZ`)K1,XʰZH}4H~.9g_oψ3vtK($!\b[.d˗aiƂn7}Ɇ+!Zsm v`uTĜqrɑdIi%[}HH2R4R&B/'^&nUaVZ<㍄|?3O-!e %uΥNYC[Z7 s/EC5/(Y8V%GR+P~gXgû)L Ԕd1,&uŤN8%m,NI)_%[&LiƥN}x#Y:H~0 ID=P'Ѭ]'L$IDsIu%dJc.\ T4ToUu%h&*;U$bUfT;3P']'@T9<%ai)$պ׊MCe'I">ԃ^?O?cۖx[Y~oiRC{?^]?vKm=Bn[Y/Z*|/58S&]oh??go*/^^6 i~'hpgmU~ST?%_v8txNm06.v ],F.6.&.t+. 0vG<'OlS~t`;jy3cU)ۑ [pj_ TU ??Ph;huH;I3f$X%il$+Ud#T| ?fZ֒N%H(j7VwjscYp*֓?jca bu#:$\a8NchZ$ Y_X ҉C#Lw$CJ (5V5 3!Fp(vtR V{c$apt_iLL $)6쵮9cvC"8$6;_MMkf>)0h, ҏ#Z1+g!n҉!n$V q!A{ #ؙ"1 h4VXF:4jz]ۍxC_k6~89 C 1tj20.IFכb?"m nM_Qh0fR%)5R%Wi.C\OPjcƲP. rosv fMFDe)ǒZ""#Dچ}vAWլ[P~Ba7 Yb#Ɉl (ܫ/χR6k&C" YFȾgt7ACmz84;r71s l$rٸ-U!C"%M,N;DMЩRKg"6:MnRbd` '#9"/gMAWZA*HKLDb?ϕyc#PN\\̪,L_ wO˧\k<;EEH`]Xb/m!_1 ߼UMy4ꦐ<#C!S$UA*WΥ64 P$+& e /uXYdA(K?Jpe V(+'dQF4m[Qc2$/l+@"6)#CU.qt8I)W?|!^"⨛\"H#yh)ӆOF4m-eƃeo^(ux\X"flԲB"R.1!:htu+-F5 y!E2T(x[-/؂ =`vSr HJn@Yښ`jjglO#>Kk†Z1FN+݃SٛkTOj ,[ӣRQq}QOFOy~ t:PK!-1%xl/charts/style2.xmlZmo8+@h)j*u[tV&qRo-7< 3܆jN#K8TZCW$X]$4BX_"EӐs'~YP> "RaXku!䤘#a0KO0ȣQ`Rtw /T};b+ᅗuoE1e&$qLB]W0\HXI'f/fmW`T{A8JgR1r᳠|;S e2cXy.a)*(]>nu>@VJYOnMHzuHeÝNm_k KH5tm/[ ўd(GxSd,  P$@P*I,qr[ɥ.Pox|w ḾMfOxu6i/8_V6j9kkDR59(K(nr\\%x>VGy;%mk"a,!$PuܜRИR,7? , @hH2NRq'x47o,M:xNHǎU]HŒ߁9@lM2!<:OK9-uL'0|SlZ\>TG^RDRNնQg6Mi490ϱm%Tצ`ecɜ9aKj7OU,wo:}PTcK4f@-ԣy7[J>@qo@]gTZeIWJMo>uzs^玥l"[PK! qM xl/charts/chart2.xmlZYo~/pt`n!r!ꢨ  %"y$[^hA;yݲ UI1z3d|ryRđ6DKAG|*fWDd4&BQ|eL9l4tvE 벤c3 bTF-6/xln8l@c0^^f,- *BQN $X#0Cf'!|7D}_.| gI zQ2Y n83+G.vգ8K:kA( )ԱcKa}v~chIg͊SPlYns ')ɮl*7S]aU%B.TFmSK$Ei}¤=vdiU#,Ya~+N)da%3N(VWE'а,_n).+2ԒqViʕJ 8Ү%CCg3Sml'NɆV(CϪJجKtMXv2'A{OA**孂K3Vx pJTjCB`9\e u7 Hs?xC*\nPܰW]*a{!蛪ZFܤp&8:OMi"iqs4F$sYTs͍3Z+l|QOh5?~s25<tUP]YuaMh g 8gǢxq-(6*J*szE'q`뵡&{XofIYfҫ=%[OzI=_5 PZ1Y dǶBXVyڶ3&]ݚfV|XbV:z#UR&S.Uﵞ2v=S4G*z6ɇY~dqt ~1MgbMgMJ"]|8zy$@Q'?r#=s*j;ȯ$8)1?#C7)5s"}`[ isRʟJ=*@V^\'In{ tAU 3vu>:#"r OEY21w햤\g0C?8}\R~5Q T,>C:"eb"\ARn@d(>춺qrPgQ-qD(`SX{.3 ]׳gfoʻ|3v%W*RҌZ/XD\f'LisJ9' կ$8?3.!9*RL~tzGʊD]E! "4R3ͅ-8fKqHGr:{;ˆ6݉]j]'#!l#,!B51n_Ckh?lNDfUBԸJ%~ ;/wABpM^lױ.A? J+F15="6h0=[|x)CC )jNR˳7ö_;wbd?gR_Tp*u܇Bg*}:௦k[hՒÌ6"8Kve;K-ݏ刀_bb2,o2ZGLz9RbLlnc0N8^WƟS<}~C!G/db\P8w<HwJ̙7{oG_vܶ1&ola?; ^WPɸjiUv8jdk'IﯿQ=.k73od h2ԛmCe„g-4̌!mXqBjq刮pPK!EҨxl/drawings/drawing4.xmlSN0#4iPDM*R8ڴc;-$K׫^d :pt%Ԯo%%ƂՊ /V}8erw,hcm'a `&Uk;.ZItȡ2 v=Th $EЙ=G޶5TZm?/u]fbvT(>1n}"P>NΖll1{uMGBAH` QkmHDU[JC$Iaa &}Arׁ %lH1FPl5X (. pór*9l]~Hi9n>k+轪qyOIk^ ,Hm=i{tOHp]\Fm8|C)]R{v,K'׏&alU?EG~svȋe.dӬ]g"/ʅJQx1no@)N\?b4 G~Kv9H1[lcW3|H nm\oi2ʕ6~wt̶;|n8ĝ3|Up)g&Sƙy>΃\>_P|6k< |v~ 3 El- -p|6LLAQ^=iNh%xl^əg,_ܱJnoӁ^ĵI:'t:C/׹ "`DX3VomP&Y2o}2BYȴˌa'5f^qϝԸpנɀdr䁷6u>j Iɣ{Vg͐s, ׆ `vUb+5*U8f Zk2" j ʋQυsĈ b5 LGL :T(=j&ȩԤyC?iӉx&-q[ⱄE.J3ոp)^6BjJ9u(K\M& 7МH?iҋ >S+{O$Gj[l̊? ϿHi"ej;Aȣ&-B O:foő kxV/V?V, @"`ǯP'"4Eߊ'AZnb^  )͝bx '52u|+iezc+q`)9=sWV\ ͙ W2g0R2ӷbKVZF(ǂ ^+#S+ͭo*OaM}"1=MP\՞ѳ.Ͼt2DHHV$+RdN"u%Lv3LA2bUVA|Uk xG_P^@ɂ' ZA}9 1S`ũ OX>ٛdZ_P}V .QOA߷Ttp+i3ó"%=>4v_tD/@_Ap C*> +xkZ[qn~h<Fl :tY0ScUTx8zÊ@{ca~phE '{O73J}LnZ~Arp;\Nj%d,#5ϫx+=>19_p|!/2\^yQIVN+`39G׫leE ם:.G֓o)+yJgțާVگi7ײ?~lFw ]* ܟ':q]{k&uin5jͩԇ&Ƨ{PK!9/c#xl/worksheets/_rels/sheet2.xml.relsԔN1&æw[a&(M6;mEQ1!Qn/ӿm00wbE_$!j4qXAP `DU*:HR8juJu^T)![d&R$ӕ?k9]>ob%j,,!P.lm=פCn[l?8$wUolH/2TYt%OMO8!ZD7/8og@ezc{ȶ{a;5rf=O}]od5PK!Iu#xl/worksheets/_rels/sheet3.xml.relsj1B!̽u RĬ"x}i2Cw}ZKO|1e''7(6wN+(+ l#$E}q(DȾLڐ&ʥLF1U͌@ڻ١mo' >rR8֑[u|O|xfr ޓPK! #xl/worksheets/_rels/sheet4.xml.relsJ0n[DD6݋{c2mm2qu\ZXm~o{Չ t·[rJ Gs lf3:$O*%(vDġv*5#&42M7 S|t-qh? teIyb@&kp2u$7B%nW-|PK!z!xl/charts/_rels/chart2.xml.relsj0 Bhod(sCpk{}(929.o_g)ѱ$x ^])hkZP-.O/`s$+ LJy,5$K*%؅JÉbL:Y'Imo ;:iATwlf4irvvݞ킹YVOybn;Z?wZZX7PK!&ꇻ%#xl/drawings/_rels/drawing4.xml.rels 0n "MzWXIFѷ7EA4Nь)8XØR+vY@.; ,JdS ٖ8?} @.%q=h\\ zفࠣq*8m J`h\a\Pm2TD`5S1qh)fOb"Iq h@.5͎4F9'|n@ʖ)ѳqt>ͣyÓ_PK!܁xl/charts/_rels/chart1.xml.relsJ0nAD6݃ U dLG3}?s<}>%P`fOcȳ(..RF + FW[HPD5J KYk &'2'Wȳ._݌3`1y겖fayJ)j̞⸾P$~kFs]:`~He| % RN?:JPK !iaaxl/media/image2.jpegJFIFC     C  1" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? grg~dRxu`{AFq$z{Xf>_OԞWfmPL>yqMQ0h tpZ>eeǭGche_qެƀ0֧ӕɋ+|9/< ~4ee0pM&1{Uo9A ovVH2e_`Uc$*68]$f Ybߛ'R]JM1UWlqN޾F$RGzR1qO|ۏ9֐ƀs;R#yn3CƆQ `K3OzFq拀x8O%sS|ۊ~om܍~g"|RܦY['&<_7ʹ?{c(kqX"6J.@zuSX>#>;7o4rȮkt'U :{w0]_{7>֕=)_ȧ׊lUQ .5=;qssh$*/?L9mO v Md S\f0K]C܌.7~T3[hue*6[?٩NSk;m}*H+xNmu9Sp<7H dzSXN\+Q#hzaU_=fBw9&r9Q0#^DE?/G4̀ʻAUr^ޣm2P>@Ff)!i9].7 ¹> _-1F81qsQw|¦O , Jܨd$\ڔ3 #InVo [z|QtЄCݕ[@g 5pF޵j%R79p œ*= nS2GOӌK6E\[tH:mAG;y9he*o!0H!=b6 Lѡn GidV$FZ,w'jcBsyP9oFPG_JiݸUnzK`|)^vdmf5`Sؔg#fS8F{3{sө>FoXb/Z 2JG=+J8^9:DT6c;4KcG/(ڎ 0C);To Qw3^hREVQgwjGFA()0DP{j9Ur&V Uܓz~$1>n ~c5.<ANB}{vB͸/w\2_Q l"EW'rzp9i )m׭t *% W\r[lUil˴;|j,+A%/ZўϒͻTF'׊#*Ծ#*\t% ڵjj{:nϽRg4ln uQIKp7tE,xiX񍼎G6}_KF݀XVeq, f`,UhO; %0Ǧ志Zr%J#{W=I+Hc/>]܏z>Xl갍ߥ>^b׻2Qyy]ȜjWr\] #(FYe~BNl8n{Yp[!? w~7Dg?U$e\vNҢM/̒,!qQ/4RH/443v5yU"F?Jm@ܫ+QG]Fܵ=b9]OUbrqG(/1G]o>peA.O˞7vNST{{qpdaT? ϜPY#6~oy] 9dO3<ϥh\02mϴ9ɥfs(OwʒIIH'U0_j`ʞՇ63D>U`t4ͪn̴G:׽H!vꧮ̖"Vc-׎bPy5>1d/#iN= 7Aڤ /U 5Qs/SEoӜ5n[*w6֣I X ԕw+y/8:FTH[ 3QAjVmsSr)6ڹd}N;Ty+Bh4 Vߕ:yc6z3*|LflCSs^n_aeb˕ 9C3OQڭ;ɌڠzGCSZ$P|ϸ_07=V˹8= o(+0*Oңs۳|nz  gQErU)*rrs՛ ڧ'_bY7#J /m9 $d?J"ϷSMxXv_*=7+LT݃mQڜ!N~RD }~Շ,8n:Oj(<3֔^Oش+$i%+~^3 W۷sNQB r iq{mR8\<"Tے nZldt]5s9HC,f3> #*X/N:BǥQ3`0OF֤ "^2_OCQodSD#5~nMDm hnrˌB@_IÖ46=߁MHfcOH 3S(ߞk\c~dFxtu',j"UW֝O󚿄W yҤPHۃ$sxTX7͓mȆcl?hQ?">gS1v{~TsKf\bP;V2xe8JdLAϣSFf{|zG;g*0~ToXq:d 1soOdK*lˑyGx( [>皒v6@ޣRyyG# ;=iaŸ9S; BzP\$$rX0Ǯ)6]j7 >f.~nFXUgDKd'FzYoޯڦX>b4Sӊ} y8#Lˎ}{T E(ƃ#U564T kʨڝ4;MRʾi9m_CN1'Hsơimz=&(Q9i ?6*{\F1&6?"y W;6YʿE> A,*NJ P˟A$!٘yl~\tܹa1Lq暑rmc柺35{EDe?9P8n#@3⧎ Ϙ7]zS5Ԁ܌1ۡK L-*͹?zbmjt;U<?X5XDV6ݪ>֓r4=ٓG""LS/;٩sYGqЍ.;SegߍNJ0ON(N y֢XX .Bvo֛.֫cZI5ܭ[Fữn[񤔶xQ/ҏ3=);s sJ[1>GOB1a<ziDk)'*DG?SGpNzT xIFlq 7cN[fv*eF|Ԧ6qЀFp,3MnݼUŷR8ڥJhX×R+wc$ <Ձ 87pJE<ѵڧxdFQL gzQYh[sisKAzT{ w^* ?)bZRvr C/=ffKsPn-S=+FHY݄gL6x;?/Z;X[?Υsʻ_esu B)F̮glcҚm wVkUm$zecӟRndW1@nM `?FE1pH1ѳ(Gd\)TԷu'*UrMh$qq*J$ݵi<޵dpSjdUW*R3S*N5XhH`H֜"%9k#AқP@]\6ZSwݡ||i{cګep|hn]Y(Ijg%ztSޑDPwyAGgovlDלzFe*Zaw#$n国W+r7.@f$aN7œޟ4YU2B"[pfoOzeĢ<''֦,nArW_\lX2sp2j_%Ro\ z9c.Y7`w;RC Ŏ,q1 'ۗ)DS&Th`HԖ-m"V IJ~&|l.M_'_1FI -`H#J:_.T[}LUo׎j[-ަiPmվa9]ǿfK Ϟ%tkqٮ'cǘ KhkWK<@8hݦ'Ù$d$!,`T!*΄\z_¿+, 5R}ךV>~&ЙQzLv3IkQ‰R/p #h$ՔMu q MxN,c-]b\<1Ӕmp=(99º&dlգ>..-ǫ*&ҋݒݽ eRJ0MJ Ptw|&|lmZ Mʣ\G ]~ cZx1}^Lx'Gջ]bf_5dP8CF2gKx; |e)e6nGV?#k/ڷ+;躿tsVkN[HU?\WbͲV+\ҝX)B? 3}9|#gp|Ad&Uoҳ5_wrنf סkˉpܪ|j-ض1$0$% #Vk;Tc'Nx#_|=ęetLzpwN4ӳIJ/F?Ͳ\&K*u"%ii((9SOy RjV>Sٸ?J=3,s3N0][8U"NJە+zE X0P@=2i]v b(l0&x)*&F۸=8|Gn Ro_Ι&_8WF@v}Hۿ̥?Lpp'Jdۑ9B{~4He ˷ޞ9qM }֩oaX|F/ƈ IwQ" ?m,cB=>JjЭTEnw(E{ޟFǒ{ժ}[=4hNw/E[0>sM-ǟ|uQ{%-LmX2cj{^fGkR,sS9@uM 'k&sV#,B")BɪpZEh MJ\s{$b[`'oύÜѧ.˱c =:(T!Pj! <۵v!cqN+4|vІ;v9=FA+R&qS?!1ۮ[f* c8ﻹ>vȕw~.v\>Ua_whk80,?婯+ хoǽ2]d2"ˌz!˷ wdr:Rw2d~^7i;2Q4sb$>.XNQU>\[4H?@=꼌Ē\UG$[翿"ΤtL۸MY6GQ0u=G'n2_ei}Rh̖>{}u(%?|("'dw"4kU,{RN*nS1i_uob5!<˃dqCq_Kׄ|+}/|#.tV=n.W5ϋ529o"S?+eqI-NkI&Ǜ_-.#|/Zg~_?)V/ > bEֲ1vS׉c+]VV-;Tn1bYX5x+wjz|Yloʑ^D8<֝__Tk¯'H%|^/W0kO_~N+dxwJ]3Ǫ&ٯh sO/o72}{ĺ͞8$\:FwT.Y?a>G(A}8rSJ_M ,oZЭYYQmm?tEf?x3nfm"{?uI-iBd\珝++?xGi+֞ ƛ;TN?lzWx'oc>ϋڬ'tx^6vޥ-V3>xM~I}#N!ג[SOmnFKsM?#柈X(?O^`z pI48ٯD]3=U C䁞kowd|jnV6O'Ϻ+ٯaio;ſ<57RmBNbޮܚB?js5t[/tCZy ,`apa^)iF;~o9y^c*^R-5f1^?#fTcZ_Uʟ}]Z7{F~Oy\W,ʦT쿙j>v=H`p~毽VױzAayT e-7UL1ZZ^Їk=}?gdViz ՜e)UQG:n矈֣.Zkʤm*v Fzql n{ +;)+>7jZp瑏Ng%ʮ={w~:0dn:sS~Ƌ 8Y מ*㍟Z~7*An#@ KjMJ"9f?ZAp.Ӑi3ׯ5 <7 qG)}d4(a-ˡ6뚑!PITqS˦̃d9 {x#>~~#kn /E3>k|#NP\_hbsӥ{!4 W~{W3/dIV\,J>no+ռPq`e m-alͷ=2g_Y-G9YxNBbX}1A^#IbxO/Xϙ7I<07ޙ>0|Wso "E˼-L5v_\.ҭU8|dՠ͇_|y>'a]X۸"K{VTaU4?gOCFվk5φڳ2x~6~Krù~qJ7Es/yMDxj4|$ޞt~3?`?GMol!Dkϧk9e"$oVz0^YoaҵeT2hu<irI|h~?o|f. 溸MU&T"*9&)^Ӭv#ѯ(wn==4ry nF1#=<|W_Aϋ~!D=Cl]֭f&|q+ڿ:o!Y!kMc;WB?|YHwskk7Ѻ},L2b8@qW>9a2W1P[vHJVR*Joc|5Ƹ9RԒWd[f;[ޟ{GuYyyf_ֹO?-ρ>ԼsG$}?}jd? xN?ltȒ96wv-<޿8n;jpPI:kV;[8kV~Ңp\cP__ > ֵb==Y[8o<VXgtD*=G KZ^^3GK7aP6~y7ʹv7m-Iens>cF30}k þ/=i?ݴn=r+YkoWP#55/V G>y9ԾM$WVJ;x;,-kY6W}MX&1߇?|1ῇ2xn4ir/o@Z3FfI >G.6n,xZ@1NݿJidqZ2yc`ѹﶬ[FN]ƴnI57CB(n׈N[ʪz.{8Lu CWIZ,gV[ 0'ӎi? Q'{΃&1֡h9NZ HA皌۳傖Dv$Ya~Įvۻm-~#,S߽l83&hRzmN{85aQcoLa JCfHԹFw2yǥ'ـl"!+_K ޡ{GU;T|F_llw$Ӗ ӑZmM*F>R*3wUփ7î}>@g5Lؠ3qց(Ԉ Eɢfn>H;xf,(nm) {{Y#_q֧h9\ޔټ;BT8dxSfc˰ԇB<*An?*F‚@,* f(8W.?ާ,k6vXz ^^}M.cΊn y=_kn۽WKhT^W@W' 6+FWF<ҴdG N~ԭ܋rk#qy>K1aIbnj9X0PJ\t]dž4'mj![R<E#8*p|+<4|~*QvZ1MF:ٶnÌ3ehsF=]]Wkz4 D~qjQIt1Eqk&x+>.xOI'5i]0'c |YUçwIo.BF=M.%?|gGDfMZ{!K#99W/q 5J!NM)[mA{rV^'nUurRյ{-ZI-JPWv~⠷˺ZDdNfvF s_J{!vfa#o:?v_jTXp;YSR$ӲX?Kf~КWtI[-Ɲ[[8i$)com)s2l+^%J zW??< [x#^Fz-7V?0xgw5mbv8V/r{Wu3cs=VIE%k~q[etwmz+}_óu4Ǻ³/+n2̃яzfR+kW.[\ִV(m럻R('hs@Ҽ*ѭރCyt8h`<;#쎗i:ihֺncn0Pƣ _p_8ƭ*V/_-S>ޒ[QWgw)I'~O%\|گ5B'Ե-$ǐ =+֒6qSH>5 =+4c~mm?Vb:'!XޜSoU0f΁1}L|t*r|J+=b4P@a?vHJzKR+&>< i择BatZn- >Ǩ0yO'?*?*HO6޼oB;FE*ir>ޙ(Њwoo&=Oiz^ kkG##SO9g; ]GP V͵՜fI |Y#Fv+t f\UBUI٦iMZ-ٝӃqtvny-i4G2s]qP휕Oc_??l?9y[Z-w4ՕJ9뵥8O^ |oYb|?Ė~Yy5Λ(_e3IO½j\AԳwWoT|~-q¶i[H?೚o-5d0oYƆDoq9<N~*ITӴWnsIdArP:[K7ةYm~gFn6h6T'5{{Rl$MVHcwӵ;3:G/->tR(ÕQqauE T~iwv*Y]NnXI7xWk(ҝN^+d?tƯ .<)i@1F\}+ٞXجU5SN]R2i=COJ߃Rv-1O0(g\S9e-Jb1&BwpSJ__+܀3GF2F#BBn=r;HT'Ub!6&}ꕁ>dNsMTfr{ی\eoZ#$eyOb'*xW՟gbIkd/ r&~1q ܓ;s+^`ާno#kV4s6`[mᧆn.'VIP\~f<('`o! BT4PO,'c_ٳ-~^? t5K8}6@,`N~_XbaxՅ JK]c%MJI;_6_qh":$xk~]4CrC>6t5+s%3i  >?x_K̗z~066izj` vt瑚NoZݖ,9}o"Cr+F y f#np卯xʳR;OGkɛˈpJ7Tۜӗ/d;dtoxZq 6vʊ$3q^[<62XK+(&>d_q9p1 xK[y[dRdpG95A%]2Gؒ_i8G_i$I1Z}ϗlO-wZKjSZ4ۡkU few a[ 3+Wk?M%u䛵KxF^'wS}v|556c#Gl.<2䟖2>0X ŝ;Ռerrzq8EMlzg?|"٩]ºZHK-OfM_5;K7^k$?~?u cftW0~5\Е}f}k >zjcS}y">tTDZ {S^GJctRFk;|m$>]?xCw!;%SyaF4ڏѽ)[\pcߥ4[sG1RͩDoMzh4B`>ZbED6d!;~S#aeq5BUִXISVa節mq\{8ڐ9__ 3qӄ%_G>s͙)٫m1>̃ỞƦR.4= 0z^e'Z6ĺ =~ݡK6SEhJ\ږ8cӉBǥDoiꛆ^ӌ/˹#J'RG/' W"2nE?UͭaٷnT|\WFpWnyS@L-ܨԓnBH`/,ݩUr`qNKGenG^[D7Mܐ{G,hۏazU.Op6}\=ƌݾZo h=ݒ߱v?^I:RrĶ>^3i a$\hۯN!?52zLiˡJ?1oj;M5iUsA9r~;xb׭n'E+ W.TjWHsss]o KSh-云&Nlw{ןx? )e~[25q.8?8ۍ0:<=HkiU%^n1]_q\;*i֩>gͥՒMtvljWM|3%xSWuhs-ݓ"&?`߶eY۳XŒ0}ʊ ;oDkME˯C-Λյi$-諺߇u ]}5՜r#29MA'}_5<ۏeF h,Ǡܿ"@<3ߵnjE]AIF7d?8/7 COhMßGnf* ##S?us 4L˖f9f5;GӉ鯆/>*躽vG;%)<='OIdrܳEh2¤)Lᶳ}~>Z4BVՀ0GW2pķ+@TQ9SԤa5l9Jg댐*F^s'hQON0({"-Tfn)•(j͏׊mnΠ#ZsHGD[ UqZ`C戶3f'?^5ohF;K)62;0 ?E~ʚ/j yV"I/[uO2<[/aY9ѓ=bڍ}ZKG?i*ӊޯgͬC/pFG yun?i/4o 0c;O_٦4Ϩ(ƞ U$d44K~uj;CڴzzK/, 95U49a_6u4Y#|~|1 O7k:Oay -cL%4qU80+VU$մ,k\m}ic IciXap|woȣhӡ~?⻽T+jyXeŸo z[m@*Hx qsĘ'qOVLsc^jrsY%IL*Dbs[1]X0AcT-lQJ^k <=k ~>.[sH_QlĻv9;~'>DJbOcx|44SnR/NhM'$ %#+t~jU$(ҋj4֍7`n?қ)5mvSZ&?5}o1XOSOw|OsVL-Wߝ.|q;֜a23zT_g,,{_r(blnޛ+32##vZ>|I kM˟;SǭxyWSPob_le؈Ѧr~G3}hֳ Yu~~{On9˝{m!_Qs S@dǖ]U]ǐBP3!OTtttpп,O^TQQ~[Pëԕ~KwZuwgxDFȇȻ]GF_]|4~#>iY$vv~GDQo.hZ: cb$L졔lʪe:IZk0!b gV](]_ANNk_&~Y|EDHJts6=pwrj`N6~/+FJ;jh/u[<֊}c3s+21pugd_-Mr;U]t]9a$8>2}>YBUZݱ6c~k!__חŚda"9g qH?AM˻dnw8"?PoH횆0AaRH̄;x-.{ t!`~b*XՊ0'L>68S"|}-V'}~Z-ٓz1DQKm?(֗5ʌ|}:SE##T@P.7JInsғ*|ѻxՂT/97qN9W{AE}&8@^SXFW~VMø‘tqlӞjuW7,Alp7sK2R:(ګ-?*ܹ:|sZ~{UUduݏ[mY[8RFXcJbv?+}U5t9=qCNԑ-#iU;qҬlv,[<-KpBr!jt~yvzHbOs 87njznB-̓cUg>=@c'ujnօQrGmNXO_ZfGbsV1ҦUbuԣ9+ꩡTfc#銵f4XyJ2zTmf>NVX@u{3ؒ0F=zmbeR{mV%?*{w?*1`TU{DC}I |{[on  LӢo3Y5|-|ug&vGS\ǿ5'5Ox|?F nQ/-:+M¾\q0v:}7rg7dP*f?37~[lb)- Hswo~])ʛm}cͱ]xNwo#3ŷ#rsY^cviU|~zS[iHfUxT#:s~8}VJ<ͼMEqxD[8Ԇ1ڬ˟5W2 x: ]SՌ;&R1OlW_?>y\_;) 1ƾi_SkB<\^`~/G9Oa%ac갽h3 Tùqf)c1S)WhRW^ t+ox@#[qqu}jڲAsY 5 iMݏϋ>LQghֶLum!%7P[?3]?cM>fվkh1P[R`W>u<#fU؆mfy;&Of>;'xܮU-yRNPKM_]}M s^ IѴk2 [ơQG@5o}0YIJsߊm[g+,ӦMY$]Q*R8`9*XhiڬGf qR5q{),~:ۆ鶬_j'<:aj/uJPT/(vKR@늕@1RGӾjxH/\bnBM/B ث o:ԱGh籯J z{Xh_Z-6]9%E,#n ֪SUͷ4O*C ݁U){6ӼaE%N;-:Gayz JhW':0:.hڅyWVϱQ?_%Gn.%!!nM {sWUONP'_('ƓiF(Mn.>Y# )0|qUXqUϽ^Wknͫ]v~]ѐsPюBi\Ȃo,[]dډ6a ^Ak⎁}G-quf2.YF<3qa4GS?c/_i? \i͘/kpZjV۫iHX 9!`ExrҾ;rq; +ʦtm/]Fm6QfaOTm` S]mZF|f- u99J΢bKF^8j4xw.~M++nn|9~JFKMKGxYNq= kgEu61Go#\,smiN|~s_ zJ|a|um2mGhճXOqGb+l2er/tU&Ե,t%%}Wf4l*sRi-{d2R1txo߱@ .+ zM+:\2n[]B|Pf_A-;N2NMvx|D6i=l}DZ;B-_inSg"L˒#gjgٺ ~+_Lxq wxĖӎ. OkM3v-M*.!JϽyib{msr ,p}Y~]?5k08'Zujm:s^ug>jϿhV?_isUbw[-}>xy+ny:x-.v|UW=s_s| Έo\=S]TCRČ6$}|sźr7Π`w{ tڵgUX{Eo_6v@HiS*9//.uCގۧ5y,acnzοOٿRa_+ͪ|:Ѯ&l]+ ޳iv\,R]vy#_/[|>O#J0kl0:r'~pTۻ*;L2 f>vdP`ԡkqbe^~bqߧZ9 ےj9#*3brEwpm2CYblot1Nߚ BTn?cKr3n/׭/䉞%5e8'A*8FlҠ.i;1ˆsofP|#45^Ff6{˵ʕF^7+ר+RY#rPdl`hD3J=L}ь_Q'?ag\~&?Ui.1Ue '#9or0]n6wiˏG[Tħ TԏRv'qPy*Ҟ\m.F;#jT$ێZX7%jT%T4F׫qhS 84Id知h[hi .1Yw)arh:0MX]S+r]dzThӊS9s4c5EXI@nâTٙ[OOC&%:9W!^Wwl.@qjeIvQi죻1F<zt3zFPu~ʡvҗ9c}:Bϧ0C}}+p W31pWM+ N}kk9dԟ٫ݯ9S~;uB) .={p*dW? 6hq[gn?s%|$PZмHW ֥Z(c /gA"\Df"T!#f~_/R̼vH1Unקyld yR>t@;{uYMvӌcWx<|95X6;Tlys$ ہ ž8p kKtjvɽb')/?{%cSU#]'E:40Ils#31y9^_O.Qo;ӳɻlm/b~f9?};7If_!CgClM-m ʻv݁r+ɧ 0c*kiNm)h^%-O2 X|*J+t[7 ~-6'j~ou= 8J}FdRuUwfa_D"8f7vp& K1W5q_8jѻy[7I7kka~#`~U;?!h%SkNtֿ/^ezײ۔4M'ݶ#q[eAZ4WDwK8 P2 Z"ŀ(2~|+2'@zKTD8)-ŽVX .O"KR`< bB!F飼"݃:Rv'n皮dfijf1o*v`bTM 9G49EvowzqTU}% nPZ< lEB,b@\YeGKU⟴-~=,vu, ґ;Tښ;J 0}*n?)pYY{{MK)6E(혜\Nkt\gUPA Yv5W){۟ !sC㎙F'QnzK$<?Y~&0oxkPh$ZOG F眊->W[#]/66_|z\1K r,&$S%,h{zK[_59e3g#Eu~ ?|/wpi> C5ٗ1Ȭ275@%ὲQi[e/5mB2v̪Oȹ9k_~Z21i/S۷'ܷ j<:0jWP[~_<)zr0 !c?y 㪖hF!e={W/xwvoUm|q~#?1xwZ_i :х#>a+_حWGe,F6.e\DN+yGʉ{&@_קA^-` c?jlcGA[rie\^6, 纡F:|< )Qu(-=,42Zq!d֌3sR2ԍO:ݻuN1+<%q*3 Y"HY6$kCoQVU#[{]'oDz?1,U(7;?C5$J4 ҾNxٍv$1^_k:KXm OڼmqM6_oS?i?|} '-JHѣXiZ]O񯅯VMִolLDa"̟oELGZMrqϭELOI,֙;WynСw+} ӚkE;p6ʚmC>只gL=1̬rW?(kcAF_/׽H,c#g cGeF;HǥU}/ʟҺWӣ?(Lni܏ʏhLhsڴ`0?U8'glWCsjȘ#w=+:؟Z9U)cPPgD6oNzU8$jXqqhQRhH$)VzxTpZ[4;WiO0KBNRцʚFLsȈ7+TW_?imCg0mds׿>ߧId"СvȾdwSпڜi\)lŻ쩮HgIЌ~jTDGyN1ޜpn OZp"&U۟+="{qNfI^1D%H0 y֨A}-~5EjA ih/B؅`@եT,+yAKi_-p*{SO$olWw1rwX6jVĄ=hkEU7Ί; D͟{半BҚn88l\ԦX8Ԋ"qlǎŀT6=5#QEfUXzVg *Wf]c b_Q.P[;0d*vڽf@?y큢+`FM;ݰuVxX:ɞOǩ !UlRĂ9/{ĭ3›kl[{Y$O|7Km͝ϝB{#B$k'v_zk;>=?|QMX`ʏv(_ 88Zs? /ӔjE7>_y?}_i񥜚7m![h\gl$TGeD>OyT|̡NO7//CE-=m4ipkd=M;Ym[ W|UfrUmkK-V W_J7hmϋN2G`R̽E?5,'̄g l}=g<]*|;qz~q3nnIݸ+e%[_3Mm^m( SжWnqӊUr94]W:3>_]Q"n!Tv5,%NW8ʂB4~ui۴6|֣ԕO[遛iZ]( Eoťǜ֦lYNJXƑ#Hiˣ5ҮWck/OgL=SL,ۧU^.CmǦE d(9{ݎue\N@lޚ2 55lzqJdr3t{MOF>l]\yHFVnE(fL#L}:|`.~֙Ef]O׷2<`(>*ṟ|>]ĴhVGmX;?oۊo*ab~чs*鿵»o۩gf/-$eu* qO׼57WмCMr5ˇ w tw:d:υ>0?>xk-/βȱYgтG GԎ+:\7rCƚ+ũ9s]I]54ZVvqnK|CXͧ%$gV6y+V1C s?PW;gQw/iK6} weƶz%U=@P0Z hHWoμξxOr4{v[%o<߅q{[͒?1+= 6][*畐y#'5Qtv9:|*Si?_G甌7҃jpjH3|}?:_{}vʼpwt;meA{OG\JfՀc_X̯W%|V,_%WNEiTS}$.@N=[;IysN+PY]pISp5ľ;.^a3}׊k-oy.ZMO:Ȱ|[6P,8 #(Fkڤf߼Bj/2I5zmRZH²wP{~50^#=k鹺[HcrYQ^I OȠTYU[u]LnAuwR%c*M45m\2qe|nOj< qTeN3 7vdӶn%GWI=*? pFlja>'8l`Ug*HNzy,u `F}*~P;ts`*Kl,J Plg :]6` =4ʏn _(?JNE(n7-{m}zXh}cEFuN̠Ylsyqn$E:6iR)ęEGSPɥaSCwJ+<ݎwϡ4w XpjWAc) Ա^y-nlƗ}+6 qW- Ǟta4i/n*hSoARZ|2#`;*nɅSGy?nշn[VrnrOڝܯrCa ۇ󩡄:nsǿzY6^NjK埘RaԒ HdvpD#E 0sZZ&ikc2~^:b|+xsXWd#r6S'?d(*[x&>] Qū:|o~h>O;⌇6ŵMVKQ Qr/?S]>~_T~z浯_h:ܚ [ZO,um4cpqk?>t:4_2Ksi-;I Khd9s֟iW5OIRighiYUKwlθ9JM6Z-Gb+Scxf@sn'hwت9t鶵mx@?:aa2fO\1~-j5sLd.I?O_h=lq~,2<\9嚏qxvݯGm\}t3-_rڣ0 $e8x Hy'4lƢkM]?yecگW#gkaͳY%_5mX=jnc`|1^W08Գ^[k=Qvd>G zе9tQ/3Z+! WxzX$ό}{ )0qM_63N$(7Ӛ]5b`>ҟ$ l bܨr;Hm/yRzcK ?/MkRe(^㢵7/։ m<Ո¨ɱAP7j^ 7k$.w+X׎/;6gf}6]ø8¦6#b|/K}]jv/Ԧ1`Yfa)tiHBi>%y⓻z=V/]6?>kk-|=5:MtNpGwls/;~\OGǏ7.d_hwR*\2!]:Y񜫫GF0R_4VI׭ F+q m2r8"Ji5f|lptpNVlb&uYp *֏yeʙ.I8ؙUYm!ݕ\w~\ի;5B |9{{(z9];;T`R, |__Kk>5h7fR^n2gA_ Qni<ݴl1?oЯ';ƪP*mܑF1׭|sG zkjgA[~=ʉY7+)6'䯠gῌo'sn[}lT%sUo^[h⪿Ko=̚&yzsҧ!+.a/.}M|l.R,mQk!bž]v~ v&,nnĺlcecUԕ+3瑏O82qʨ^z}+R}K;yo_30U5?V{_Z /aV`U|}HA_ OQ r1F͠Gυ2l5flY>͵:u]meyl;Fa7:.Uui5ѕ{=TDE呁Kecuz3 V]P0` oqUEl(^2zGZ$ofhHn~県g}RvpFlS:-,(m5}%жC.sbޠvr dc豷mH՝'\T7df˝=m"l8=kǾ"f=45sț+a#цh0 FoOgÙNKG:繷`Swqm/Ù%V'Ù$0>I$_?g*j޿ି 渾+_I{\4\ZȨw2x\Wܨt~S#G~\dn@.m"J13AX҈C_ZugʹyL#pF֤K&#Օ2n?Q(ܬlY93t\Y&6~ZʾH('~]:@mN&â*KO!lma+)l̻E.s 6KT J`eST.>aGUUvnPbcMji,ßZa7Q \Or>pQiv5iA&eMBo^$A:V[#F !UdЯʨGsߚnʑSз;Ud[8>S}^Ɯ9JT*-~a$^ CFx|^+շOs bTֽNwxOq+5ƙP%֚Ȏ">PsֱIim~CJqYGwCx%߅-- Vv"w7j# %*39v` we ES_hgZ^M'dF"۞z~ZC>=ݷuWKX :yH $KGT>e9H d~WG:njX2o>CoR4g kS䔡Q>)7_c#|j1=O1n%_luRld19-wTHbe" 9?.qaBϲ#/1бY{YF]?G浫.)TF-]-z̼nmǷAM_!?vYUAX*y b9HXi#Y@vqsJi>flB ̉ϙ9$lr|C{r9 ms@>΋5]?2su'¹Kc%eK,-.y6NX㝼:XU`K}rwBWhUo_'y,k}?_Hwog].z $u4ZxIVQYNC uxGY,|ELK@Pp !zaaqws]u[oK mlbe(CxW BJy؛JOK=%>ksZNݬ*ě̬NC{*ʹy-^EԚѝw*h{dW>L~QO]Gcafu0]/UUݟzmxJ<\óX˱Ћ 8XV6ڡm_<Q,+u5n8ݞ;\V|ꏔ=; u-kV9K4R29M[ڣrk N#$ E\#k4bk=ͤ8$.~cըocsvާ K?zR8t&CڭֹPӏG596bQ=p v]A춺X^Vs?pt$z?ՌwaI Fj9r=~\xb/gK~7y*֩Wn-5oM/&Fؽhw.43vOĝrT'm6Fq D6ep*0YIG"GFgM̸VT 3_ x[tG}xTpfe]?N;ƚD ݻ2)Ba弟W+Ne𱮆|#Q9Nk;qvr9"o=_ﯵ}kRz;d`/+ރ!{SAoF>47PwOs-Ս¸F0q 2D俋|ezgV%C{񜊤ub -.Mwa%I##%sǖ*?cXzI=V{}O ,k n}NXߥiEr۝fsqnkiY[H֢V[Ytɜ8kVЭun,o('8qiEq5A'nsOửHUUnI u8oZk=\> XBHʐF<>-on6 -\~n`qیNM} > kxY.h$#wLx9 m_mq>j0ھ N2x)35\B T ~C?>, 76,Ʊ )to1-+s &G _mFQ[ds0*.UFs_ax=cRl6ñoWYjZ\Vn>[hwUמWrFoK[o͓j3=\V:N~2Fkg7rMckӅ (;WomWE2ta\/?,}7ӥ|*= 9bٗmHu=}-k35Uï/Uj2m*/j2ԉ 'z{Mq/x4ۢrIҡiRq/F?bقîNqTn! v*o3Yp:Sq)H`xt'ޘHMYpE1rٷ\R<'Eo\FՏjń1U)P/QoF{p2 +8 Z) 9EI/:qUL9-S@*[5s+ew~bn޻ *Qi bxn?w;A,T)_7{k2f/oj\]^zդO7+Yn+vu_#е2W}ֳ8XFQJ_yzyC (q1&ḓ9jH\ppji8hlZ\(@:օۃ\^aoWu88i@ ~~pWP r}jdԃAU5 ;5:~⤎R?4;wΒM@3Kf^/j훑9T |T Ǐ᧛bCᎳ cQ必m縵2Li#<$$J#j^<-? o06"8 pkIϭM |/VZxJ8/t|wW-~%UjzM[m ֛{]mev?:$t*H_9hkq-ߗ$\4hc\oa7bAphݵ+2Ir:,:t*@N0GomgX$;Dk/g2no;s n$vfw/19Pş:QItđØHe,jT-b6}n!Fz2\1f@QjԂX dV y%/C26K+?)+v:o j2F".o|Ql4 ra^aۻc,'-W|rH>ml) ÑAX6Hy)Э =֧pӥիU~ {{ "i*ntBD?>e #PG8w sG!Zm Gb濖okϬXvG7=l~fC X]k6º~bbĤשT_cy&ٟӑc=⣖'@dRZCxUUkF<̣1|HТWph}r׭CFzQQ\X٤ЕX*Ӹ|}iju(ܙM$F&I#w{0T ȉRIr[zF|+U˓55nlv-e+x<*(wE2?oj 7DU>جgʃМ uwy_ɭ[}*[HmXfg3^aWPo$ 8N%>b{pF=+(*OnzS'|/[ V 0Qwn 3 <kW{)"[(o2}we9P@𝞫ɥ΍gϰ*1m 랗b"C巒MiUA>F1VG~I|.sxYo5gz$in2/񟎵];ķ|]i:ͮ. '_-C"aeQٟƨ {7KwP֬5.md;;3GI?_5yQ 2xa+:t=<ˉ5K3;#>Yܕ$p m{{ejUY,dFILVk`B;#r@5t9|ON|n>ɨlz 5ZG6& ZImʳGE2* .7+cҡW5/e|-{c#H{-C·VFY :T9JqÚ>|K3|+8khVvѫϱ%Oҿ-Fݤ`HUH@7͒@><^|AoZכ7[sf IG[˭CfX-֫3ORFN ך RC/}߆w%lE^KM9⼟QI|ּ#%5/>^6}C_UŔqh:f9+5b !Uh]\*As*ʪcQֽ74ۦSSS |qfeYʫc漿_K*:2S]7yV d,Hm!y8!TV*9+x9۩L\ ?2)]`,2+xvR9~kBL]ǜ?b_^bft!|{n$%&}.;٣/.Α ZrH~l)+!2yny˟~|4MڪǀAa^ֿ_ۃFe4XBˉd{-!9.,7M!vrC$]1auߙNjDZxPe ne|" Y͟S򲑜5OMnYշ*rq3V.k[O*cPT|Wv~PǠ㰫MN ,yXKbGMRl'QWs*ü[E3~ir J3VPӣY0`)Xm֦!%W_xۼ.rvӆ ?v Z&hts4.;K_gfؤwoj"\gxCcJ;@RvO/ި5X6QN1ԥ&hArȐZmɸzqYֻ|siFF]>R[? Rt:KUۆVTJ p1<,[Jh~j6ÞU㞕 ؔY+ޔ<\=ύuDXlMɹ@?Q|kLbkO#ʥ]b;Gw*+I #_̂UxdidoAU!F< T k/^ Hefue?6pM{?ʍKRUhg'1Gw'uwxTƼ0u5[%dS679VV7 żӿJ tU)2+[$dʈ]D|瓐A@6>^cH73ǹjGݱpZuFU;6>B hO8?UBUW׶ MJnLW·W"eY$c̿^=Z}jjLFF5A5y{bhm ܮ<]ьa?gf)'̚?>|şO̲.1J8 1f#hu ͵}7_Msxۈ5kq!L>N0y"|ۋc .xz x{v>gr_C̟>o,׼7jdxD`|^8㿵~;xú˖ҋ2~ԉO׽C8j5+}~R__>Ӌgcu0cx IգY.q`3Ԧ}f"hL7~}rukW PURyQITζ#v]͕[Oz_MrG7>GnS=<;S_Ҵ-nA?svp` _?՟aVpB]Ƥ ˺Ihgr&L | 7|/oR}Cb\ U`9lio#|;M;@or*yJC3l*񗊵 mkUh4h]CfP\g"RkA ̬[b^y+rm-l]|V׼U{70hڮuq$qٛrTno1@rw)Sϙ~2xT.ex 1.T,&H٥FLO7)o,đ_*HFKOFn'{}OMqG *1xT(zr\_Mvn-t7#ʑIJLJV;`kA&+Ԛ餎kVIʦ62((y'W>bա $I$lƠzdyRP֦/#c4)0* wyF1}Gԧ鵽>+[kVy/'7p>lʱkjc-dW2#u&f~hUf [gWEv&{5#sd@cӓ^g>2J4[S<+ecU$y#;͞@x1V۹RֻM[fYcq*0$u8Fpk|eѱkfPoaHxR%T2]˝!0svaZ J=]ͫ-+hZkn^xtL^~ VnKHb$x^ Rna<ϗ%ؼKa$2$7q#wrqڽ' |7|$:--5 $e؜< zs9K ){W_#ӣQw`M: ,ڄqȪW9<c ]OwL+p;t<^V_|$ѵTbU$=_.|a$w\(`Z ̱Oױ_J>OJgG͌}+z嶫+IeW<?/AMoj vQ򯥊OS_H~qY:*ɋ 1VF[u@P隫1iV 3zr?]Q3c`5l7 r)> <_|-V-0x\V]NY#ξ7xEÝ{տcSܫHUZ7BH9qk?~;k Cj&fXl FM~0'Я7Ja,w*?Z~ZU.o4OE&bI2ɖ$=_11RvקOTӞ#ۖqiu՞?$V7 <$h^ge` #\vK5 Ƿ֩_^HnTbHp#r:pLJ[f%OҾG$5G]BBT6%#o O|~TԣWG N4nuxڌ \4̳1@9sSKY|XbWAXgk+.7 xRrGk)kX[gQ8j#楅#`+en[" K"q$λOS |sdQsb sQ"" =*5ߧ_ƘRҹd4ٖ9>cU[F.rO[%43-%>O•\d ㊝^*Ոw7AUn{NiN=? K%zu09=[v6PY_*woʯDլ\W/DXT.S&l3i12V/~<术٢7dIc)7SsM_7b6&Xl xO㶍1.sYI<[ZI.ad  -{u~7/̞2ǥ&XF:R'f,zYUoOތe}kXskpU쵆cOu'%_c#$Z~_izk6v 1Ckm''bf}DTgO0OZ=%--VaauY,ݙnO|.O bTT̛;s,p@` q>76CCYYM!S<UF8rKJ?b=ʍ !)1sڿ^[Eӣ_MST]FXq/'ɯS53zEe{5d;卤ׅSǢ=qmlj[9vf?jе3b2YOzV̺]cُ1>z=BwY9C[Q^_MӢ]IBRK걫V8ʬ{~_W[H[.ݶFƹ 6g!T} ]~uWQ7 >U#sJ\o"O3k4,/r+nX,chfXF*ղʱ3ۗ⦥&ܵf#Q3 jLKa'Jʶ1<6Ekic&;>ܯ=3\<%mrO#5\WːH< jמ;GǙ ϴ]>h~>:xIwٴ:g>^@v$k? yi?gZo48{rČc>&}/nPW̒-~z`7N彸0%NnjYX6Ͽg -Id>i2k>2seǨ Yhxg=*𝪦8 BrkhYm}GgIJnj<]֚mnbTɰͱpOx>0NÚ-wp[UYcAҹ?m'څ2emռ߻VY.Y0̱E>iy y'MC-+"iQ.yztcxU,=CTxS<9˚=4aeڧp' Gs7}ST"2)ݸߝo>?r\kn|1FC.zOzfo]6F+%GWˑ RA-ԓZ#jrW{m-h7^>x3úeyq$3,Y2bp_? xCfGQ[ I3[]}o`To1-p090>T/-v:&uva$QZ8* q@=3E~о',d;7Q=˱&<]#,$r{&#ʑC?jڀI]:"RsI$apUUPkv գQƜw\g hIS|@w^kM#$kxdHT@11 %gYP%I#oyV&SiJR?*kf?Z/ok/AKk J QdI 뉈ErϾd%ءϋo]5,t|-q5ZS3[YB$xq 9JP/sx^mrH`Ag4Ks?&0053 9-\ޭˏXV֖axz^ p7Z{IE 3 ЬIz#o;x 0lFdBXZrG\`^WN_<\O/nwkw{;+#E%0ß)T!Pgp0#v*mS6Йdǒ=x8 Afe9Q۶k%Lګni0?=ǰy5j&@w8>}DoUl8Udo_bw[>uaЩS3 [[WAk$jZHvžOvfj[;viYKZP .g~z Ļma>0v u⼟O-|A[6JdaSCum 28{P1Tf;Ww?w41 $B.Gӌ# ,hHj[e =Mk *ΫۄϼEZ47$ G3vծ;t㯸.m5(| b6w9jWV+n>y? ݫؿ_u["Uxmi\7,m&_)#stĖ̒@Ug5NjƯԛUT,w=߇1 |y?u kB.r'U_mj?_ "pmp| u nG8>-9f'l|ƴkJ3d54VIkgަgouᑶ?7L+(c GYZ3@ uXqVz&kfKmi =wǔeor1ԃkRo->vhL3A9c{V.bP8 IQRj<yhWmw$mn!Ho,9w.yZۍj 5EHtwW,r .2C 0{^8STt=UoO|A֢ӡfIŝpꈁ#Yj$5Q;x~LA !t^|<ĸ4E*K&8T%.su<^Zk?ji__2"5fD;2O rө{t^ ^KYզiǶkXwf@9gN7jP^^|.նsz_x$߯Vn|Hǫ4xV=y^Vn ez+n5^A&gd aF0㞕ۧ*9)ӧiFn#w[gԮVl#Xtevy,ʶq#8ϜXw LגhvQ]2ed_#mb$0;I 9~b3@CsT_*m%koSYu6?T<(n4x_ #8 K1ݷ;oY%$\͍FJs ]&-xKme%Л 2Yb n?/C6*1 ̻RTN3"֦OcELԭm>Ƌzn0p }(YevٲFѓmMF}V-$̏t%Us |} xOoCe&lxvY5X-34ȳ'n#5xNF.S+]X ̻ȸa MGθı7ʨv ĕ+3z#HFs ,09?SǢ^KKK|q/q7]ӧ}lmqyMz,4񬓆7Q#33=Gz?;gss9ڦ"Fu%3EX>N~NKO\oټJf•8hդ!I#طGz9ѯ6W^_m\,Zơ)fq$lCs軳6+MKns1r!IMzւX^ehvGm2aցFQT2[̸\Ub}06{5JnZgbUDb̟́[@zMoiZ7YG'u= ޸7P1GxɅf+O>4I!{0:p wJu!Z>3j9~~⻘<B%sox ^H$mMNA4,[\[>a9ANG_pT@_F-QӒQӡƵy}r `OӒ8?j]TV'o\Mw:\[Fz% s1 ~2(@ŖZE4<Vo_O]ٴ$qѮ~~uj3i4"̞[T Vײv,FQ6[Or~hH&.}&kt :{$qOϽҾ_h8gOVf4JTMeז8F=6W@NNqۊәBm6[@@5Ṿ+u;3JRQ;f=Ռ,v01Ҫ<:ZrEaSv*;r6yeqAhX)HLen)[aYܹp5i4TZ5L+Y`5RZ#HO 09ҧG|ںxIUKKXت 㫊GqǯMx*.&zZb9OJ: rQfǪ<Š\`6 qYqL/ڞa3tbۨ#h$gwͬ׹uyv>iѹCz}ڋf`F瀤Ա0K IO)0ڤHtNZ|TjiIRWcM0`s,Dm e_Zjq}@kJ MCq0ʎݩE8L,:-tYcCm)}*F8Nҏi2AX !R}iI3pc͸bv`9S#Q9ՠm7Ү<5bo8-.77s{4g~t~vzA<:62ijFNY*;^`xYYK8v(POw8pK|W7-^Wnmdgb=;A~G ?gG3_ [V/> V,ja%(d刯|{W7z?ºhf;ai$q $ k֯f_|[॒⧋mtM q}@*\Lc~+}q559C4,W0z\m,,KZ+p#SK|)>OK&H6=s_Jb3}Gbnx8x'żs {׭mUROk]9Qb;+=JPX9X/_`Zs@!HMvpV{uP$|u9>д]<vfUF>4Rv4n5Hjn!fm4pZ58Wq8iڴ1#R3NJq8zdq[}f=cq(<2QV2R$w8Ge؅Kc.:r3Z>Fq~xZb- ל޲M ʡW0#% ." lg 7{aeeXvxƠ'ֽ53)63%)TQTƍ;}ixKh34_k[`#+z cs(5m"O2;TvÀ|CW/K_ 8,=:#n+5awQIwbzL)8n:rC_5+-'D%o1[35mw6ihQIy -cvb=Z~Mă;[YfݨdHc^X<;[ gVi$ .VѧRU-}wC,|IC{ċkGr I`p |Co8Sog>80p#|18T#~C%f~Ѫ?kCR83\vjkV:WMQ/63[>ZG_lȐظqHfR5Fk)ߖM}'RDaY-t˖%vr3=$H>Zru_~!^-K> 쮡bpy a8֒з/5elZԦR)__u'S_ bPS ^Iς?o{DC- f;KMSėIǵܪ^9fm_ඳ_^IcMM=dk/Ϳ|=X\=ԟؚz5i ɔbH7w/݅JSrwnݬߋgkˋԚm[{$1Q|ňvTe_kz妅 mf]FYFF39;~XRPwmǑ2!s8>cREC)Nb4:W1\ԟjݣ{I@yZWk$Y#O}1i' 7t}M"5/pqm5ְH] O_O#㋹:mdKi2DDq\nTaUTV^"ctO쥻ma7SE4r_u=o Y/lR$BU!b#F 5e9,ȑq~X$g )]G.ኌ&[1cp7=qNOr7vBQX4ZKId]I#HrWq5֋awtoas]eo27kKM}kTq&vLwv,s4~v$.q:bxvp{k-yfty9mk[6һꙵD2GnjZ0.Kn0?xEyy,4a,fi/&YPbm"2;1,|TCOANd+#>U y2ٌc cTkԜfsx݃2}yI8'܅nHxsƴ7`#ƪ6vHGr{ӟt^իԋ!a\3jYWtY'=EtD5f*vg5Cqcp=-co ;/G+7׼5›5->~RG&_? h|BY{M_K獎n7C;wkI־o]鏼:== g{6mׇ$&sz"2+ov6vxMgCEޛ!sʒF)WA4Lț|w'w8b]9OiJHܖFPc*w=8+!V3nCֆ(#!om?1_4foHNQ ߇-یK]W;1_Q67{ii8{o՛wPVNp WU9Xڻ⑍Hq#֪ݬ|ǯ54 ?<_^K;6曓2!msIk ڒ/Z e̙}JocJU9Pء ,=ZhY#jճݎ&?sԪZCgޮH|Ì~BH VP yd^&a^j[1]©Zȹ\ֶ&U⼺җBer "9CPZȸե.SכRL43A !:kAБ٨NqZh&ug˖R7l[}*E}w?nknpƛE澢X}Ogw<跅>mᛳ,]TW'w{5z0 k)2FR35G9n|C15=)bv&MhF\獵I6M|r,kR=&i[T' ߿}Jh-+]> ޽\Qp1ES^x^m?T 6Y[@w'>GsKYMTH.-uy3cni+ȩVxZ kO=^,)uw~DY%o{oetNUccwRM~4|<;Wߊ~;<:`t냪/jsFxC%dmyn.<&r-ro#qKOڋ4;P%x-~` *JS4J+䝒]V]m~ gˌK]=4;9D#dȬ{;r:?yׄ]mp1U]ETtn_W,RL{qwǻ1pj󭯒d36:?C=,sL"{J֚!lɷeUVlzIץ.T@4hP'-W-b)LU1ANILEX3UY9?WY_pfd9 ҪIrRI˶6vM47P}Gj{kŷOS}Ck,pif}9#qԛ< cH΀GrגOs1 A|i ~o[f̚= rQ!TװVi~ ^7uk=h$V]}:ߏi:ſ^jіF#_EUWQBUs3~~K^ u+BiI4Vcf'$&} QvA$j\n'0#ǒfU$x'mYġ{"3DDy2۳5qK +Frǧ\d7ʿ.md_Yͮx-bdBprF X+>ï X߄Mbqhc'5X1JxJuewl.|^uEkχtMI<EwxG3S-/lCnXƠڵBnb##Gʒ4a ƿuBTrl {BWRK+"ct:Jǔ_S7cD"q`Uup7 ~ ׾a#kZJO]pln$Ği+#m%0@"0WƟ-O&-\>]^Co#%rk>%[w z}{bHMǚchسd4Ax onCHͧ$V2FbeA >csmss<̭y#ÿTzY_ڮc[M( Dа*1 ,G;ZzD c}dnJVfM>hg@ Nq#t35wQ'&d^ǒG>+6Sq fXˎbJ?EQ[R H_12J~Tr{;4guL2ktY~ƥWD9`(9\ں]4?MomwF"d}˟SJm'3llh+I=ZXߴ $*=`ﮉ~62=zz:m2E[n#BF;B{ǫ77-i xIգem@a>kV7v`11'*t+R2n {d+24ںO86z~cYŠ 'Q>[bn;ֆ‰#Eȫcǜt='1I[yv?j6' xn+ ʆm 4^a&~e163Ex:^Pzu]_U^,9{e^#TDrg=Uo/'ZT2D#B3t'bHF8Zb6]8݀xAK}F fd[dm#1^4?t1Vd]]urY?G#qыU!F[$ 1k#>޵^LM4 e!Fz{Pa`UE>~V9=>Q;zdQ7u:E]?oC GwqM#K?,j o_zgnHC@~yKkmv>WbI'pCw<|ftό /5=.\Uo#|3g~uOu?ƥ:?$cG4,VXۭr?u* # ܓ^;*~מ~;ndiͲkB0p7.z!Moe`Аrk/[35~7WmoӎѤ]ol!ROezk H$V)qֽ O3o۟m:ǓZY}x 3h8xe&wӼ=9B&IXwqM(lu9o#"cRsKgj>j縥je,g>Llv,g=;k#kdPC\1 L>'B[ztOz}i զkqok6LmBztvJ /5sR_U=ܟ֮bye $ XthV|7ȱ/Rp:}#itr*w?y|[zK݊d~nfnVgSAY$|hs&~7EbcxC_3͟m iZ,KtʼǼgfw-lJ 7S)儊J~ Ijp_3&Xτ ƽ ʊ}+'Ds7}>+Ys݈xmQCpO'^ A-0象L~]F'#[tq0;+:j CbpzQW9QC {BˤH"^=2uj*w%F$'pZb\l"1Wucw@&⧎Z;X*jomeDOjh2ұijgv=B8}ASo ZrWԢemnkhJ&Qz%Si\nTXj!R\˹ZN1Q+ؓZV։'] s=4ĚolZ\}*Ann \L=4 6M7#UUTd~Tp[y9tMB4\ҺBpš-k*N+SK$` g^u&.k6* t n[C(9G7N՛}8#-u;X¢:r\WWe(cMK;->//>Xio o࠿e emDi]F@;eIc<]/ï'^ qqj`?<ڿ JgF 5y|q _&!0GNOIFޖw E=|G.~:~z?s|dw۵fMʠ+=1?:ן^/&->Z|S+"hwO#?_+ɑVAaXuNZzcqխ;RdfF9׹ONV1-v'Z޶xVel>\Vu\F|oS5#7Og)ԏc.^Vt2OYzBXn'S~ϱF- q횧342Ʌ*0(Qٙw[Vy1ϑA̓WKie_hxU;cA黟5%Y|%axt6Iq! WhW<`n~otkY~ Hf j ?\^[af!]vQɎ{ǟ5ߋ_>'.mBK~UUF81_>FG;_z\5->E8U1 /3FEoT.IZ7 7GӌC5,\Ք\CHS&k 8l3  '~ZGe$,MV礇 9 gxeJk}2Ě)>g=d<^MJ߲c89|I].'m畺d)Ow>gWktcs9aIY.i tXG¡eZLٕ77կ-:snoz=s]t6(+]k CKKCtp6*-t%õo.dу(+XN[Hj]WK-ڢmps\+GmNifYҧ 6*dG\Es*ϊ<5xE}K}6v "upqؚfejZ,6ecHrfc&푕8;PkSޣO:F2\^k27HbTݏX NUŏ:^5~&k+˛5` mE8LjVqZ?z/3&y[w#ZiIg2kmbfbNN 9]BqE8ގoG\ִ6+6 bvpe ȎςXQ$u#ҹ?ɤiΝhE\fFJEt)?&/;K9[}<ĶO͕V*Pc`)zI6_2=nXo_=L=7ZONECzW9~9 u+ \r2eh¬@0V m#kRxtf#je`–9'b7QٚO:d\<`(8Z/{xo帗K])Q/H,XS'i02dq9MMh5}.UJT\[-E" W*sV汷w+nP[t/YBdƬsg[>=ztGFG?+1޴\]]ve}{^|,ڢYʸ-ylֳ|~F[˸f;U jlb:ךڎK,#-R+A!10z<ҩ/'muhnIO#n?NW{+_d3y3c O8w??nOk᭾o'<9g*~"Wx"fO~^iK/=n?xκЖ^iСbxdm1p֏4]N}^ujnENj&|&MNoZrP?3W#zuGY&Sof*JA?-\רzo=zľaYx^el $ MNar]'(qЂ;'[Us_{ymGK\|ȣ Hoh^1{'`7>nϽ[zւ]@nU5%#1nM9`m= I-[؄4 mInRO=A*[^ 25_wOq"LtxY,\5.Mnc7$Fk'܀, ֆmfI n}붍M,ψMKKc?cړ߲O 5q]od$qq>WΗ}?M<9y[C52nIc`m=wfq} s^~σ5+_8lZIYn0grWѲ^6cO9qeGu./[v1"xdR/a~1VLlϖFO7}?ºzw^F[\mk˩تG,izZĢݚImL+#l|11ǵhIo暚]^ ~p%wsHYYYP3ck`gyl~KG쉯kM& _G.#zXu.ʪ?d PZ)MkAPI`R2دW(ƖUgӲ8_¶$W"kh{8I#[Q;UYЯ Y=]k8ٙwnOYZtqzٺubH-zTY-nIe|Usnʍ.$玦_FGg)#vP<(۴VѰEYG1+6tu((rwZK7>@O֜2: 26> /QeG&&.;Y=4Q-M?-Y>d?.Զ1` Qw@TF.p` )ѦV1)պQ8OZpNK~j?ZJ} {UIQֵ1T/mVt2tO/'{_?VfԚQ (gU iGڿ >\m[^o䑠dP~ܿan5Rg]rZs 3QgV|:/Egj7^Z kuv'S?lcL?f ;[sL-qN|ۉ Ndb^Ai-һcTMdm ^U{:j/= Esh|?'{˫[Hl$-= g/cqh`w6z. d=k+RI9>+_Cqq |<3ZG\~d5iO2՛\r\󐪹#=Jl 8`rƼ'YC}R0m/ YՔ̱>Lg>+{Midtr~,"^6yǖW [u%Tʤw*z~k.?F?iKr\3m&/<[n[v,x$USi&SvWcs$!98>SX"2Lt'VaY<$1)x/RZC/ړ4Hs?~}}gFc?6Q\uZIy7h)SAyI3fdkPcL1^~\`PލB6+S{8X.E9\A~u?Zg'yx |0eȲkZı'L+\g ח%-? Ե+89ҿO$KkxiMrnMv'^}jқտ|\{'k|'jq\mM+w$1^{w<4lYpiy.1FewfxTT~ z|4*f'Jbz~5;>.9a +)src.H!p{'KqJ_{ьVLQy<ŸiFT|jv?Ʀ`G ,s^x7bshwi $gl A20k0ռ5j焭+IU&rʲ!w)&O:{Ui%ło|ߵOP 'όZ syKquIyw5E_ݨ2eWe9ĥ-?Oé^5Ή QLUm"h&iQ@2ێF6n<^ZM{P"-yq_gO.5*aD|`rvuRvsz펖.,"P9e,r:m>֕;,1M$<h׭{uV^fYd DJ Ao;+ ]kXQegm;s]Wt};@nέmysy qGk VyeT{Ȅ",6~!t-FW Ïw✕w;p^Xvo)֭O} }z2Fh邠5 l1̨ǃ*C]Yʫ(Xrsܓ\.|kP#eUSA㞼qN]muAylgّ7~qÎ a!E啦~V%B=3TM r?Kl|,7en߭cFRt/R<l+Hy5U*p}GLelw&TuB2G*$c""pc~3>=zU/E|mnH|Eܵhd2to:US̓||k}f֛z#;Rw{Ǫj+%jao4l#{vF潵U8U15o4#L$ŤG@a9>CIhZf!y#"ƊrX=+ܩF5*K-|jT9Nݯw5o}#?bٹ>8-~iP\4Nw׽|}ioz'ӽ~ |?kZ՗_#uǙD8?)'4x>bk;<;kuxm>hOo3 e#xΫ)w*嗌z,7؛ C35-Lj|;.匱B+Vi,$4yE. 1eJ/!t> q[jɡ;=b#%WȆ2I  :&c$ MfQg< QuZ!}wWw\@;Ů zB,5Y-.t# 8xe~e+=ENWYӓOU[?SEKt{_?4)-Ƚy TV ۹|2bn՞}SjB@ C\҄զS2z^v؎;Z;rɻc!xUV7fcy= $~wABĂK-[s㝼^(Ӽ[{tB2ʼWHFG PGz{JrG_NQxQ (`Ƽ5tOQ{Hu|g;-L+uI$y}M}za8jŞerޭT()q?uxT-kOFUhjޜ%c{0ҼhVޅk522eG SLRpk `u8BOSbt2Mn2٢3WӾ͸rQhܧ/Q`eVYrjuJQP\8R He^uk3íDҪ5<9L,x9r-}οQM23I)iR%n/gl%[;Ɍ,188*2ZX8yqV@Ax/S,}c ; 5-ONMZKv猊"馾M37Kybvwg??O`4I..X%s_MWIeȥq +?ؿ 'wפsyi﷓RU`ģ"ھ>u=o_qk_H?&=[MF][e)WzW­Ct1S&Nqo}i} ľavű ezWxRk{+-WRUE8^?\;{V9MS)X7TQ";RIEhgM(sTեuHՌsI陈ڣ>VO /2ȸd럛A޶b dU;[fݎ|r=)U8?Z8'7V+鰲}|~i.jn"kX>SfysC0-JX,*IA5[Ps$p9 Am| )Q zZ4YidvVϤAn '\k7HصMR{8,oE ˙RA$wFKFI&ۧMR ?]j n4縛GXaBn5H䷺}gg\* c$5jRH5ﴭhEoWj`t!1/x}H. K4>aFr1OݥH^ro~Ϋd$"^F6;a#x'V9[fK}v7FY&]mw}eGfGef[z׏Zї >ATF#c,{J&9=zt]ۧXc6psJ_ݱ*[?ʯEusGRŘϠ :1h. Kdz(9&B8ceQ!oN=1mŲI;J%hyb߆96[q ĻTR$cǭk.hZǖV:=:Yd Q@,kyTnpz[=r{}]/ G= +o5ռ.ءi>r@,=72\߁QK^aRgnvMxtY_hӦ|AGkk φ +VoL5wxO~Cg*(_S~j)~ u~&I4,,0K^J߻a~`{cҿLR}BŮlU0 9+SgV]t^7#Si.WܕOIZRHV$/ j?e 6uj׳r_ .iHl?~oM^Ӽ5"\w؁|.v}e>΋o>:Jm<k$ kO`[[؜)< _~5Ծ|F֏hiilU8*_ɕbd*oFqt{e-r.cf_sz+3JTy_oZ7G͝<#k]F&\B2 ڏz5tmJh!SѦetFQ{W3 Ĵ~#[j'do7ͥ:90rv`:<&J}NݗC!H 2[ =v*\Rk-{=+Pnj48 woq">`q_Q?U~#|Ρ;7Xcc/J#=GmƟ$W#ǸHHx=k|m-K=PuL D=8JߣzUc,%@0!G_ѼkQĴzg)RLʙf>V~"a+QיBU[JG$Yr۱RaQ!n*eV_Ҝԇ4-98 s9rlM;FdZqQ4aA"[PLHuf#!!WsڪQ-,61Pҩ9-QHP3jT$*[ENj^QFLдCwVqCR6c) K%q|rjQ's>;ԑ\.niXۆ4R~-|dpһ3}zzq_ta>3ִL[X#]a sn~8No4\xqq]M #RvPXxN]k܎5]{9K|ryu7rI(I+]K-iPcpVW{m5>5mNN%ioyx:L$mi3+h`zΡiq/X|ExR? ZYOn40I"|Pu/Cx'2-n^-׃^ 跶~m5 4<}l#ڒ|3yH׻jq6oCɯOb\k}O?_|1g SÞ ]AtGҥd=ӅFΤ]\};E64Isҹe:!'r֏ .O6^p]{.oڝH̛X'Eq6X&a#y[Qk?jԭ 1$f[>g8e$@?pI8s)Jk{k=/PK?,xq)ʐO#E<-p\_7 㑂@;>j59(3*xz4ZFxnf)eEh1ȩ'85NP/|g<QwMp^t5{hm$i0~U@ӓ y\1[U6WT%g3%8^YII? }{ a`c+n72X=똸y$ON[h|m˜CƵ%8)Ub=s֨`ӗ/!UYC|$;rx9>K_~gQ1a?HϙffQ>栔 3eU)ݐ89k5[7H\+<2gbsF:Q\^AtZYlHvE$'ҼIr~j?ׯ7-!{t,UF3>X?=flQiT}N?'0HE|{z,O֣U|nr H|ftXxݓ@Z5 .[稢Oԟ&/ni%58z, t澏I"ܻd p:5|/]CqXm۬v0omZ鴫n*ʫs猟k h>c zWwџNxΑOf$Cy3e>>οKQݟk%dм^I9*s} ? *ާF1&If?L̘:JJq2)4N KWjo Se.az}."Q\oE ѫ5֛!f\"1SlzxKoc7 Q\͆#ѳdƸw_4_s$p9<+4 f uz|קG!cqXO zzq^+_)ǟV@)$23[5ᢶ0s{uʘzLG_ ߆1yO#؋wtLkz0{u\GhZGxCziݓ1?.8B,G3+mEOsHTn$VRh#p 2Z֮4Y ŬAbr:??5N?n߂0EVЮn<һ"|ApxOoZ.Jn_Z3/E?!&08+ޢF۟z'.H $+MkfҦOCC?9 [cOdAN/1k;b1‘i$Ni>l"V+ךQ$j68a@sol6[(#)JlM6x=h_5\>G'ԋ{p_si$sSs}ټoTzuf5G_ֵ!rɮkF&ҧ6'}*% qwqרjHazV#f0ILYT#"h7KAT qB׀R)ǡn[_{/_%MQ.m> .݋rL ڭ>~WO ~ƞ'g}Iyuŗij*zZVi;8ʢA$'BZfW>g^|F&]6Iu5UɑC[iC:c7u{k*}5C%L<(31UD(wN3uke?KXz|־kg'u{}Zɬk7 d;cv?dlQiǘ%@~u1C6ccZ@-S4|Ue&YQ>̧͌~ջMN/Oo>yg,\h]c/ ٚHg=<@lN,FbNc587kr}އ8+mSWm[PɃR8IFی#пE3BWXKe푷I6UG1~4|SLCU1lOv|?;p`eKrRBua?ki:]mk*n ܞT)qp:+'V/)i :+U$]hչuې3ߌ2.-cXbd?ۍ.qiEip'ϒ`<YKlmSBU-%HF],>&rLp̫7]ц:~!^j^#AH|6Wa[^ծo.tkn l}`ujZR dn=ȖZF,4p^xVK*y]Q3Qm_)k47jl.Dy7e6 0ڭ!eMPC}կo s wCj&bbW#oiwH2ݕ1hp\Śn`+d6c)4oro),R,rEeZ`sTS?1XjhV6Y-{Ϛs8+#S}ҬGAkyrw>v'a'P>fN9ys*; #s(1dMxa)h g./3G7ÿ*𕽳o繱 Ȁt9Jomld\Gr=9#6Dc fJHc牉E~yx9ao!e)ܕ*z80Qݴ92775hyPNF q\~="KT R_nIiHʓFY0A'xJEۈI7~Jg)]j|v~?o%f*lPգ8R<F{vhf9,:c5 Scr/]랹(_7 :CGn`۴,W%aS//Ӄ-#,gacQ.~Ϯ;I!|ʲb2rGMUEOjڙLL0ߒ0O76LկAGr'3.[ zLj6A95)+SR*@V~2(eJ .m D:K_ºm/Wڏde4z~3f_?1#;Ie<秥aADS$3]=F3mbM]&>i5,#:>\W#^HG"*Wy(R1+S\]~uJ噭dyL7|7IEqfא\M$2$Om 7F=V KjfKHC7̒ \sЎ+㺺I+7F R, A^W^jTVmikhvEƬO]*WYȢFBcaה%pz[uhђ RU][C@r[5V+.o^Տrcf}jΉ,j+.~d2|c?A__[xῇ|aiv.!t+KZ !G濅5,B6_w?|sopҋ:>fKTG}*-TZs+=jyuTHq֚Xzg՝\ISoLsYu57Y.;#n|Fj7ssBp?-F[D$~aX)FVcMg'砦j6a*&hp)Ue UsQbj7w' *n[<Ӏ rkhBo.mdaOZ*S9^=ҽJgQe#Ђ8b*R'PkI; f{%oQ(iAc#b5 W?e#kHԒܙ#6 _9'!4ۑ]>ז79(T-mxk߶O߂rZ=;ñ\F>S=6q 3W#xVW tmm26>{ CUQ.gZFݏ$7~ī{ "_js_L qƿa-CX<*31CMڼbDZЙ#Ujsa}ڻzŝ1{i b͌W~~ _5:nh_3o#U3I^Aݟj  ?~_5ucvݱ:?){e/W,`@os¾rZmK !ߒ8=}:wV~')8ml]JK,'ˀN xƛ1ȌL3=)n.oahUPF|8(&;+GMO=?־g#KN Y$Hcʅ{X?sy?+$j2X~5MmFS 6U rx>T&eFo Aj9+յX5RH(csz׊Lw-nu hce nXY9>Ы,WɯC4{墫ɱX.pJk*v=#2^2fᄞue y}ztϭk3ȳ\ɥ߻7f g;8ĚkV{sPO?=Eu!v0o VhDHqsno-I \xTT!^@@A__)eO cm¨|2Ǎj65h4v[r1Sl_|AO\4Cw"F3HaSS`W֤˄ܣֱ-l^G*ɴgoңV&@7e6lmR*GJ2m')Ʌ8k.8W]k-C宭++mRs}xn>-}q~'k?abS3mFb"D,p*Hg?3xSɟ/J5,u-6ՖY%3yWqF!pkʾ$_QD=({+oʁf8wav?q5F[LѵL|6UBFp@t\-׊5-:n&XV#URx%9UN2QZ~שKSmyy2XZV]ۈx71 b|q4uځFP%lbBPpʼV'f&kD~[HGF%oI|AhRyҢ ۳ OlrcqrVMmnrW:+X\Ŷ|~Oqrk:{Gu|) v q܊MIΑ(ۖp#ۏ(bX{uekqyAkyi6)̊0flUy'j_FQM[[//;.Y#TEhϿ<敢ch^d__ Q%fGKi7}V}31~1oUİPO'£i}hz\܊yfH渖CD.*ۥ]gkx`X.dndkcWqssj394oݣ.1 5&_mΧ7EߞS#ih|g>{+sM/w4pGxLi3ƹe@~cO^+Y+q`]F+oXi^7M \an|lS@2ԃ߸? > P|lYHK[y|UWOEp~*p^To * N{EJep6➏UrW?Zh2Ǿ0oИԗ1kvS?zCoi|i7|?4KV>d#=+^Apk0"ͧF!o\k's_SZ]`~XnrqV,8qiTo(9K6r;qk)P5ў[fE$k{Xs `*IG$9ju7H6ۖ!WXu?zWNR{#j:ItdҬ[UE0ArJuFft*6mݹ\]J)=}:hk9.*3 I]پ+>ia% .?, sgQ]Oiq,veTh dLֆ-b;\oqFc220P1ڿveOc_-Yr޿>h꿭̿ k@1qh,Z|S4-=Afڞx{3Fy&◫K~6Tԟu"_F tګIyZH2+ztcN)X%+,0?.ZJLlI2fs ^{HѳO,xO|bitk6XDW $dܶ=HiGoLs>G)>x Nx񦓤E $'폕G@ ^qۭiRh/b C mܿaRyNg[fۥw(hֱ\YdF0qăQvrӏ%$2g+,1+$V}3i/ um2# ӯ]#̲|,Veռȷmʹ,/5ӧS_jє/,Ɇa$+60ұ5K,ЌsȻ@ug_գmo vɌr@'8r+Xio2@:+_Ev7x(X&O2@ F6#Vَusw!X!"#>kX'ɽ7cVZwjkٜخ>:;Ud@+CDiZ4SRpts_-4Y(kvlfwܨX0=H?}+Ѭ| ^&k=VRGm mi$M˽\܌E8Ɲh?U9FK¿7j2ZbP} dYT{I.|=}|yjP1ʌz=~ķX_F=Xi7nK0n<wo_)#6|[dbaJÎYp0q_IE1o,tԇ'q|AM]HjW6K lTw$p5"m_Zq¶2[iv1^SlF,X 0r%z͢^5m.#:; X;#=7(q}OqŨꑴ,wl'hFa/mG=zskd]A~jҩ7_W'7\5Ԭ3IB~^ۛzԭcneV3Ddr[k鷾kxOkzY_˿'v+J7C#y/R#8 U ^3j>@NNZ:٬pѨN%arj+1 sOS^}IJ:u~]|RSejMk9V@v!א@鞸8VFvcl˴ųUѶYuO25IXͻt^ZN&gDVrRvsZO)=uק~ORgܖhAm0 xIkzcZ'l} Wu7Z&Is!댷~s]jqe rk0)nWI<廎;riמX#5/$ '=xҰRJ>Uc2nWqem"M봖 hQyIz1npFտ#cZlY#d )Ur@_ڟ]ւ~b#\ F:r]*,ꛥY8;xNmlIh.*A쨆jQEK*ʝE5WZͶ?%/'=yW4fxѫuw]kݿrel6* ֽzc]Imm7 I"CnHiqfS+>m1sŽ0`]4)'H<۫K.T)㜀w\&Rh3?1SK_~ҀryznH/L˖5Mta?hB9wRvAɭ}u%b9&@zf®~bG҅^*- (}m4!ݝմU11ԁ8nSE#:r;ũDp%O0D)V#br׮F1Wv5mjX8涿* Ϥj_l#;l|Yoq=/ ? գ[6ެ%F2+މ˨&pY mE?E늟0T5JoGbScݍ櫱j q^U #nY~je |#8ML:D_tewF~<,f RO_ ]\L^eQjBݬK*ae{%5wM՘.&B& \=^*}r7rs^əMh{AWR!O `%LO.}<.\"4s'7Qw 7 g{[k]6Fm7$vSǩq 7T_j{7\5x$n :eHݲƭ[f\i1 z2;/^-k7L%ߎEuK#|<FXjĿLt- %p *ȊYd'"&ˇzwz#4ՓIѦzqY*.3׿"zVK}$Ⱦj=Q]5Y6A/og\漋zldkj쫷vrI(ɩQEiEr~]hwe`d}seΡG$qYw;*a_&mr7˃}+[-#1yL;~W)_Ť snןҸs~(ݯ< h̤FsbAi4 d HfaG\{W*ܱ彎zM|RFH\N ߧg_xFjFc+*hgv>G85FauCc2LrOL{ 㜽Y2)Stյ8`e7]8y.O6$01Fy+ҵuh-c%XI`ò\WxB-M٩%ѨFk8I}t?_ 4Ϗ.bMtOy<іa9Pzo'9͟' |n!VHb 8?Ҿއ U^Q]?z9tZ=fj7W|Z?zjuZR waSGjtN\djZŦ7-zoVWj wr+xZ}1zgkTsdguq6;>S]8pݛ\U#j+7 'QdwMf"ϖ+8xRo 2c_Cg _~WĿm'Sn>"NTR7U-΅[qIcԚI3Ǽl,ZBw5PQNXPGAf̬2 .p{Tګj6#CVyn$kQon98qX*՞DK$/DŕTCz6r.=ki>X?-oyxhu#g{>ʾ%O"w*LiqUݞrsTWH$+ Gf*eF2: ;[ 09i%kWI +:8ڲ.5, c$Ͷ@7*TxpVc&3S_"P]Hk¤H6'ߐ?ݪ  lmA$}ݛqZW >º$wN'i7t oA<``W`W}~>nIT*[}2N>Ҷj}evl}v¾ҹrWſMm{M%(%x rFH?ZkLˎ`Z6f$߂>-RWkw(7ZyS⥝ѧ|NkF9 w:VZ>VF8}m"(Yx?wsV>cdWln^_52ΪJ?7i#!ϡhEQ\݇}.fUyш4v@h%חFs_Ao&Mn:\0%mN˖2a/*_sKCW},bTc8)xpZz圗>]DF1uϡ=j~ƷO{?g{H=O/C2,4gmsIcԁj-9u]_IyKV+H,:u#{]RRog 1;N csNӮE9 ;w}ELRXοgS]{A>φz?4FK^gZ I\:M;hV+nH*%C`ּ+A⹵-B=6մy|ԝ 2:]KW¾ {%7PcIW c)8g٣Wà6 s7qFd le+|ßbMtZz9?=N$)_<mʓF \g#~a.k7߾$2Y5ifV_)nw,-`If !v|=sbR_1S'5bEIIV=QVv_[s5(]}?/6б>&_»|;Csp7,e "rk=C';iAk8̰Y( ygs ~XxAu0w#[7h>HU n|U겹9OvECj/ U5m=uC %%sd`g? ^pQ+7M Gr>-~]R/.?پ!F>_z7u(?U/mCW53`יoͩb@͞Fk"["ajRAKꏸ'yX*FuIC7=9c\c' XԶǀҼ'y6tsN*ˡ[;YW%\o<_W?hlh^wOr#Vfmʌj*n,r8FwZHm?hG6VE]ҵ?i۟]18a^fclԊ#s>{S}W_ګRZuosė-]+!#}ږ9MNzvʄ-OV?g; ΢?oCbd]UBx,I3 ExrUM`k_Su @uqy&irX})A*5|{KZ*ծe[8;9Z6^*m _15p8k1izZ:VO3U>Sԯ,fMRh٭7'3o^*W#G?JZꚅδGOj.Z8 X nцSv]ʴo?-7iMLv=s$oMfɯ,EoY#UiPF63ry5>xokxPG>Z<$gq^YMSGc*K$'pG9޾%ˁ_- ^Y6PEuxd\~y>"C564r4\qo~xı$Y±Σ1bVgߦ{Wjyh#Zgqզ"%;mO:~yWr4V0O;1|iٳco26e8|W;Ǻu͗%%sԮ= VljE+6}^iy|:M?R^87Y5ڭY^4QM1t\#;G}9jK1,e[. Ҥo,[{ eVSߵe/{9lgMؙ!!g3ڶ1mi6cfݴ~_&F{ ͂6;#q2@:wKx,-®s^=F;bh>ѵ)uf[G8nױxgA47d('+id2t'QM6ƺ:[ۯ" zVycDcέճ@X\_;J[fЩq]ov@6abTp8w% ןSG =gE.Zɓ-t.?U|y] py}*|{5 Bm_osI:-GĚ13qW4k|߻e Xqo韭KRZ\9cG .vOjuƭ~;g$5JAG9)RT'>ݩƥHb\Ne^Q'įfcG/NxiGzTG$[>_m譶. ~5(׼Aڟ4漎?7M$'ĺG&>sI/Y|x6SE|Qg.\1#,dR8.v3mRV~(15K NԤI~IN⃪d6F3 ?rZ-Ռʺj?빞<8$pp<+5fgfkRۡ"8RWgkдKcMM;ƒNOoܞdʱ|1 ׻GJ޿>Rr9__<ա$ԋ%yx*Ȏn$}OXMagmYTlǣ`,7j_:6饝u8c*Jy g`כQYMV @r-qy8O/ᅴ&RդkFKͧbǏJ5a8?x6%mcqs _iT ,-  \J#l޼g$DiRr8eAo\{qyxe`7M+c늰fi6$ѫI1_=T[%hIo1dÀ==뒽HI~7Z]1MypƗB!w#\7 "9|Wn= c;teѣXO&Nx5_YZč"lhdݡ'-im73:\Ix-$[T[dU$`$m};=+ٿaWǭDJGYuVYF}sM|ӦX@ -n:+Mh4xűJqq2j,>fÔQ2_Z̞_ZBz%w>k4_*N+]_3ОIc7Caot*>1ҼŐjzĭka{%?$g^lxXmC5W{FgP Fj`Y3*OvFtI'zw<1'`r^IK1jiw9Rz#>.xQcV.>4&C3-zW\d)u.VWFޣ_Y}dz7_j/jryJùՏ*/=;nPpĜKfƗ4uz?[IF]<7 (͹_c}s#%{4tcsЮ>=>c=Uڐ$ҾG1>bxqZiv)J1rDC^F Ê>ԗѭvoy1'dnn|zvt:^6 `ziVKw3*U=+>|^sx ⷄ淺tI`9\jRx|-yڤ`yQj9hJuBm'%H6;oh_t?LR=~s^9X m͵}ojfCHz֏];\6>_&Pna|zv0u}J2nsU̲!jч4K@]٧G)۟Ao0U)X_?Q\-'#Ձpvp}}jy_sN i#V,o|iudv;sC}1\{zݤ,_;Fӻ5.Ɩ=h]dVXT"נb`.Kɻኄ†r~nxfWru7_Eo32Lƪs? h[jՍA>j9}X1[Zl stFFceM rJzRͱ }_gx3"F_u& G~i6|mZv ȱʪvO WRDŽ5 skm2OlԸڈc>`=Йym2NJ{oEWxŸo,B>,SBAfTQ]Xnog7.{$c<3eFt[XaMa2$C*t~#;)%c,qL@zk}f &Mt.5U$%E׆H巊oDkqWmŷ-@Gzq~_[WMm$.6;^)Ӥ˹3rKW'xuy b@w޸FB:~VbG=px{rb%O3ǝi|+ti5[!,̹b *GVrBl1o |xf VhL/w A WK$uEU;W=VG] / +yt$/;[wm'q$?ixH֯.|(niO& 2q˺̜4~\"CPC s& a@WX?9MHƕܺM2o$ <瞝1]WN+I ø85]:1u F(dA;oC]9֪fΈY3j KqC"V c{oߢI..mp~NW+<)yJdžW|9m/ exi F*QEn{^ֲs#M+KsqN~c 5f9kw>X*'YInQD?A*N#&zm5YPcoP8QNBQX)zu* $(k\R rdqY=WcZ|~D\ZG3L%s^]M'\3ES7u}>6Zm#ξ|J “ۭ}K/~j^.xkYs=\ư2/|w.|2_j+xUM[c.g2Y¡w'+?_MG/)O8澸YY]KqIRJnnn4soqZ-տѣVo Io18b#vWZJWL@dfg>8)~dм3[ ;;85(#ԯ 4V ų,|Osz]j6Ztu [9ZCh,kԕ5:|}/򶾟3RT/m:8y4J7Btc`.3eopsTYcimlTHcaK3W_J?cVQEEU'wϯJ㧂+{ut$QyּeIJ6ۥϦb=TM?_[vjj;9an {.L72찦Uw`u#,q4GJ_1w Y]6Ux< A]¨xq׏̹UKsHjtgiv7V,øSU%>V+sINA5Yﴄ3HLRA oNac*'Eo]|^^Λ^5{xdfw1+@`_J ஭ڛ]FK;qڿx N^=,d1[l?b :?|_?h^'./m>$8B n;H}P W2ۧ}tgRWk}mzIw}L<7U<׮ZmkRct6U@pՉ$IPNҦeTTd :d +I |+l95)88qW⥇7Yz,1OdXBVFx2S>+c̿+ ŽMA5񙙛cʿ+)"џzT^GQ<,8g@*GPwvcßJaI1?AhGx9#$tz; yWdik:kmC=|t>{{oz_MI4~XBsU}T}H?ofrr_ Z'%--k{8Io˟W 5,dY-o:5dAv^$LWc9_AR4?h2xеG,JݲS[Q_LkQ's/g=Ֆ^I#)Q^Ҿ4|=oQеEuq1[99?Ԓ\] {EJ߼=pc//s|ȿ"x19 x^^ 4 *YI&[+$* N7t'漺? ԧdnuKuYV[Gb= h +'^M$v*!kլ%1 <&~0"O/pƲ7H ї9ߞ1Ymg׹>+5]VUK;_:=0߻e%]H9>|Ehgo43̎c*T֚|aFv$xb亞L+x|L!$ǢNK;ZPqM~V49{ku/ƫ3K&9HIyVԌV!m⩼7vrT#ʡ*z$)&LʿN ܶs^7-"W9#\N~lUyV\J\2j+.*|.5HsB9x :GJ<ԫpxO2H.<[8,BX/zYC "zVW*w<R JMYt[VK;YY6lyW{ҪQBĹpRyvHsfa:t#R㍵,rJw>]N;}~U5ጟ+vd-rV'T@OSf-Qڣ&N;Tw. [>шcNћFK)<}9IȊUeWb[N7thq'#a]4)!K=G!e;XDȮ K-6ƳrnO&I̅#Yzn[O PDyS\I<p1MRښ=BOFa>6O4xucNҷdg#:U+ݣh>]CnM2I.{ZvwI&ry0{qYZv T};fQUq\~G̬L3 sL JjxfYrr gJ)sӴe1qs5;20#T x=*0Ȩpҝ 4x/jcךbyc,7L>sR tUL4wrhe{WNih*1+ީa [Mfk3ȾkNH W'tn]FABMrK[ݴvj7 rF_bB*P~R_Q#Mv1F[j++mx[o|X}Pk61@8!x_׼?ڠK3I",b]MF0kҦ/8)ӔV{|סmN,nZZ-cooq0l(VH'.KXM9LVRfT}2`2>xkHYѯ\!tX5]39|dta+SwFۣөKJ/u׿UKrzig 7c"K`2CON1kC@!kI հR?}I\ˤV.ep\߬n6BՙJ$WjxhamaqА=2+9jA&vUϵe>3iGG[譾]F%B،nU]=x5 Ļ6hfgCu*c m)rfWjV9>\}MC=CG\ɻS_)YJK{?Gʴzh,*A$aAg -I.da9,j~X}!i\X 1ZՍY-fQ쪯\95`Ԇ鯟O;Z3Z]];ơ W ig4:­/4$K_h|390AaOq|u#Y$MuT=:_p-=|U->;.8#=}+k-zy~^g01m%=_g&_d$U2ppT545lm\}[&}׼WxcI5Oe"[>oTjJקz3QºwryTl#eg­QwhC'-ZOOާ\gK <$]ǠG׆)<割os"oqyauzukyAfaYN# zZ[h`c\ y2jQn\qQ)+pM6:{A1.YPdzV湎KDj7-bryY PRѼOʬOΩ^M,fUf=eꪨIJUK|~ܻV43-I?_^2)?6ql^4.\~I1X+zc׌E73:w"k^,7tZjGcH "XnQ5Fz&e7L,òɗrTmQ_& U^-d4{RH.d \z˛S{}Jo<)k/Zk 5kWaTʆ#I*}gx\ !X FBrIVe޻xC1Age3}:ymU%1[>8&g"Yh:4L)$)F:^ҫ5[7^SEӦ˾Pխknխbԥvo-&h?:X`ҭY]mVA4{ '?) {wX_&ͽP +A=ҴfA#?,A1ǵy+_^}+/>: M: m+EMbr0ݲ9qKǺb8O㚆{[K mxc# .s۱޵ޒuCRM˞p=֥H֢M٭ztmgmWM4)Joo]u]۾SsR؊;rc\G'??O}{SHFn4٦TԠ_Oz.`yVa;̥=W_u?'qm L-Ydi]=Hb5Qdi-y/-dO.Z1vۛ[{3(m\~_ZĚlvùw[&{ꫤ$V@G^洙ԙ E}s秭,vѷYWp} 1nzѵYFU^ nS).kt >Ujx) {W?qt9DErCrQPVCH m˄#.v]vduD#1f N̷^٫Sq< 䚪ʸy9Fкrm.:t'*gqQ$J,G>}ha}\,ۑH\ڢrJewdrL?0ǹ\g9$|NzR=ܢX ,}ҳc5fYcWsyXS%am5v$mc-ơ0I͟CҚueiqi-⸲"<GCL4*Fy~h|ڮ)iuJV!u&ڻ6rcsrzU]7dkluZ%ԛ m">EedXW;>aVeUkҵbFڈ˚^ˡ{:O\ Wn2(415%qCxm*fmF ~oo||O?Rx?7t{#R4_-XI`bcW~Zd%|Fg4{ۋmH!DϗlcڽG_.>'x[YfaR᳎J96{%>cӮn.uo^ |#@erW g ayy_wie~Zy]o%/ 4Vf-qqFqdu޾o.$#Edgw+Fۙ22*Īͨjlm!.X]W|U׭o-w4@kX?s:*ӧ|&c__|khj1c?{ZwM/ [}MYHHniW gGoިGXK[N#;ySrC޹(#}qA\V';cҡJ\Rȱ+;X`S)]0uy1[nGY%3.d[rOK+΃ᩎM?<5?T?_suֲxU̙ Y1`pH33ۦ$TLGJ?wuCCxCOզG<%KE%1QpI>W*o*07}5=xUCwc-qu YR0\͙9}_ 7}m閺꭭547vBF0%HS$ |%5Oڎmд[uڐ-qdV18m_s~^"_ KᕭmH*O^O$hrF͟ 'RMFmnGE8_ ~_~퓮ziǤ>^ثԧh<֜n(0ܗZvЙ'8Dʲ#:=9M9Bަ|EoQ&[hR7a"^2;7xus4*,5Ͳ\Cj !CseӥF+_WOD|5jحuO]vmZ޽m^ ]&k;Y-ehay瑑!cN9p"bm5"mz)eeu][utҭl5Đ}{?22.Ez~xXyf'>8 1FQQ+[Y^qWt|8Vk6ݪ(b^$3j s$sD4.4}Ρʣl+9ʯ?lMZ"VeaקOʼҧ>]n+kaiCHCR+ug qGe h1UhltI&phul<}GYSi 6s *bZ2K+s۷_tmu-L`gPFi0|^#ֿDek1 nZۂs$QOz]2Gmoij^7Me!_,e{«6Zh-m3I[-U*TD߭*JFR]~>|)O]>ëkCڕb0!qkttIuRV[i}piG$lᶓ׹G1s7ӧ} <.c*K$vz~4mD ]#ێ*fUw?0+rjv5?qSO|dt2ORlpWݚmɿoz-5nSc jM/'m*YQ\qFtv26?Њr<ŷ4:,&UPrk]kv\1Hǧ_g]I4~a^O0 VscQ?V~i\´cZ^>5own|Gt? >xAnﯯ$^7ۭQif/*wVqZ(RИWM3 !oΐJ%i5WAWH$fݿv~U]ç_jf"yfݨY˖/CEN4YO٧]aHAϯ_v_ >xGcT9}3^Zgopn8tIض˷Jۖ4m~[2z}FG;ubd.Y%F1ꖖ U Le_2/ Wlj2SŞM9B;ڗso %[/ecC=sϠ -'@8ᆋgH˥e kNOy=(b!$sg~2vlg]'Zk=bI0<ŕ7q뜜db_Iu:5ӆ`h_1BxAB?,c""m6|qw1ꑊZ4XrܪH^_ď|Kx>լiSq@+$d TujQ_-ޟy2"f6!O^ <5_iƏ}yxAEHd] wq8A:i:mJ6J̆(|R}FKkvKo5w'`aKK6՝cwq6EdQwpt~*l/ KChZ6o;wޏ899?-tOەMrdpGnٮCJM6d#1Epѫ/R2;\2+:o]M.]Bf5{Z_G{+km套Mp(,#R~ Xi#C$ڙq_jW,tG>ɤx,guxn\;|x M7N=t%w<'*FsWNmfwjjRR6[i~/$ZCyHcJVOݷY'ŏ:_z;'ն;⌰9]ÿcx")D̙cnv?G?zwxOD_y62; Ȁ~=HW>+.]uMʼe#Kk׶;iOaXͪǧƚqI'7-T#h7ʳ~ҮǥR ZF.R۫ǝ/0^}JQmt4 IۑTֵS,-J>Tjdp=RBײN۷o=jJujJܽK(x8&lI|Kz33H0T}q39ET4XiW5>q}*1\s]pcQ?Z57͸S dJ+OJGAը#~Xv8˖Wu1$ɧ-{wȒ};_^+tc9?*$گ խ^$jWL7IER3-N5Y\S\ fʉ '#\wi--,@d3wW1&%C~Ky5tEY\0m{֦ZN@`-cfsprG5Č(19_SC"53 *nާz ܪ~Tp|z~f*Z^gm.g$T?/_ӣTI#uJrVYMݛ~\⡷ڸۛi9YFF~SQ'7- L31os֋xQߵ#jSʃx?vRG2`R6lo_ZO" 1|ϩRp"gs^  hŽ{E*tiC &~qxg|qi8|^-ֳwxc>gR| QaW^^X:thY6',U$5HY]]5m:5ٵ7Nݕ IsWk+١ǝQx~k*j~ YѺg \\_[E%hlmUdfg_¼ǦVzCŸsGC^;2YQu}:>vl[w-.qisە [ +k4:y.?IAx־-snba䭻se<2j[>8I,,EF }?ƾҕ YBѬ$_+OӟTEV.cmfݒN~^>,qZ4MmsD&B1}3jD*`o{5o'n޿M&{Z2}~$o,==cc[*Z8N*Ͷ ؍dH9c_/y>NuQg^[ֹF=x@Aj\+Vgan<ٚv `_yG4J4Ѥ1\?!;Ԗ]>y0O1FTdWx[@toV 3>xVMBڸ޾X8lEQѼ 鶞EVJ'NQM"F^j Wp9q[GMGOY`tWaxdž|Cj"mgdb* |.M5ddn*k.jGEq[xKfjg#jvtN>Q+T[;D&]ǯV:V[e T撴.N0cv!h{k׷hukbՉkh8 ~g vzFx[8漺O CkegK<[ $61Ěvj/ ;NIߎ ׵ (JOOg)UFxgX^C5O>cM,  T)c-#b~9t;|Y>Ś-]4+p1#E$WwY:LUޝ[ //JmCVv\N#I;8>\x[ AҡmBEb-%~<X<ӗ+ cU*+_%n4m唒+C>Ngtb.{(>վ.m}%!yV|6-nHy<7yڊ+ghjnuW|۹ }+d =3^t3ӿWe~N"RY{5kZr/u?A<Mu}Das[w@gW{隇ŽbM?_EXE_p +C >_%U. lN0_n`Ѿ..5)mb qp+jՖoY{4Z뚅3,b>GcKz3G4Yˌ~ yeo-{ĿcSJ_ؿiЯacb31lJ59_{>RW_/:F;7\Fz=7/wBNNҹ]cψȐ~a*|DIM>ª[jFQϧcMװHs*2r*Xө[m _:ޫip%[tHs^x1*[rQYRrw4[MNZwqWo}ZjVh8Py?J|I NcI~v^.EƊJ;w?~TqNoۦdo ßs21mNUVeV!gxkeOgm>:Oſzi K8W֞aROg]׼# PfQO5-2C$`e[z}+-wX?мhַǼ,8۞x?23:7Of8E=*zßTx֖ rYy=_c&Wrnb=wax:'l{=~iq{po5&!VIjy:~4rԏifR'yZ}?/:rL7u9;jZem%²*_p>q.k_OuuߥwR_m|&,\7Eh~4yI+_=[FIMN`Dܩ3|`U;M)$-%x_Ozό0!1n[h n}j_| VĈNzIS+KrmC-n-<>GMKFѰ q/1?jVǧh&[$ʀGkӼ e4ʬJcl\>Mb}8U@?E8=nFZiğ_C{>[W˫y'V8 CeBp8M^&[I Hc/0E~~]=¿#E7|=-2$0 |z~~7AFdYO.Yd7ǵuEԫFNJc^4)hߖ-~gO_F+4M3VsZO",=={!m㿆\ ԥi(b1b5ӣSiel}h~(Dhچ@}Ƭ,ƪAr8=5dыSvIny|ExPܓVko ^.(>ѫ[u S$qᠹSL-.HuofMh̅r=1֣;jNdmryۓssiоއ$q2q;O*2[-/}?^ʰRa%7?#' C[~ʿ-|1wk5ƧK&4c0`t=+ ~-/O%Y m \|KcvK.ydhyp3Ӝ^sM}jcВ3Z%W֜byޚ~:xfZusTĚcqk7&n6\qS=G؉jMnCq!G_ߵ/ xsⴰMZ^hk3A~@~SW|/m}-@,?z2p^/0~Κ?fO \OSn*Ilt,vf]ruоtnk[Vls \؋S{-:n 54wghV+̌ Z6U A M/ʮՕ>Xmz;Ud#+mHg8<{g$ZIUoxI)|K d\,,"} |/O5m#Tv\}@-ˑT6䬚__֯A?ׇMiDdn8[ƿ>/B.4{qW_Ʈ:]/ hxI wvjMG>~:͵Ҭ1]=IBqZ_9⽢">sO"q[vy:fI-|Yg4nG~4v"]E+jL͌n%U.m$`Bba%yPK4P"5':h>bVUVP̑+/NI>QQM|l3p?v4i ,?ܮ&ϙ׶9.dD2I#FňUl.QI-KD5G4s$%TkDnL}ݶuYɨ\ӏ'ruBzONMrrbC;Ie:R7w&jWqq\#|3FY"n.JjZkjcJ3YrF잧~vZert2C˜8r{DZGT[{!jHfйBX䑑w񷇴=w36+#'ܓWſ6~&)՝l#W +<3֌e_z'++ dhp䝃]Z+Ƿ?i ?_ 4ƭ7n} qyg+'2ts_>Z%P.)d15RSUakUo=n5tg⤢o߇ GCiv]ԡTdoeҿ~;af`s$w8x~zg |P״&TފO[H -I"k]풨ILny}꒗Uk6+ۦ߅YmA$A5|VkPHZKcɭ;-r,IIp0G_zŤ]>l]\H`0=}k2udz{8;:^$-]_hֳ~+[NƑ Eqm4,]d,|N'x~mk1ts%$gTd)=0+খ_;'m6I"2[nb-$jڈJ{ ":_N $[hKj.Iae-g Yź5n\կc5{̴KnbI$^_R(ʣoV.*8Egm_=tOXk^6G h7o  s~u5_ꚲJ~ L>N>NTyn'tL6Զ1LV?_-ᮝkq4֫c,sY+`WZNK0e|i[wWq:?iJ^PGJѡ'q 7#4 , [{gmk m~ |4!t]?YC  l}MixxWďt>m꛱qǠ^#0鯒Lѥ̫Ejn_#/;E2*1A|eԳ/VɷhWr>]yFuܮe$3Fه}kⵒVjVo7̎5/# aA⾳ajTWu\.3up۶Wӧ7k׶j x6Ydܧ`kΣO=fUm-%fU-WI;f-'4A*v7g~&0h:ntۈ2vGsв*sΎ#-}K?5}Lܯ*bj}dFo+Yt=Yu[i,|y-Ҡ?x˚aw|9klBNz=z? o~l,٣U\'ƽU)=$K+BF$2289 CqwۧNǽJJ<[Fo<?,Sī~|o A_x돻;}a!~!~ bqok4y:utEo(t6G|7+g?v֛ï")J7mϖ}oӼQk r(P+5fWI۵*w^qf ,7v9# 8~?s+6ڏCsz|\ ;~2>`JJ1o>BI]zo[oO㇀u]\[4?gF0koeSE G$9}q ynWtpvzgh>)k?{g"m맟cmtvXEї9⳴} zDZ|3nH*=5X|5h6!Yurim,gd8ln ⸿ܟ>|iykNV# ÂTtcz]>wN*R~܏x׺e?BϜ=pIjKhn#XC-#zk/^?4Ɗx0 cG>#vpK_*/xu-o޷_ԕfoڃz7u]kItt |WCkŤ|CDuoxv JGcH"Xa嬙TFz׀~аj6_Eoj772`UNv$zZӗ8[V{zT)Pz(/? '{/Z>xUְAYo>BOԯt1R+_'7|;ئ]'G"=$f<[Ng;5FiXi_O 5iu+r;6'8?h>,? %G[ɣ*b^F~+A%>ݴm[TT⍇v$5sh~evMLuay):681*߳~)$vktGq A΁ᕼKTSv>PnZQ/I.?.e 6axq}~w_~0WHiH Ğ\k#Rkz>4(/Ԙ,>cKj~ZO~ ZM-njiv_W.}N)#i 0CU5ڊ/1ּPcp: ͷ qW$g|7QVֵEZt/ !Sx\t0=k|-OWyoZrj\q{~q9F<}3.^iZK㥏kQcIi2ZZD.{_~*_xJčEyo 7WG%QE/^s/ͬj+n7%m \@zչ> V:s|oo"[pE!<qnsJU=W]dz2YA[9/>C4k~.cZypc%%'9~j__l'i6zG&9^kb4{[zǂ>xߵ+[?WKHI7.,~>Go|Rݥo(͖ʒ禚uJu>^PWZ~oj4(Eݲ@m(NAc$u¾4kO6~J+xmכ1`Q#*kį[|d4f=3[Wh,A#= j_'|8mgdH3mmGX)5Nik_rѳg/i,hZ KZlN|#csCofXcL`@ 84o.$Y$k?D1q_@\ l5sG);YU7ktg"2ʦ5f'H⊐嵶O]5鲾w9-W~'y i7'_&;ME֞V5 $ G ƣ/vK#-D)?go_g_!Vy SٓZ@I`K.UA5ُmwyuQ՚.o.'Cno u檤bG߶kw>TEK4V6jMΦAnuCQj6Ѱ yỲrn[Au|DO hd`X/ϕeC; ʣް١Xݮ./gݷ>o'foLwV9xUT.o_JZ]~or ^V{ǽF}+a7 Ao ( Eʎ棰[4_C|w^&XܝZUż ܻ7{UG[.Kr%<6N0:l릲Ok) ~5cZZ̰ X#CLΈ^yǽ8M_wv1U.-4Nk=.i#Ef[8Gl֮Z0dg`EfjMgG-n嵚&z9Z/9M5!>dѕ1HtJ\ܷv42w !iۏqSCh dr@Y9ߊMWF&ԣ5<)p ݂y`R^qywBy PK SDRgmmȫYG qjۣ9UBGe{}Zݖ}$V<`t-ŞZ;4ϽxIzR׺5kx&fB+HA HzTKW_*1OGާ756.qo"Qgjg~(\ˣkw6pV7h($}S׮ZlRq+~>ҟ 2o k-]OOQpnCK(J>ak\ӵ nuH^5dbDar;_l>)(/eKo4䰄eMcpx[c?YҵٵL752wDG8#>Gݴv۽|So<煾=x7P4RZ,rHh$q&{Y+cM6$ƿw񯶿Te_i}KImUW,H<Ҽ7<9xU%g#n"g2'.@k`5nlO/\1MkKAS+.[n!]%[9Uv([f~?LJf/Z+-Tw0l8֬[AMV;,qӽ~ x+׋h%O2vm9>nپ(YdX#et *aiIۿ_c.YR]QmNX|d_.BRtLUYYɈZ) ~\ $)ˡkԪԘ.SJw/wi0w&fKYZh. FGk 3|dGzJcWјD2Z;ֹO atF7}`vSy6o=HaXô.[ ~zwDD]4i4.!RO[re:W48km}٤cSfo ԯw$p@@Ǩ<{@}sMu2-<>^Ͽz4K~4^h2puƍetܡr2:Qx,gsJ_2wMѾ*#FM6ŕqqڽg,ׅgn}EA +jK}> 5鶟yiB+_>B3勾m1^x;xdg[]JţiC0 ~0j2!v#+__R߄*G|/wygy21lm9cvk^U.[(`-,'.[=×嫿T- rvD^gGĝ/ſgE_ vme&E; %MQ3bFS7J^cQWKOqh:'Ŀ7eo?0BsTn?1 ^[]5?>s=$Wfܜq|6yFu=k[ѴbPΑ\I\̶&eg8$ W◁N#t k%è+ƞdlHʗplC;e^׼iXG^i:Gq^/?h}Koďzk5;{=oR=7QKZ %NV#7vwO[^yq%M[iK5gѿ~ muOxXHev$ç%5"Kath/GHֽZW6>/x_o[=BkO?qq<pv8mx]U͢Z<?1TԵSEwcqu'X|S uZM%y}|1<֟ >0|acߵѳ084dG(*rz|j _ 4? icn+'RL.tX7pr iߙFK/zNxhrM[TޮcU?3p)RM8jR{ 8idt?22=}k:/6^{XmmoYc~h$SFrUb8ql*--u8K ɹczd9Б޷7%gӹ^Qv?d~!HtQixJI![ֆ 9q^YxbxBIHټcj<$([3x3t=WLGX|u5(eo)${-' lj_qA9tSO RW僲mS0m Skͬ|K-|W169ܽLWƟ~x -cj_at[7:L2#$p=+oD? |95Ԍs^$C#r:Zko S? \5&3Cd1;sw9Y[_*c%IYd׶5o3O]?i~ ֶCp$VBWR@^}Z:3[kN%kq#zW˟~*~~k6}4اJ| #>lW~}MBW&u;װ23BX?:ymh>jCtRf{jvV]/;{[ac&zew(F9' kGxZ&] i/MHaVB*g؋ .R&ԭtZ-’ʯ%)pĂ85?I|?/WMh xs 60H<`rGTݯcZ ]omk(Gk5jb=-J\D,ҪDcg#z׍5o.#-ykk GgjX3[Toa?Zמ-kOӚG3!'nYLmP0v?W_ƽ[Ycѝ[i)S2pjR+Gws55eޛ,jZ>c>?zo?(xYPj$ C]͒pq&?O3<7RylQj74W$/*7E|QD~՞ E|k߬Yibp̅r۲˯߳汥fFkȮׯ9ӢBJ*U.w9G7uN7T{iF[mDְ3T4{@t{0;k'?dI|Egjֺֺj xDVg'|!; %?|%ৃcѡ\6p=r_"YT/0xrO⟌)֤]f^i ˦{kXImo*Er'֯g$kmZn^]p3>܆*^De^ty.$2\xHFdm3$=O˒0?<9il?MUϙXsS܊j>!iSB2 āT(8LdYU]M .#ʎq3n5oЯgNIm2)Yۑr?:~|f*޵$2%V Á_-2嬒l'#+G$ I$k>W=xgV+$+֝m,qF%9Ȯar{ooyVK;yi_5-Oo|5BF־7^#w1&QI= _Q iumI^<@ ~XP,#98f贽/ޞI5Ρ%`Y~̛39𿆾+>7OIkym|tI\}6P\ɭty"l5!I1ueWG.qjwLUXTnK[]?jx?+ῄfOKwoy&( #,ʖ=95|)':@u]uuil]b;d 9+ךz]~[k&tC4Sy\ĂQQ@ڼW߈?ڬRMt*m2>a9ֻaa/]{9\ޛ;tN> ky/t˫CjXIe۹p3e8)_ 1VPkϡƄtI$]Z!%>bB!\ hOa$my$}ͽƑu2Fb-@F+TsIkX*h7_xKᯍ5 vTWƿ RXn ǹW_D[ϩIq-Mw,e aw)y|^k/^i$ݏеgj?Wn젵KHtYԼ"恈e,y~b/y~F$dc|kƞ;. FCyr( =5{46dV40\l$E#v8'hLr-:;iV!Q}'s6UKQHc$£$ 2 .p+NֵKsEz#gmkPt.~KQ1Qq[6+Zu{MRP.IcזE1UG> ֯5um3OLUS񅆙t;/"fP31w\6Gm6c ⏁n/#6?5 .Hc_֍I3nu+ a`0*xWv|+q2",1*Ƥ4^koÈ6Zvn?O<;oAu;{{]J0IJlڥ?ŐGkbIJ.o鮻Y\.v+ld"JmG,b˴66v(Sǁ7_IEm)Ӧi" B2B!RFC r |}vZiϪcn=kvY\IwDmԫ4˷`:#kj/5*_^ҡ[@r*(.m,~ođ[kZa7.cˍH݌!hjlힳs!L!lsàl2+~b'kyS4-Dz|-P]@ˎGݨm+Uೋ[Լ¯AnȜW$oB>cw cv7Ն7K#":d!v~n}zV[ϥڭ.-/'U[[ݯ[;)m^FL7ztfKeH0vjvzgh\N|']Gj~k}IM֣q‘4JЀpޠVK\YZgqtB;7 9Q꟞Weenu.Q涞Gw,6;O*uޫʤ {5S+X-1*~נloWKi7WGPo#Q$lCw0K&8v:)$[r!f\*"92wZ}4ort=Uzw_4){8ش_w g/*YѮco.gn6ҌV\'1S^N RMjk+%PF9f50Nl)[k_xź1ZZȶt&i3d?uȩ)i[mvTo"- 2Oj|*R@q(;gڣo0jt\Mmꑟ&?*FN7oou_Vn[~O2+xVeI0o &}BkĿх T ԰nA9w\Z]k7{k.K+}~I5-CԒ-U8@R3>+ w{so]឵gkciW2*1SkG΁>w5ǫGfKɣC w/l`\ f\|D,<{F΃C`dnJdž|gSE[U۾yMr&_+~$?zǃ|M̒Պ3]: aYtrh"5[t8)#%޽Kh/x>~)Ž%gjH`qֿ@_gQ7|Up믚e?g+?fiƗWwZ+30XŴB=/ As_?d 7Vգ4,^ʈcV;s'$dUS呗/#N.XLt)l7voѴcacYvN08 h(3G1L_EˤhkC\I ˡEٰB(X{?[sIncuhuU+J~69HV+:cer׺[nc,>;V[vW?)4cjhmc[\nQ{k>x-lo!X&ha1l#z,! n;}?1K P2Fc.Ynpx`iY½9q+*˵H$\0ixov%X̷᭄)+R~k.kE4Lok47Xx*0z vƚbo>'Pb4]EmDf#y\^Zj֭si~o(cyUQC5.^Vt[:o}؎#nрd,n:V0֒ ?X< ^Nybw筗o3BD_۵ ck%[ \GDZ'- “Cn,1]yD?t5o߰]NUl66`U٥ aQry&VOc/`UO>Vh u'b>q/NT_SX8{s;+|ۮzzƟ,qcQh# Z}mi.4okIYK bCaV_~49om|=85Mobjޟ(,?h/EyV8hlV..&l qEX`d2"oc|-CoE޷H]鮭/fq6#IмuoWQ= )`96Y4Ps&xRIqZZ;o|CmƟO;ZV捚6,s#XzWu81xvkI?.;C⤎Ze0e:U?w4}n{0s׾o/nǃ|a'C^\ȶwM"8~ /l$& aG䁎p|uh1׃io&[ m[9Y? x" AcwZWݛjcJvrTsµ<MZ-N-lG)Pi-4Zy]IJcFV_6kn8DW>_Gt_-帺Hff'tlc;˖M6Ww=s 6 |gЭ7kh}/+rC0ыIZ=uw^i~imn~k+]⫶H*I,ӻ86Л?&6ZO Bk ;cǙĐT,2 q Kr"<@Gzۈy +H Q`wJXwFi[/_yե4J rrO9_6,v7: XHRV{E)S־Yh>f#{(f̑mB7`e1n==7P.eeޟtyz zskZ? 4b0r w?Ŀlu}B0|,ն)YYi@hlBƦ_F+o|R\ahh_-[|CiqrxvU]|68@3֨[#iՄ1l8'{3ާ]7~ vϷrl⇍t?_]ťitDq4^U 98`:W'$(z*.J伿c'̬o=VNO_ҡ]~5Vx^$|-S̉hn ~숙SU pxZNzN ʲ[n#kwd{2;fP~%'H鿗L92zv˩bZK^i4i$RsH\CԆ%XU8+? _Kwwy+.k|B}-&vy'K k}|>m;8g 5s!NʡO=xk]V_&qVl3~nc/)wYbn4ETz{^|, MoZ0X\LW6͛nr;澠-u2IA$-7\kImż/vJrm%P@տcڀxM{i'}H_b-QeNL,˜x=oKGfث/aky7f><+>wz4I^jexlcV'#Sڵ4O3e-Ҵ>XmS(>Z 5ŷC׵oxvr^Ic0\(U\&(!!d߱q>Y|\M}wm̪ەQ`k#-$֝?<\0IY߆v !+?.m5[B) OQoCԣZ5-r^p &v1y'y{ſn'O"ҡycyLLa"XsՇߴ$w^EX]*{wYeUEv+#;lP ̱zqg~dsj9'??/K.%{5]Jn5 cX,^Q89Y?}W5xC+ê S,2 `gxcҴψ/'owSEbyXC5y f'㝕΋h _-7QEHG2.FT2jjbpKsITU%E^I/jOa-"v 5 Ŵh-XW$0D!@v sկ] I=ok*y6dI"zƾ¶]ELxc f/m5Ҵq\ ț.bVAIl?,ӵӢkCg44}&[kU69$1e洪/ʱQ:T pH/M{?|,yiycu~[kxsə>#|rkϖN_:%G68ݻ'[lE.4U-y QV+1g6o:vv5ı@v p0~%w|Kk~׃.]d6{ٗ"#?E7:?5M/L}SIH ys4 0w@~ce#Q/].ڵ۾^gjtJOE_/tzݞhz i$EO#nr08U%\t4Z,mA=WNeINe|S`lӌaG~jxkSom6f#Y-w‘F.\fx[4?𿉼)oF ZYi sy g IQ=Ayc {6m_.8t^SMJ>X >hKP%p@K tN'Gƾ QXٴvhf e#<շ-dΩ}Ki}5v/(.bwJuO3_/ďJkXg8ULҝٕ)Fr`T׷wJ 81N5%5nnկn|iڟ;]%nTl\Q#oFH銫%P`ɣKXv,c < y}x<{_|05xF[9)dR:[KɄy$E/: /_&9"ju+k׬EE@Ee"S .a,f[8ZvԚtsi54+uO+j>|K.%[œ9+ dvVzxrKHђf!f Q@?(T/} uSǚ*t}nV(#Ή3aU+GмokeK+-"?\D%YFyC̰U{t.rqSsj7]Oo<:i}!$x#ݰPzq"mcRN!YP(HYSmڥXbY,5Vi#RĜHr+r=nߊ[=%7Ko?cLƭ\@6P. )9Ny!ִn#:jC*d7 C~~׼U..F?-V R"m d**j`~2e].57H:"ݹ#k[3|.w*y.6Vm|ӻ;_Ho-%aW̒Gv,6J}f-cBu 4:w%z4'am(ebWku+3?o["Mxe190)e|봂5KOi[uPx]umEM;1NqCѽ寧ϱrZ6y[n|#.srZkXǘ#*r8`7'蚂)ْ+3 b*aU>g y8+gOLش:o".B!B3刢ڪѳ*BϿ*]s ˦--VBRN\]\QKo>6O%muwZ/sq9l#Hq3Nv. yr; $uRԷ}+YkW`c%Q8N|!a-<gkc*ij^rIb0 BLHH-$EI]1]跂Rkz^3攕~Xkr:(_}W*Y )?(KRmtz~{4xXx0Ю&}y c34KT! {>4}=4Vf-Bc/.rXr}O/m.68le;["FȂmɆn;vNVHK? Xa3*P /rk擺__1tuM'Ϧ#Cx|/hvb.?7 s#+<*O#\&157ZnWog]X.G2C(Gx*ܐAZ#uJUEfk>95ɦ(1:]r麵ѹ\_k-լ 2brKK/7FDs$Qἁ~Ӛw.5}KYzc{hmw}楆7L8ui#VI2)]N$ּ-wZh=6s\g.FS41C1PBFzGl#—l*]sG{I!-8ſPsS^Kcxk[bIPd>ad/̜wdoFV}7}Duv5}t!nx/OoiPܭvZf$9BY(. vzS^j6rocwuOc(osTjWjբm P#&WY7u a&O h2i1ǨJ `n`EHGaE*mr%Vm>V}.eCiiykxu.Œ[u!񧃵|O o(kF9[Xdu%zu΁.5Ñaoվ7 WG1dXّXؾ<}-2XlܹYvL-5;ZY4X^[9 EJ&h<;c}p0MG@r i,7?`]y|;Ao`IA w\. EpAyd՛55_=]xv T6V,b#Vzb 241VNBJ7Ρ}sQ5(k -|elbYtguH#[YaY[,#2BBcxZ}5ߥF*_-4w$|4M{5荽0bynA>GOZ}GL}zdʱ4,22vڇbqs0WVRi%-bDj*B%]ႾgJ񅟆C:WO42-U$UU3pK~{?/V}P&]|34äk}}3Ic_ m ,H!. Cmp=1 &k4kPmɆ"Oc/s*)ٽpj-:-KŸEoK;u"R@zcO[<)y5Glx83ٔfy"v 4JWTƜ쯭O޿Q+;kw_~nRxwOXuQ[#6p@ 嶱ʖ#u-KPWC}&|W^\0~d%ec9 }Y[Z\xG5+ˈN}m8 ShXȑ%ExBI)<9ĚҵgN?.P4HFv|gi.ntf脜ݚkHR5>x^Mwfw(x޻?,XK(Z+w{ d^YǩI 'gIlɲ\nnaV|[x%<f+-kO6- eQŗ"$ds|Co sFL^XM`gY|*pQ䆬gmo5|Nxk_ZD\Gwr$;O2>`͇ \]DŽ3A,dHI'XI%&Fap*nqaMѼAA{Z-#@KX3yAY.@R ):r海|mVaӍ}v#ѿcٶ}G8KYtȊfeeMeeg`Kq i Yg5+ 5ۏ&$9ܱ tMk:u]15˫FKMsTkLP1%c(dV_baj:uQ77KC-6lb;tTV]nA=)8meTw]Ӿ!P.^>ӥ\~߳מ+j(Xiw10_cpFEn5 K7,o-qݖ6XrXH&6p+ӬU揦u~u!q e ufܨ0r| 7~솬]iTW(4[9&O ?ӼSj.0QjEyq}"nĒK&w$c`NG+.}7RV;8T;v !S1ʶ",yK}5Ny~5е67W]Vp.0Sԍķy%$UY|/˻O fx-O&$*@67ٗ@>ӬoKNVZ. OonH1;@Fje:}BG.(MBO.m8MY,|UVUu~>wb%-{^D6:6.},-[abcIb+c#Wc"ӭUJš~..tN##s xk, rO'yo.7}(6e Fك2##/M׏_ i3WW{i 664gMB_ycirT+o-u泩_{[K#ԥuQҬuAgc-嵿 9qa XCRA{m69 ^E=~̒/++;x>Rws_,|Ue%ķ>ee@@bŅOxC[que66k')4wWQ#pܿ(Rd嶾[e9^*;w˦ohZ,|#4/7"\$%6By~bCC^ rx~"I{9]hb0`oRg2,wvu:-HV >dJ9<x^ּ]>GRk=5u\IoB"Vˍ)T"PRJOTo]䖆[ѭ5]][_>2-|ijrirXj̳` m?#-y eo/ \½6i%==tCE4 GD*rIJ*mۿx=/OM7ex mj&&4d ?~wOK{KfX$MBU bCsg׶yc*믪߮M*Q͎eiֲދ^D{vNWAl5\>tY- *%6mk#2aCV^uwoľ"ng'׵*Pu}5Z~F6ڌ:5]yVR(mmt-͍ʛ݋3$ {X?[3R](Qp-0fR{iÚKijPԭcܠ,EfUf4-VC= K%ݻ4ޡKet)o_]m秭f.nT?mo(3}'Y~C1Ey]Bm쭭&+i\y[W+j1j^`/.u-?2-3IňѧD".6)Sk>=֡g"hw^uaͼpG7pJ{W~gVGII < r#U)uG Xz*T${oekon]t{:7viwyGi,~|K X0xCDOɗef8"w¾ T#ϫnX<$܃N9 |R5&߆:<>5ėVTRuc]$ueSRu}lN1e쿫^{f\xHuPJAIܬkR4Z|-ɧ4txuXxMղLL6ʥAfDvqf[aq]zEVY˛-i(vlU9ί_Gh᧹пysv+doToer1QDmpzlek'gr3mumߦj;_iz}j$E17܆b6 p#/ᾝO1.BI46~.Ԯ{[y;%CL7|9 }.J[~J:yKY6lGg/muur7ߚ. pɅ*S6r+O<7i /uH]|-'Xљa.cexZ(&]ؼz}_/fRѻ1(nr9߁~^_Գk>*Rh% v1&8=.ooWJEyɴԲ)B}9J\_[h-/O%8ũylYt}]*t __z^kjZI>"F[a-R䂌u 3Nɴ_cR}kVJyt˫Qekks4mI_4# FܖsmjZKw4zԒMqp6|qA<zFal.[]d{UH, }U䯻Ymn}5\#mwE52o_Q3K{$((9wd#H*Wė6ۏgՖw t&Ua; |p9Ufž8_OM'IGղ$Caqp~g]]5e~ڴwWMkm]pXx[:M%\v6,D1HQ my fuYj[]R OX WF49PF(#mtMԚgjm#rmcw  Z >k *F4BēS 2[߿w^^3wkTVs7fz.Goyqli0A[|r8 xBU:-c[i4iUñMM5@q骲\õasFef;L5FB4OJXua u#]A 2}̶`AsE(M4Me߫+rQjkvݗM^ҡE՚a2%w]αo-CmeS ~ҟ>)xv>@W0m;TySխJKm ;򂄸kd)^Ͱmti.-;^Zlhl.D,ad #bB_ _h Ue^Q\؏\Gz|Q^Z~-x(#/z_䎃_7ו'Wz(7SLW_)oxgm_"euESx7gM{֟>*;c(3ԩC-|K߅Uӌg\n[Ezxэ_࿗y&ocо'+?.&b]yfO(_ sTУ__iO[c$CsV׏'%W_#ۡU  DW/?5'Ҋ*}_!b7Ң4#V.IֹzI([/;b ޗZ=u4被b5;H|U6կJ(]O}i_hWX~.5TP_ u莋jb3蛪(~N|R2a"<3#_cGMEX?FaSG?KBZu_Ph|?3_T#tp֧H{PEkST}We/ޟMהO%b)g?Eo-]ebK?ʺ)-EuG^zlsIhk՛%ڗuhl4C}yWIB JW-M_Y_YԿ? ǿLQ]ox13R\|-ƭcEz(9zGJfXG_iu/?K>&ŸuEDfC~ݮ+#~M+XȑSᏯu~ ?j%qW=O',9xSoϢR ґ3`3F /8袺2 g{ou+EWE4sM?PwV+?v6:ke.?޹]v$.?UV|,?G/^2]F*7vaJ/ŸEfh7.O*_%H*>j;-'@+5?@|R/lok+Q_ÏraW;ß;k چ|?Ѣ+H'mlc%գ4?&ڊ+ɘ2_R-G?N|A:J(n"_ÏPK!&8 xl/pivotTables/pivotTable2.xmlVMo8/@vlm.ܸFvό4 P@R,!Iog73{W=6BhpяJEGޯ{o#f,/R.UALnY)VBX0fm-ql-\ ɔιPobSj\~*ι("V^Zˈ%87)E|H<eo~tzƼV={ǭ%]ؗRlάOvfۡP-naD_v@=BL[uD˙cےhOG{rkmTr b*cyI\jS=Xe93"M\cOpOtp \ 4{iA|1!-67z 1e`:f6#xj΂GiFl Òy4NF6|ڽ -*DS {;WS_ NQ9_NU~yN3 UZ/YMo]#2 E1T'8G>6aE鮁S0tˍ*w+T[cUmr5r%Qo~=G{i=4H2*ce"ӖpOBSgέH)t]>ķvԅuJuB$pxmu.~PK!{U<4 xl/slicerCaches/slicerCache1.xmlMo0 iIh6M2M'-U"s[aoȗGg# 9Rס2~WQ J+c!OHrYdzXo<rRPXt@3: utp*5*j"BE{䬚s5We(4٢A,W϶1dƚth3)^v>DZxnv̓cغcݠ6A_,Bt3 -@{7կ 9~a*$t$th}*CyQU4AO 5Uc֩d|@7NA>PK !I  xl/media/image1.jpegJFIFC     C  0" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?U$R#1@62ORwH^%;ܵQ涤+hZEonhjAyg?2T/4?>J/9eЌD Jn*_'+gruZz"2 8:ؾFjCó`cJ5oQն"{#*ܑi$caʼn%ҹVX+ 7S.nx_aX>Ռ2!}=h) Oo !~n>JUIfֶxW*R23nq]1~KB!]/\ԆTsڭĖrdæzX]<҅+-ŎΪm n,pS2{x~W֔ *Ws߂~^/OyjF.vU^2Ƹ{M*1s3p͏^;Xdܖcɭ/F%(<#ԞD`d]>*ŭ zqI6=W,z~5~͍_1gݿpeW$+%Ȫ=n˜ R*RO*1zT~Bb~m~/ʘhHfǯ\l]'+ӽd˔2Ƚi\ҋiO^=^{6]>mrmuHmCInIݏC+4/&޽wA ܯ4ی%GrBcBU2c隌&6W#&OoZI,+|d+L|ibt][OuM5~G%rWx5yӐں٣PRŭ5SZ9@?W_/;~ӼKe fC2§AoCRնuNڟpx\2_iQJ~Ǻ*6 C;nS'QWh U}=xֹdjYs^ɫ*?[9]>Ώ "J֓\yrzu)N&s.dyC%v5t?/AV;ڐ$ ?Q[ V$TXa9EŌ an?8H H ̹ȩ'SSfi${H9(AF̿{J[ۀwb6(BIןUh|]1T[=̩xK61 ~Ut4ox3_kX'{<&bWMݸSǵex cd?|S'BUsr] LÞ1VڻX WAoAmc|?$p6QV>w(< J1=--ɥ,G-6di,7*,z׵X5~Xmj;hU_jZ$zv:/QUzՖſ\{Wc#Es#i7yo/p{h֧ɩSCؼ7ثdKxn^6ʇk⇇Zò\.2+ɂJ{ڞL9oF'{6ƱX4{o?3Wyaq gI;gġ=qҺ/]:;2ʹvhs[q"']Cy_M eVvf"~Ѥz<zPO'r~_JvuUt'sSrn[<\Ʃ)\HQJ19zUp剽X4McϽyKyh| rNkܺI+&_eI&LW2Etc5IFlү7g9tU.#eozu4QG8*~"jJ *p).M67U%GHt~fTyylg\HR-EFB-FSUv [Z=ҵ*%U fJƉ(dz~ӌ0 9:IF>^ҹOpABУ.vM* 2U%B*r83DRwz.I'n|| '1\R fN\k7K$6Qr~fSҶ"Giqˌ}ew/Y .4xm]u H~2v5Cj Ak\YD![ ǐ6sl.b9-޶ROW.Xiy[:1[I KC#yudq80 $Y_yNbARGnMq ?-קJɿI#'-^;ƥ7FC$5b8n^gr6xesQZGN>~j#M~S/1jR4m_*V-9[dgq􇳸[9a&ߔ0ݕ\Vs666qӚ 2÷4_-&Z[eV,E}ΧH?R BZKq"ey ?[Þ!+}k*T uG-)SXj:3 ࿶Z}^H}qW> c掷qXQ´}qzm]jWpù@Wҟw?g}J=?7N$Xdd$cSڜNU>S5}on >&c*}3ڨ&]jR&\ToӎN._(:fvu_@ޕڷ+SP|Asygiտ$#|EU}47nl[K+03,K~Tjw; uqHfz? oho?kUTQҼ'P3 Ś5ӵtM]ȋ9 =k菄_>;6=wڴ6mpIq#LcKP ~ϾX",\׈qO|y>ۄXbc{aKgJT{('¸t;62L~uSwxk=/c\qѿG'֩wZیI&x>o|El֪#pS6uƥG~f>KXl>+K }=kQ|?sz͵\xɗnd 85k7ƕ5¼220eI^&"{@c1_f9r(o 2յZ޺Mk x4\Ur6~^Z@r/k+|1+F6|$ymU!ɔ|\k7ы~^yjзr< ^ӚY9 yǥO;uR}կ^{F7nZH ~} Q$cVQ)hzX,?37$F3gJ x[ j0Vw|,Hѷpz`W: [7Ƨf6Q|=qQXCT; 2"+ž? ͅ zɨq#ܾ^>/%ѯmo/&%ہ}LssX8@vy;} ݕɏӭMH`{dFq[(JU3jDC1mpʿΫCDmkPHSsuj(cmvXq;~K>i˞5*,27&{TqNrܷ'5ybb&oJŕ*n,bGhb`6K3cR?1`9a#-zm+gjw ҡkm[c/v2y~]y~"[ 1 B~pgjyp{\_hFG gOq5E2Wn2) _x[ =A'%x-j<*w#`_GX#ufe-ڻ C6X ;u aj JX~ }+b~ū 5iZO/ s7w\׷Ѹn{cޫ B[#vMF[O32^8'Uk}gۻ?\u-AUe'SclLrPȪR +Id͜K)Lnv֥D1?vw|6ϙ$Oӌ R,{>gQ9+`H jE}✖2F$@LE;}O"Ď[j~##6q"{SB]yNUCJ_Z|Bv2/͍xV#ŘdF[Ӳ4ی\잭G:p0e%îK(4k{w\3I †n/YXڥOp:UG-Ʉ~\}:zn&N篽es9q1g$80O5)0&Hc"F s:-U[uavmmTτ-^1[{2g' B3P_˙l]>.)nZŶNqpc'pU7c"Wrʹ?:¤t˦xF°?+4o>!CAqt A'k|wg?|Qj.~AV :;p| ~f[ ޳z>> >2S[,AES@/ WZLc$\猒@z|a{[=xMa1Z}t#Ό&zu5ʯ}DaZ}#?6ǖy8W\vy^GU'?M~$ߊ5M-[\Uz}+S^ht ÐBZmU'ԁ\nqρ/xJnOr0 ϭyo?şEw\ML-{:#l#֥FXh'ݪIc{A.t; }xZ\x3|U5P÷5Y$16ưa69sxȫ?K>տ [$rMònL{ɩ|{^^hoik5ک @|l [qoyeiGg4 c:~ ׂ-zέw}q$IEsZsrfvRL>&*[DW̿vSoVs k\O'}c_oƿ~xARco !<;;x᭕şz /y|/F<Ϻ-|y3cRżg 8^YM6ğ¾ܖoZ7%c <q]|zPa㑖2`rMs֥NqE)8ԺzWO|PwWV9t9-ὑm{Oڏ{ǟk=.NRRp1ڼG6yV6R?ƾv9Flz}ヺUS k9hp_8BQ BpG̪~u&<#hJxs|wVw :H6:cj4iN3? |"G.67~]k~WR6zoi"%lPv{nϠQx[kj͞?Zi}WlY ڧdDvqUmu72uo25gݏ\|eǞs]&$*U\G5;C%=mM-2P$\}k|{gv![>9Z8ٔۊkIduW<]J6Pexȸܫ{ZZV|.s~5$k.I}k'ϾqY<I̸ohؚlErv$E\1 mF_?`}Flz=PQm&S4+ kks+=v `R$yJЋo8*{׏Z2t$M[Eyp ^OwÉ10ϱƒi4Z5V=f wOWnϽEuS(-:s#ϵ<>n(<սJ%;kz*_W`:*'dcʊ۳;N.x=)Z gOJ4$dp0ݷ]Zьߛ5҂QD3[Me-/Z5NgQ֛ A0/?(MPr $cpAZFUFդ9'|N&U;}(ՙ[r o/q񚀮+ 3oJњ-v[`ޒtXw/sG K+q2z C͏v࣑Fg[JN\o5+`cpܿ+R$nˀ)I!|ߙXm#oG> "iyF;O!Er$O)Ȓnf;v+/qgJUY $pqhMF#s:H,Zc_bQF)J0]F5m<-w#\s;ץms_@ uu:O!|9{ \.JE|ƮRQÚwݠ7- -lVsA#gSt}_U6}J^i&{=Bo y@<z8վ(T>}&}V=kڅg 17dp+t`t'#ϋA4Ѳjz60ɷI aH8Fۏęn<|u(QK԰&dP8ߍ^(\/,ГnE }JI1>:zע/Zv 3¾?+6:by#9d=ʹN Onf 3+aO\nz~ > mk]k@2ǬCX={Rm^ǡx]ZaԾvzt_&"7`XfxBKfoG w(pI>xᶻbRct.a"YHׯx^a!uƝoŜxV^zmG3ZP׎'f:e~>??\[:jsIխcG\eb#9ԓyIyz]X^2 닡$(ܟՋk>& i(V+j r}|>G⟈ Ph&s&~k7Bbʽf1Ϻ>-]Ko-56]hJڋWQ[3los>~r{]hw ~-k#[<7rֲc&Ee}|)m:sHu*G_ִ<W^/֯<+цHd;@ڙ?:ki:ƩF<;gR3:)hf_K]6XYK\B?jGŻ?W!'rP7_ى~_죊_6h˥P3x /Zz_=e,vKnj~;7xj}/ ע!i/Z̀A'FL}Y_~9oxCuBRkLfIZ}{"kIO$ľ}_ɉwW<f| kxfMIh8peq_y4JMH|3Zwxa2ʲ 8}?|o Ox3J]JFۙ$k}Q|[N}]m[xZ!9aZ}u=R\趫1Sh(r3 oijib]Ko̲#2F\Gex43fBUD9=Iio¾4hS-޿. ϖ?ƽc>)䷃U=#?tokWPRm>hdUe_v>j4EYtA T/toxl˫ż-+HgȲ4gjBp|?/<0uk/-j!|gྍb$6e8=}q_7ʌZH9څ mNGZχ: yW\Oy%7u^{GE`Ƹahk>QQ]{WlU>_;`U[k|;nX+n]ːJQwCd (Vv֛)j7©hfj([kE3uۑx٘6oJj6}sֺoj!iA漯ȱ{RZ8ψzr9Ul=<_}O]GLH؍Cy^kn=!]l?:蕗S˫>iXF+n}kyQ/˫l,߭GBxc\u$s]L-OqD~edw*ja޾C6+'(3ir FqN+df+ź9:󆋪Ϧӫl'k{ׯK?!UiT2ϭxX+ MIO \B0'c \nn;W_׬g۰E`Ѯ7ey⺒d\tj{Jkcq<f2;|15}7~sQȬPuFɅ :Ec$b(Sqq҈P `zTI#ۂO2lSL•3uk gڤ[uY/n O1 n?2lTi "2X֝4Qm"/UbuUm~W^S]чxi~{rsLw8˜vUwO%WwNk YB1ʫwu/#Ǔf7GrֵJM%2>^[޺S54 K[C!8ߊCF$7-BKE>J*9=x[QD&U9w>#ޥM$?*7 / *K9_Q|E5-ćAgd%$F͌rkA><~ɤs6oOQԭn-%.9#b<[7ּCdz/Z*ő@o>0W))6k pFq`,vm(F' Yv6TrUA]ֿItdu{@S\L$pql.EC6;JmPlLjf-;0pWwO|Չaecn{l }FHj}I)/﷫hJdu5jYu_zS+}DEPxXnߧFQ2n#jU%AѴm( mf,blOL,Yw K2*K,ҖWi7AtڻU{b7?; גI8Y03#cjjAUR3=3Oe9# Z58+ }*De8ǽ6c e}F[?xtmw]p n>¸k_Uv!ʼ:us6Tחbah]w|54hkR@6ݒyjOCBR˅2k&XW{v;jlm+VIs|lfnB8dem իܡaSxUn^<>&z&cqM~ap<\V-J,t0i4;<+=74ҼC#52Gs i,Xtξ??l nK=2mhRJ}+#He{Gu[C ֽ(Э6cZ¨漗FOmO/&֤:';GRk5I^j,9!$ g qN^if~`6f= rRs+GOC| +/ i~:khiCqbH#9ksVo*FRj$KA=0\y'Tnt&dW*į#ҿ5Iy'?gT"8Oj"_.5\Y<\$3qV sYkzZk{sA EQs\hsƚ2ne @n+틡Y|6 H&<{¶|9G]RAR^ `?-'|)k{k4co'1OJKg×+5@hP"[=c$ZxRobL׫|8ikw>5]G[ F7#ھ7Zx]vl$`z7MB8u+[M+GqNwZΕ_^o1x C5Vo_Mqoh/ACԯWsL_ڇ2 {\-Zmωԍ[Yߋ`?byZokanfq 볤81^aाѴ>TXݵ8`D$,VE9D|Mi5]N.C &}r1XlKYOb?m.|^WRKL^s>^Eţ5;0 _g-u dVk5 +Ok9mnJ!R60 g#[+ԏ4>~k |{_xZ>ͭڑy~ !;O|=ǍZk*Of01$Rl|, e|>,||O 7z.VKk}׌.Ҫ1&Rj>ѼUEv[F[Myđd\Bq3m3Q{ξ8Şkd:@=r +k}ƭk.ppAY֕ua-,P~%p9ms|WR^dV#dn+^C9|x^id2Kt߼uk?ٿ㿃u} LR[ҹ*SOw#kÝMmn4Fzz:w1#^¾/ Jev,_t/;syhUWS/FQQas /7,fp^ai@wzWq_[:<1c/_s\mŇ'1I,RXqֈQjs$r><٦K9Xg^+J187m9u0G_Uh9>'xZ).$dXD~^y]Kpԫh8wF*7˴6u3,\%}85FSi7lp'Ƽ\ҵ;_Ǒާ±-t8%&2΋>f9lҵŶY-3+K~GX-%ALi 2~Q\,pn aA&a(z4QZ[jVsyN& ک–;CCY`errPœެ[j7t?ynzYԍMJo(+2lu^4睈* c-ْndrZ\&Iu'Waipr\m^^zqyڷ.o~6EUm5 }b9#UH`H16~ _2J?Q 3&_;A8Z˛I*4mϢ~!~ U]cZYD*($/ψKvXVFEVh2.p:שJ|1o,:,/7Ү´ms16?(Ekqe@p~%t2qX֣\)Ƥe{!!wsį}0=DI !J68k?dm't>[zݐ\\ sEz J=} xfqen]Ljk%ͼtcRx;Wҟ uCu/֗i4MC!4c ?a,4*Siso \xf>2ӦHIzmsƺ)P1ʩů ׮4 s58FG;8譅_$ھ]iq$ݳ^s+ǫFt2a<,ݪaG_k='6ZE#3F(yϽcƜ3nbgE([oEDloeWn/qE2ռz'0rDޥz ͜N@}zzD+xq"Q]/IT;e 3zJd'C1m{?1Kj(qҤIΣ|2۶&/7an.et>CU N<VsVI|FILmVWgnGJSF|A@.vZLir]xجJ .>QzaT㟥u=Ư*W7 aʺlMIG Cc>,dDʿCc !ռon=5D{w't|C- pBcO;s}~~̟b.!O*uS JM+R0 񶥥,vG՘z*ˠWj7IY}m KiFhـظkڿKE{PeF+֭8ӧʞB3R&|FN\x{>o}+JI{X=q_ ~+[>!033y:t=W|b/I%Ѵce:ڧ9W8==kWo[ᢔnxR3znhϓ8:XCAJX0+o+d?{qx /|l`q/ ,eUsFb8<7K |mBET][Ē:m^yşڃB;|HlS)ךfm{:d\ǥ) ٬1Ǿ"o/-yq[[ѱ!h_N?x'+rl@{J|_/փ*Cơ <ԞZ>f<-~:u:6->6YZCoa]|_>X\¸-#aL|GћOC,lds+O|g۹Zq!GV=P_Zjin ׌ZcW+/[5mJrE凹K߈_ySSeW͚y$aI+𳦅_!;NP $_m+nF*ͳs/f=3*m;u撫Nn6q~ ~О)ד\8+HQV|7-֨.|1f.&4źNsߚz_s~xcVBg6/$J;} f[U&<Wxn46rE$~c%?VOp׈#ѷ?2 &DHTqҮxCo[䷋tSEx ^/wi,]bvok3Dzфoiw|)ccin-a`a+ x¶[^IlʞlG=vn{V֭3~zIhft1o쿦~(G/K&#[r;MQz+ƞ1nS@rM̚c5],/kO7ݤ,n.4k˫{;i4>f~>|u.=6i(Pqիį *NeuXɺ!6 q)r7gi<%sXGO) Jnm/rE/ĨoZ,>|0dY3{ 5O o+xeu%Λ{&C)uōzz7ď~ՈG4Inҍd*a:c+&?aO;iuMK0ƩEkh, .@|ǥxWxŞ4hZV}N$iSIvuxѳ \ e <T>n|q"[jc-cަ~Vڏv4Z|Y(|(:W?*FԼs|?j]J9'T%AV=iKs/h4?^_[^iWfH!!T\υ5x>0 4Ҡr9q^!|q[%o/qu=]\[6#8]?S''xo|D#͵́Qy1Y8K7Ob\˓u=jfOxoUXm6/מ|%__կM&Es67/&)u۵6w6]iw %SӚ䝤Jхo |giNy$_ޡfL^mw]|4I"۩4- k&5ьסxZM6IoH*\MZ8RbdwmFh|p;'V<(@GUaaVŦ/c]t}VfnȞ?FU]L|VsRvZIx.ѣF n⣼5۳r8MekKmm$;Z炤jo jmkhByd\.'ռm 䒲np!^bpֻ7mլƸqԮ:XlmbX BE<6nF+Ͼ-~?+n5/ ؼY( nxFaa|y#i5λxVӯa&ayP-KR*. BZ 'M>hm[0(a^ Yχ,.~15cyb׾ xkݯٛIVBr)8,}oMo kd:h۷%\jQZH51Ts=S?u i#-yrFF;VMկ3,p |.>(~,fcreh{q޸gGkhuZCx,`eZֱVz?0tw\7ۍ:qTk ر 3/ZN4&g+X#q=+&7Jhleff SPKB/_@;nXZ/%|`5wUlj+GqiaxY0E>匢dggNsBe`2s!l;7z]Ԛv]ZĦՆFMm87ş>ף[h v- \7uu7U\XFYҾ!_9K&߆>_I?gg<'rs^U=co ~-zNJ/.;V1o )i`!1VC9/zƯ?jp];]_/gRrN1BHxKR"x:č*oFŞ խmIvy[*0F{o _ $ŇCZVf[VY1" KOXhXKG6񼋶Fw!@_9<6ྣuSY 7c\++S11 Wԟ=˦xgᗄ-n|QKRkd4pH`#+0;"#_O<q*Y澭"vF!X:k9J1**iߊ弟 k=;ghzŴzl E;b,3Ji_/?Nj' nEHT`Sl&FPrqQ,|QÏxv[kA# x ~Zl-kI[tP=TN4sE&h(0?iO^?Gմ]3Iu<]oge]Z7Pa5WH2>)ONKX!oGW5xU1fYT8;: ~8hW4+ ]/̘ͮIu'mo&pqH[_f8Kc⏄<[75Kti[b# t{Bx^[_տt% --ًJc\|u{אkMi-jy^ae}F7̏tvq㹮ῆy !eo~=:wV^U"Kz[ S^h$oz̶ aݎMVW9ෑJHwdwk|#oLԮ:a3Mc_O6;HڝaxN5[n Z z月m(xn&n?1kѾz 6wD>ƾs>'Fnc\9zW|0moWֶuDP{:Ǚ >> imt^fgѯM} V_xm$=g  ~ϓYäH(XnuFܮW{T~ía>۬@N2srT}nmmVf]_hb9W?*/ ~ںVZW-_(6vH}\oj?gdj?h /|%aqUJs[7c;_7o>3|[ŗWW;K䑬pA q<Rk/>Z5̪d&>z |+q-*FW+$99*u${Ѵn|Üs.cx)5N|G׼w5CJFVLrU[<^g:/Ϗ[K݋YhW}r C1_[j0cdnyϯ^E]X|MW ~κnu\Y% (+{KzsS˳.2_"zᯊ^*-luqug[C6?c5x>onoݻx|3. l*"ǍH#/3 ~.4.c&T zF=klo_&/EGUK#cbJ4%cIJN&KEɨ z+FlTOx'/=i#ޟ%׌5ɤE2CߔW?D<k"[[Sf(Np#r;s?V|ULxT% ev`Y+G$_g\OX;u)#q}+ĿYM[qci+8_|U,Ugv˴aχSBnKk$vޛHkuS>8|) _ uⵛ~byxBJei/W +1 1/< :mٮdV{wv~'|sn :{>|xʯeo+g⾵47'd2o[h[Yxf;D;[i7vIHݔn] 7 zL~dvzBwIXq{i{GGj[Z]-60z}3\.X iV~nOy1hwmg2gw-W>Ě?8fk @0~`y ǖ^Fr=pcCžگu >`Kõ,g=+?d L>jN9^_XI<^V|=C=ϊ46kٵkZO%V,h>P˰mVmcGOm"IV)0::#*v=#!KkX.#8cW.#;AW^mY\vN)'T{j?.UżG4ao`jRZd5k-;Hh>tJ<7BnjHc$^_=?|&-ŽbGb|ͼ;žtmlKsp$mñN@[ʔjZКuxDu[I c9>3iߙO[9WɩHY8ucam{?`i߶_o/P(?vgRxe?*飈W((>,:*V}N?+Q Wpx%.J鱓&}kQmDm+|1tIdk4tC01ϋ5Oi)_Cn=>].=> kD}d)Tf+ܚw ir]SWȧ[l5?D^0K1/>ymuGl2+?W<fk'xCrͿmFf 5C=kΩqugU:ʑ<mk3^sZgN9ۑ?j?6? 5|7mY3^+ڇT nUqrWSzƗ㡤]?2}els^I3oៅg]>4iuh$8w9i(_+*u%*ϿĿ|RG}׃nIK6& u$]l*Tpunm3Rka5֟ "x{j\c Zv ;Z_:Zj(fgo1t8RH~nxMyi_Ywiqc_h펛l~uϟ>D&68g+k*ViKC{e ľ,r*eNPHшaweNMtOͷu_Z!썥Jmoh~ xf|e]Eauib=?|soSA3L_o׃t?x>_J۬qnv"ϙm#|?>x;1V6gۙBu6#md>s2k8b74$l"#3aF9d8>4u;Mh/OnH61}߻]p9_t>75H}B} e;1fg*;5:wïx27mKf񧉭nSn`k|2L|orXݝOJň(KIOe WnpI5*X[C|e{ w7mk .vo"!r zv@ u]|Gy}M/nZH&LE`3$^I O?lx/_nfkekq (YKŷj8~@jh=7OJͨx2=G\B-/H/M.98Oxz ϣW̅F~q'wkϏ>=5=c>k_Iqkb`P|*%ƒU=_}_6_ۚxMK_Nԋ 379AY:z|}}hu[PFvx8_ h+-֧y%f,6JfKyPdٍ .cYnH)4nbIqxe^+Oma_ 0pTḠ VuFKRi?i/'Z>#Akmf6($}/\dZE)/+%&U9aWi_jXFm l1ʪhӌ#Jω:iz9R{[*˸dڼFtF|pGORbvn C] o=*m7${h /3ow2Ԫw?_tNR=.u^.a}=$dxWD-Y6Aiavk=t/xvhUU$ms{x?òjf?I7 !GO|WN6tr:Om$蝤f5K_fTԯ-?e ;խ4M]+G{ׇv]Vm$XHI+xDV9Z:=Ϛi_@,X$y^ (g7H|JIүXtW Ocf-t:ۦڙIn*f2Qg}rSK8Eqkҿd gB;;Y<`\z7װxCդ7y&Z=l_x~.]k0ŬrֺzI 1f2GO?  {4#+}P)}Xğ3Mƫoqus/n$3zq^/H;bѕg;(䇒vg=c̊XmU *̒F~OJ^W\&ƞ6OG 6ȟ'f i&pˏ. eT m{{EkyI5tS3 | fu6q]鮵(OgL^mb_ h74+ X U5g{S񟋢Z];NiP?J RVU`Yo?}xG6sYxڥprmWmڄyXY۷Ob_[}^Ei}w627{ƗK&pֶ@F?|}ᯅj+k"V#_G~3xuKFi[䬴>/hqNљ;ه^z |ԴKVcdpۀnC ?*wƯ0_ \izItJ򗎜U G7NյAiF5F?Z쏳Q_|Җt Nkְۦ{WjW?~B2gPWucew\][i܅ 49kYk-/-$k0ҵYўIY|*y\*砬cH>\O.+ Dfc7Begƿn|=< K6VK3ʷ$fr{Ӎ[_խ tXj1r\x߁tQ_Jη٦x#ŐDklXlc7^?^!t3M@`Mw6[Zh>_Q.'fI;P3>3pAm_׈.aKE汅qtLFaۮkӼcx灾=|y%ZnGtWaERI'iu7$?k#!ujV;+u!Jz&\/,U4Y$I4lN97qᯃ;c଻<3}Z?m Ggcq$k;WhR|n^3lSc%ƛcOgyj-L 3sZVe7Du? °^*a{XMl/>.?뚂hpmծR{v,[.3Z׏_/9*4Y#OգMB[H? <[@&8MFL/!&4e{A*8 |rpI5wuYh?>.i^.efg k$aGcm`@] @:SgMvMUgΊ>~i(7|3xë,w-uM4G9kԿ^XOKvX^|&9n\ zR6KI&v^O_1 M[GvbK&pyuYҴ/Ŷ?$זA^KaXBJpW;W67F$}굖;{;;$AB^ ~973-C|=abŒNDcZIH#QEXu/Y?o {@T<9u4k4ywǤGx2WhV#q~=xOy71k:?ٴ***#h@Uy/+'5oxiv5K̈́ 2F8Ju6>c^&R#XY2Bt f߂ ukRԼEx{By/%rWv9*xĶq{B#we-y4#P NXFGSmaZCLR OJvۨߡ #k5o>(DX1p8@o↯M4 e5k9GODj3H>⹿|cd֞U~ `[V>d$HeprkGC6fFr@qޗ*^Jt<q}`irO%x6y'+KI]'In<[@Dvjv\e_~+'B꺆KHW煗ϱ<-\}<}+[kIΨ_AܫpY^Ros>XM?V SRZkXMJ)=ۧ^+][UeizlM 0UsyWߍW=%5=>qMٶWoȬJu*u7ևF՞-m g6']5{?h7\#y6qLu_>xS'}Vti| !I>Xb3n9A;S[ͤYB[,̌,LvּZ$͚4I }6;[ח;FO.RZV\iM[vͯi1]f[+|DZgQaWoG?f>KOIYj,mUH ׂ?εiz׌ixK AB1yI`|-u xךnطa 7IOCnhgDž<=x}:?"K;dj_r^J?M{X HGO _༚EהeЦㅙ7Pz|)aߊ6k=|M/͌10^{U"#8-SǏ3T|uْ2m7OSq=QIP#*r}s/VdOiWɔ+Y?t]āS]1<=̱6V#qj>w_z|-|;Ziᖾ6Isӊxg_fUukVvbțN>?O -sD-u&sj pF@9"@a;߷7}oP=kak|,7J]FXexj"QWZmZ/Gw @f񧇾$k^>|[Im6]:,ww kyۏY؆!YG:jy #wVcd=#$|@4/ڽ*8Y)e9Ah>[|W%?^s'޹O +O B"5kؖwx>xyQx_+Fi&zMҔFuQđchNX6I$@'ktS.Qa[̉i+H^)" ?:5%3ņl<Ԋ(v2mUyRVUonnK޼S-`7ADbFx_GBI4*G3}ډ^Gf 7t^Χoc-,jsWZ|Y]CA GPY;z&i^Yٯ ^O4߉H$ĶZ^5WeVVg%Nh[{9NwWx/Riz-mP+u^)׎k6`j/5LҤBJG\#/~HCm7Y w]R^X}pOZּΑ$5{k Ҷ1W <_iOo˧Æ)2ʽr>i;ӵ"5l=2/q?fކ<)*K+u&-$U^M'O5xP-2w?+S[B /&ɒ\Om^]WSաR6pߧj&Rg/c-?kfYYFXv1ߴMgoӤ R|Ub/9u #ӁI_C{dY7T洊rԗ+95?oդYZiq灞g~/XEcګI r7qں}:^LŮh+U8|=KOO !,tq9ܮ/AqxmfLj+!6|TPķOzU=?׺gQCk[s,?>2k{Y+6"jHrg7w&6}gt݃+濈^Ũ++x^kWⷉ//~=&Ȝ-\yiz,̟(\WxDg|S%rRyzYZiw<}+zXҊ{9mHR}+𮙭^H᰹߄_>iVh|4ﱍI1#kOKBy潏ß#o/ɘK'ic}Mյ[}H]M{o"۰I^ ~ o»˭>ݗY`{9E׎|:ÈuK9a]V`tY<{ _-CT ;.I\v5x}v>]GH z漞ILzG,֝~Dia9lL:χѭWpGq.;Q=}[ $KiE6ج7F{W0gmV>UY/o!9t_ivr>Wh7r=#xC^(mFND8'I#<?9N1~~2>q%u 63n?/ |j\gSoa``Twr?^+WZ 'Vs^ou,}۪Ʋd#n+ͭ(&i|84t?OV=.F} 2$F Wxؿחz%lX+uc=q}x3ßK|@:]/aMnePDm9Rt<>6|"sYF:nkk@WeR7v9?f?_ _||CIPa|*6T}>wŽ'º>$𝎏u u[8ʸi`Ò ך>3xIOkbե6$N$#"xBX'KOh:}j\(QX=Ey|w񮏪|3Լycoxv4f,[;a"Wg?#s,7rA{o"ڕXa_%|: _~:&b_Z?['L.H b>x+IV]4k}{W|S4hss wO1QMjZ&}o}7[̾ 'l`6j9&؏w&1}u{g7\KHcp0"l䑷=1U3_i.uρNm5!ѭ]KOxͷal19\/| #5Okxn;Ckhs6y&۹73Ee⦟I6~,>ѧF+6"]? ~ϟIO~.ռW}{%=H>bطcsGO|!ٚŞ;]*) I0,Oy[}}oω3k:q&YlDN|0unc /c375 /.u e徺2 q$'O,MN~gFOڷǟuGgk}jG&Uc@uWP׾%r/to/VQoI:$ܐ |pG1}aKšM熴n-m|3%i$hce%@N1ַ>I>mnZi#/.@pr1OsxZ|o7/S_j&m<@~ ׾$#þԦlc,*4dH$APH kSzWa?m.`_SsRMx!uf񽞖X1+%^G>⏆ <2jiwIzOoi6ܟ:׽TjOxwPv6xr#P1q_?G}Zlˣx΄3yyۯڻÚǗZVM*m5)ugkm/&\5c~W,;AٷwT>+uu[9o%8P6 7f? z^xs> ӵ/xmYui)@1qםoާ@-]*=^147 P([3?9x'Y%kq]Zcaq%|&+ ~# >!/wLshw -w,h j ~0Ɲo~wu{=AwNQBr<`t^o<=_| ekiZk]SFڎ?9U#;P9_.bPO^ǐk҄mW֞yǡ絒5/tV0g:!< ۰',n<_|?hkkmoFxrK76ty?C\m7>0n'<%kђuDo/Q6? }ii[}vJ=z`#qxRl׻#ޡѴ&mf`)5~I_:}=}Z K;>Kyb@ N9q D3(RN5LrXgFkũxOKOCtK.O|Us?ÏI$Myk.+''ְaĺ@mw%lCinxGkμ|%Io$Kn.8Z8`8O<Wҟ_ SgZԮY[AƿZ²CJ5LlNj4:!)3?=;9>l.lSlg`G8؃s?+RxoxIxmRfX 6J>P1=T״*/(կĭSOw5yH-lcyj~1w/hǪ.f[gءf<0kKCO,|[/OzJmC1#?y[#)-9J:~cmMŔ/OR:'of]*vv K.~mSFWr|秧_"tsw%퉕c֥sr2XQ͂ޯ+/ክ;12Z@~}|Upί4:u&}}Jsńn|٭ j0i췏 1R{ .ӽz7Xmy«^Nxzt~rcyxV[GP? /`zՆoCBXwM" kNQZ$z3xGNt6H^׶|q/ X/!ݭ.max^~Lx߄xqW9cqɿk= /o)!cp ?zr9-{5)¾&5k7|(AcRI- ]\7/.s׊D^4ghG ~|eBW:~)(]hZ|OVT^^¹MoΜڧuS6M>=3]12kj 36@feQ*:"'|S=ƕI4f?h6G]vJ~)Ư^^i~3m :yĿ>OKuF´II}61W8OGމk/>xb3ӗg=kxZnV=B^WxÑZo m4דFom#U):1lL^>k1m+}iTF̬~_`j~D&y| g=+b`#v.̯SӞj՟=Yenv-ڴ.|Y{ob u݊Uֵٻz~;EIu4<(-+G^zX+V[#l2F)G%1q^c׋fm6HM:}(3ʷg e |⟈4۝l?~u Vx@Xdz߱N[FW kn´j唩31ǚWx~KLBaջQcc:NYǹ-7\u~1tmvv'mx#LMHAs] ю3GmxO|OuPI4ݠQc^g7n?5!tmĒnFElկݽB'P,>ϖOOs&e8Ss3/@8׊|yuo]k ˆfˋ u%~ZG~55mN(mg݃ZRi9_9*UcY/+UF=o$"¶vԗV 0%#^7jsҨ #vjW\]̫«xgº4M*qrb^ꢹPMokX4%_qxs¾~\sFxdvo#6+d5 qHR:־~+xKAסӭ`d d>l{<` \Vr3f?e5Y 8b} Ÿ3~Ρj*LeON5? l-3.|Ag\n1ip԰dp:/ir=kO@^!;2Z䁏SjZY>s4?؏whk]Hm Gc[5xQe'_\ԭ/jo61n}AP|/-*,汙̍&C,Y Yo+|+ku?"x-XΥk;y1!'z178!܂ S,0jG>1߆M#Pd[l9ǜIW/w}cNR|E/\xRkB}AH!M _A? O٫Z7mhOڕ;6Ȭ'~Ş/xD췖z`NLH?} b:*9dZIDu~$1]%# ՠa*(*FJUM{fn>Q|G.m0_kY8ňI?t]BtR]s9c+OS𨷽ԬdԴ~f{vI szq&rI4x N;Z |+&k+i %ϐ&?[`=*;{f+OGil4/hg8fx1 =1g4 |8< twm&p#ṲW]C\g)U8|=| gR׵B lHtVNA>cp;Þu3]sqZ_h.͗t2D|{R|56i_>:noiQ3[[y6c ]:χ0xG|;k^+,z@1\mC+h9$ YƟ5i)J:YWY~%G3M]`#72HYؙ0JO?_i07&qZF)<ȗNv'5?W_xெ7i]uXo|H! A~_k,Wv&a_y_l0v ;1 <ڋyžKo̒\8th[\1;!sqW ~ZR-k-&H{//>Q^$n;N^2O 7^.VvVdI5<,*C}I8E}-i/fO IRm&G] -[/]sۃXzomQmSNjlM׳Ȭ$mdzcυ_W=GI&:jɩ^@kEeV#dr5oCKqWA4խFau2 ڥW=NF(r#å~ |;45.5K846[[F&GszcWσ .Q};c4j;$Gx e7YKM>-wλΪּ[ী#@uqk2hXޖ62B#99ZWNSC9io^&?M lȻ\295sÞ$I֕.kWGzV9RUmh^0mVjꪥstpc$b|`׾!j_ᯅ|O -a.RU!c"Rv9W+R|!QcVa3#ux+vxIV|EyjcPNf+s(ux (WVgUPC:yk~|G$fyn$#3G*d4{_k ;;x>Ae[6qsX-wPuۉ)年FSH[?(u kχ?U]_NBKĥQIq>Cg7~2#P56MR5#% e_j^LQͧ?1X'Xv5?%M7jv bTX,`$o: s5mg^#Ah9<޿6zJT}GџO=Ǟ?\-mL-=*^b:|mCJomCTeH*V$ʆ(!E~aCbxz&Ke_xvGE5V'l,TI#9}[8GJ߈> =I4B&x%bGA>88*vQhr+Zm'ή;bEg._TxXo|?H٣f pZOP/2>Ľ3=v+?6^հ^jM~P>a{Gov ʷ<++Ufm1AO979ė>%WP6£-Hn>ڥεG.۴@FJ˃\,|mo~Gݣ}_I1In\0apɸd| ?_i?&.BZo%g~`eg#fưߋ4{?IIN$,B0/Ce֧o7o TxwDCqFP3ƢI>p7DB)#<]>07׭-M.Kh<";&X8'>+必~|)|<=֟ ݴ#"l#rE} 4',g#"FC=j"ڙl}o߀|9wڶ #M6hN9g[>gQ q^G߈4KENJ-tVkR C~`scNox4='M9-D2setڶ<_>ixfOGI38 ĩ#N;GH>ᛝ[c?֣a0 ]!~V=/o_SmJ ?L#Sʝ@ۼV6-@#]s d̉=N1T]ʰ%mgҪ1H$m"Ź;VޑiI\0 {W-K:UǓ zooj.Kʶv-JO=5%S46*6=Okw}Xs2|/;+>Zj6fʺi8h\/]ӾϠX?B|Nj7T췍{V%sGxZ_cox6?l5P}yv돔eFU;#zMlAHGjmSt?M.ٯ.s[x6qWF7kn+M)|'Vc[W)n>f5FNJF1vGu}M;:z-.IY98ֱokoi -i.zk cJrqk~+|e=K{9?v:s`s |&>q#mBX5&[kzo%_aRQ]fg xxG,)"K Yc{'??緶4lS\Sy[GI0Iܷ\rZVզ6cWC{xBay?QyzAY$wt^.WO =.$k;%⯶ֶZo٭__Mo ֭6cyjҼֻộZB<7n+xxf"UF3Hxޙv [M0V>zRcԣg\_iyk+AEC.{GHnԏr3ܲi9漇:Qt?K2UN~ Uua:y{ׅi7Wq,-q `>Sz'X|1$63jO_.sQi2:KvÖ*ͷ.8#ͥ8+rkoL,~cxKFi$Ud3uh?z<^Dk?.#W1'=>-LԴ-ksPI.Uw.?JGmS^(M][ڶ#2=޽[]ukcyO-v -1su Ƿoְ$nuk_xXJhU?3?l ?VԤo$+1XߧҾgG[c@Ҿ`G; /OΑ4kw3ol"L'+k-PƯUIfv cc1]HD8دb|)+Ʋ Ͱ\| U/ůxeQ03m}v\%60r{h"Z}I?ෆ~O]m&Zf$0OM/s w}# ι݈Y;L|r]_l!XǤ=aGa9ן*Zᱛ,Hml6p\Fx;y35?K4#Դ}0),>0lsXǕ?xګw +cB&R\iy)IcO\ަ=A&ojue״OgpcYdml1} Sڸ߇:qExƚu0aE;Z#_H؀#;< Iuӵoz '*ۗ‘)M%uxwŸ:lbLS \ kW<x/-]vRLA䑰1(P~QֽZ~~"mS_]JVmg8aݒ%bLICr;g52^Q?YP+Axf~HmRɬesh71%R Ui~omqċph%y8,xcJo_wkk:Uf-o/52wg3q]& _ xXok]KR|k!pr'ҏC+&Bխ51 +;'bqǭUbFdCա|ZNJtjMK^6R[[?I$Kqح[|yᯄw.#] ə̸&rݕ< <-zW'5Z-?ץd;d0F*}&i u} _fBYm?jJ:n@mc K;#IbUv *Rc僐NO28Gx#O~!Au/wLlF[A71k~5v?[ҵ 'Ib3XwRL" bmC Iᾥ_ͷ;iZ0e۲L#H'ӓu|Uy@?7R(H'ھDm􏌦>!i#Ud9/VԒ.]g gAtu%i z?1^A 'm>wMU+3܏8-u"|&ֿ|Oyj-yr _}?.LIﯮ5:AqZR?.Wލ>z1z#o#%ƃ7U66$ֺԎ{ׄ,O6ĴF_L$ƟV8t/ cqRԌ\4Ѵ>?xY|@ ̗-dq턴8:o<;bfԼSjO`΁Ƚ@90xBh3^\xZ.{᥷)8;)J"3yg|&g7?)q6 \>)-2xjR]Q ƚ7a!d;۾jX޿?iB-Q-kº:+io-jv!pk/|`uov6vZ[Vg2 v`c20W ޵_?,g-6KP[ +4/:Fz ~!ߴ-^g$>g4KPL0,YXvd#895nTd}p;UöA2k|b#'1.xoI\ircepZ9x \g׎{/5+_Oߢ8DHk9m k񖍫ٶO|Y\EKW26 FlZVGy^M}=zM=$8~4BHxu+++4~TѰ<.:Б\mFt-[:#x}xϸ# yM{ᎎ7x8ՂZ6wԨ_^.l4զf_Rx:i+:#GǏ?l/vo}2.Խlդ<{W//dA[bk{`(ǭpRX^GG2M1cnR8cCWc hy~;lsbߚςYV{/xV? 6Ip꜎:`c֥,Qd5}"t;_[V{w6PL-+lwX{+KKK mW6I!‘8J|ww/6:GfK:Lb8[q`y>?'c?h ok|4k{[֏sKUNr++xǺϦd&7[cz.r61*;_ f֝!${?PS+:Psφf=Cԗ7Mh ~p1WSSWFSG&A1Y6ͺ^F'RAaAdtŭ.T𿉴٭!j`Ģۅ=x|+^[L𶒺=y7Z}ĹDFN2 |1gu$`%r\|vHs>хnPĉy=UXyѱ;H]Z6*:!#2z/q[su_͂GҫٚGS3juJ]ڕ*mxiJ'Q6pm;wn<5Q tnRqx$ !t2 6ç&hR1ZM_oƺ1xK'FRv8^eoj1xY;LE U3 w(#sp_Dм9{6jۊƫ j.~3n{rM~^ڏԯ?6N[ξS9Wzv˹OJ~?C߻[` sl"'gyϵuR< O|smk/t3|1^յk{/-|͎O~~k^ gV6* ѣH{5N_NW.0w&8T5s[]G>jnj,`Z=.+%#X;8k|u~,濗Ne1ebR?Q]1bqI\–@z.co#7u~+l68(SOy1'l)Z|gxަLXSO8&Ҽ/;GefoukgMy.c'_/{\\Z1khLø>L.EkVF\z2 -dgA[PYE nێy&yd,1/F^6Ҵ᱖ILmr:DRMQuìjbV. wL[|ͣڻ)>x7 *Xk- sM8ҧ]ɔ:WGm>ϥ-yqqZnxjZD>UX}kSZz~Ѷ`m[P4qZ2,=JM⼁ϛfx֟.luK[||m/nDs2p_xivB |FYi:Ʒھ8t{Q"c\aI>wL4{4D;9%3W^{ x㦓z./4P5nueaYGÞ pԨNݴ=ZtuG~7>%j?Pf.-dSΏx`E|T7M/umU{9"vς~A!Fyz |%4aԴY 1!Jdy<:U |ׇ kmŴs'#qׯz4e"Z~Hnt/ >Լ'.6Wx[ nY+6ݝ;f|16zXּ+3f&BY`y:`װYj#[4)Mt3*Fcǰ'fhVZv6M$ͨ:-,GE5aC/iR7^[?h4{]jRD*@5_'>3@KwjXeyc$VSs <)bRt}5N;u8#{9|=/É5ơo,XWlDx#<]{-yls~+>>:i~3_h2ͰAr8' 3s\|%VܥƟ&s,1nu'Wɐb6#ּl|QxSFox KAv%%+,a]jtח~gn>jMt9`ǥm)GOS8MIִ[_kiiG>mX~qD8zWQoxD}EN6m1 4x[ɩf5:yU-/޻v)ʀFȮ?υ> ڔv%$ddXŎG@8bƱx_օ'gtsDf3ZLQ9^/^KV-Z.FKĤp@; +1鳦R_ 2%}͎%gԼPM7(rp9'㞙 @Ѽe|Hkp&MVhE 3ɁZ^:׾%rmM}JRKxuF]gdž+V $j?h$ox^5-NElll6BAbQO5N\I;l#H? \"YFaWb@<|V0J b_~?gMDy>p.F|$`'=+(?>*1Esy-MUxaK"n%v+Gc;gWZ~(5>ݮd_:1C +D( ,Tg_nhi:^3zNJ=R7dL0w1ci<$O(.skf$oa'1 :O3\_O>\Xn\HLiqB+nI>|y{Z hZ,?Z j/:Ipk9%qA&kx֒Cq-< 3jN㞄 kꯀ $|Q\񖧧\cr1lzWξ״iw߄}\2;F- eql?3o>~҆DԼAF,|)aGZ;_ Jv2XNbMH>0|8( C0i7یv`Fzw_al<,IK|| 喧5hISA֭|S47-fDhʕUlt#{7hO_xCP]mbm^hD2 d(Mx ^עHmY>̏$)H;̷..zѤf#l6s][[v; w?Űzw;ঋ^?|7ǔbԇnF:}+Nv&#%Ů^afUǺּR~;bԮ.m/H`-V#>2+]'|OG~v&,Plq8[ּAeoϢx/Bu8tUQ7Gz/mSᬿ 47rh \\AᙤgoE!c݁k1 [:[֞L`u1ZE\5>OjZݮZ\xvöxO/"4 GC'/|=}}J/̷uDА(܍dUW>8x>ޣfkn5RBț?vSc.U|5^v0鯫7et ws(8۞{WGo I!t#RϚ +k=kZlӤbH~#o3,F~>H^8MѵźQg<[Bd-T==*o~m'M i'Fkv6Ӊ!u>3O߇K?7t,ﴽo';6ѥLoPy(Y'x݄77C3tSW#]Y[Nx'\˵8ڛn|#-Y|@BY+a^9͠i?ax"Hi̒GX~СI {+ґfV)m6dd꣱%R^ M܍8F":Rh[^\IZjNVJPŗj^>|qj;AMNGڱJ: ӻZMDYR0s^W3^~eNjdh{~=. V:{ :C:H0d) nH?۵yMrŰXL*2\I嫳ƚ[h* @LMj;::uE]Mo4$:*^Tt>Ƴcc/=| 6u;K@j3\aW)ǡN15=S h:7ßڽhf02ɺ\Pa⛦[ ĻUw+p:zz>[ hqRGmB̜J9Ԕe8ex}[I~i??t}Yt`F8i"n |_>? |@-;Qn&L֧dˑ"1X;WO6Zmuupcխ"8hQɯ`{B*xCoGZ$jn-E#a;w,#+g()82?:lIԽi##W^A'FV0^yĚeHss WSVqҤ*Az<385iJ>bz~\.>|O-I<;GҺ mX!YY{gMuOxdV[灑ڹ_:}3]0,6Oqǽ8ǹ.'J[SD|^g+m^hÑ2+7\]jǝßZEHLIe繭Іs>#ݠXCz-DN2j+IG;ct|1pa޽  Ck/Xվgh-ʜXR7d_> }jmoP83'P|AkZ~eqg p\}'Y6\iȹoVS5,2ErDe)7y|?.5Q'*#׆4 B D;89jºz~#f/&g|r[-qULҺ(GEf㙼hjztn\YW*b'~7ɫZ4?yNCv/ k ?G.AecںLFGQ?|%sOVm$v͇ƃq\؊ܲzxZ/ԗOWIioymn<07K%l,u+k[K'3#87{5u򮴝J;L)Wj&Uu&emߊo;mFMfk6cVM&&hÈ]p8oWu-KiHPT#Aʑk'%<.asmx^CU硪×~,xHX~O yQjK ,ctm #Y߱ǀ11 SY,J H 籮A=ǂ'kCE]KWcf~$)=vחx|[G>մ*[õ卉Ro^͇N:]xO O.u}xңE@0{B=|Wm2 B->+&dB:`O*kZׂoxV5milXy+JOsX !߯tmAtmK96T^3<(1)Jk/[㵯%|J|Q`ٺl6S_-_<67Ү.uM7PѢu-?Nv[s.!#DYQI).U'|A'kճxӼ3Q$XT8f W;GL?J7E_ jU6.# p1naU~$HZekY"r@S\ŵ_Si2GHRdb>8n@\O_?Mcu:b:~mr񅼍i[F Hhû1:ĖsWZm<*YY!+~ Գ/N6 C$}xNXOQi~#{1Ww^GC$Py61))N*^&֟]P“jqi.}8Z6m171p3pjW:wuOZ]jw*! +ȯ5_ߵ⶝foud2O4O&BYIp**RW>~u |}mn hl:>,oKOUbv7vp=voPãJz\G/Y;ع'%sO/O.^Wxck%$y핔tdzf3w u%4]>KJd"=$h۝3DdۙYcO'LH񯈯u7P<~+1b+| 6Nxg~ ?ƿ~b <;tlD62'NKKt]S <庛O+cyS+ Jwr*;'­?jw5L-]c`z7x^x|]鮴Z}դCO1~$*Oψ#Y^2:}M2oN9&f]ݦ[b&}I'q cQƪ9g<) u4'.Y-7um9ڰ(ՎOS^2þC}%a}Zx>moCfGo1 ِep>S|_x1ڣB#1 =һpӔ}?WE~9S&v%,P@8;ZO[x5hc)1$J#61؊y]s^Gg{uAzcu6"yv8^Kk|R#Q#MjՏ To7hqE<77Zޟqnkd.Qَ9 +SKKi#FVyPzց{u/ZGkZK2yr}Ӝqu>,7Mu& /v<F9=-+|FxcO$w}XX7͑^Z YOkb 9>u$ ONMYFׂ~&4/xUuCF` 69+<k eufqè{ywIݵ8 N21^Q>it MHnwF0IY]OtwӮup璌0YȮSu;_E|TM~ϟ&Z/fDxImA9s>٭ Jʾ[WW|Կ|;[63F?):WxŏNkmog2c.mu=xYu.teJl8+Oxk['ӵ ɧ&;7pG5;Įih bOCK뗎,< wqʞQG,$[mMW9+Du {yH;:_M}UxKtt-e+5eϯsk:9#YXyI3ǽ>sҭ#RkܿeTkgwH9c~~,¾3{>!d*q z|#áI5彜biN}wPэJz|1Mv]_TΤY՚E9u0gtFjQ!ms~YFlcsо,kz. icܠn;}sdmx^_E Ymdr~T_]t>#bK1-@HQsۊj/$%~l:sr+\(boӵK1;}0Uv{|M/VA8O–<>#X2J+sH["ʙ^"#/PĖfdYvV[rxp_#H7*+{kwzoi}+VOMݨX˨06+~~+n&7 ̥} .oo#~| ѼWsnڴ#\zOjX>XLJ:{W~ᦗomZ #Lww1G {޽=rרlSTwO3a&u?S\;K7֭;o'"?olCn1SnkhiZ 0E/͓ޱ<;CxQQ2IA^R[6!WYjllś$7֛mN>NR [}j_(|k|aL u3/N6Fs|W}<qm23\M:{*9>Gƪ[Nf8//xDVFĠׯgyBu_sB޽@l|3lU>Q[R斉Es< g¿ !l^Ē``qvj.:zS5C9%iF'$sҾrK<1mc]iT3;P.wi8u^֮"՞^Vٜߕ|_#|c⸵-^Ej33gq}/h˧im#RKݕ^[WɇGj$xRt.Uպn_|(|5mJKbei&wV#=ÿƛ>.Ͼa-\@$#ޤ.Xܬ=7R>fhqs<3]6կ eZڻc zZeY~k};P Z2(nv^I/KW(Z}|eZ4f{>kT|WCM1Gjv'|^Լa{GR&,[ϖ ~o-91ڬ.i/G,[cŶJ0 ?kj7 3748[Pdwls3]\ƞ qUV;j<i4߇$cp{&vğ0Iq^/?'{}ϊfl-ku{\J6y+_x/GMZ"ү![ci$|6NTm's?5 -i!(Wݷy幤c.n]4[Bh?|e 95 *_F7sK"2.r3ӱ3x~|1_x~== !܌WL7|=|-ͭjE}$xzk7ύxl|EYWPmluE->М!XAcQLJG;s.uIt; bc#$apLV'|W#\y$^TҶZX,|kG| YG_j:<#6ki0b"HɕHAWm!{}yF" 7VKc9JM|I╇i]5& )IѿrdcƽG_eWPW~(;=ι*ͫG.Ic|ׇuWLq_^;ye\rw9a|H->=nkF$ 'Q>ƫF&NRE<%ֲj>&R-ŒRO`XMJºma\`Pba%^5Sᗃ5>CUdž ҶB@s[.[gI/﵉4(n?gn 9zNKRx_&;TK+gG ^W: FyaΛ ab Hc?vҾF/$U57d6z|=s^m\|?oFHm=$*d)gu~վ}kx3 zwßOޯgFy<;4.]HY z^+o?osk+U-OD}?LՠkguKR#"(ld~ ~u$p]BELE$gs6ѝ5hQ~ tZ Mnq'ڶ?e[W~Cº]:F`TmUާ2rױ lqi»i4xau٘T=x8^!3EV~lF ̑6òHcKŝ={uX$Ѡ(#9[ k( kqkڻHۦROYtg;Fş,g5yuoy}yxdI[vG.Nqsxk~8ž][C&IDo,@R~MC$|cMmUfИ- r{g>$xºVsPtgI4kikMeY-wIWzVw2IEl)xI]RE/*wK3[rQ֙jt;'j JC} ='Ji3c|*?K;BM9dHvbțN~w>ү>\|>伵:l8#Df'T`AV31Y#𖱨Ch"71Y>b;I$ `W!-=a'𕷑,Z-VP2a.@{ 2u:|\V:J[<3a$f_ú7ůnվ"x 3s!Yz,/N}QRzb K9^IռGjRs\Ph_@ 0qO5~|IAeis[^iS]/ |'|Q\5r#;dYFŀyWzjr|>_O#emG +6Ih3I B4+^ xkZ=omss@ c^%^g|3ֵo ~pVcȉؗmp=n3lD}۟Wio˃m2Dw8:M3^աRƶsJ~"To5%kucKhW>F26uZTͻv~{_5^=W?ՑlE^Ɩqz:\0j~=!4wϖÿlWM#P$XShmd|eF ӊ٧~xg^A._91$>p 2H9+?}ѵIyo_,66$5=YQ=/{x=w|)M/7QRFvcָM7& >"8]4"E%i%s5Y-?zeo ɅW9>YQ!-MAwkg/FTPQաѯZ_Z`zқrb,{xñ/Ywrǡ>ӥX[jOQ}9_~įJkɅ֭L~1_rkw]x;,s9K0 N{zW'Ƌlt `YF i +ʨ{zTl3 Z\=]zgtSg5HޥSԬ*.C16yvҺM>drqM>85#Deؾ/r{2i={n\Im1KoO὾ 9?/YNꚎ8fя\h+xK{KX!]t9U#N6O_Sf_~ӷ[.+N čx?/Wtd_oʹ{2[B;g^ZV<׋>Sf}V/;Z\3i ׮h<#Lj ̋0='/KIe\4y~~(MhxvUrdw+:C7W5n(kž5h7Zns?ziw þ*Z<7<߉V>JR%bg(Խ_QѼ+3_[FB v8x=_XլQ1ڽKVqٝ"fۻ$6QmxW+<ҕR,m&X}yPaJS:h`W*~fOvqo? >i31jWK pFJ/8W9o^. k,'JOž$־ױI1cxQ[nn l&>h&QK  hqz:!pc?z][k8U`ǖ9 /TuF86nd#+мh6Y$`OcJK9e xZ+5Lr7?z}oJ[Q|=my'ѷ?:?RՔŎUFՙx;-,>X+?\9cαzϜz_?ʻxN=%I"knUwo3''(mECumZfXqq_YKccy-n`Áw|Gaiv~ZAMdZ~gYJF/ٯ i |.cp;M'ʫ\ՋQ%id{]&E揘Oc57q>?qhk^gj*]B,bDS)-ո{/Wźnݻq^A5 >;;ک%(w#5V3\-.O >-~tl-2oK+XeW98GTd.Hq>&o7`^EiQ]ơ~WP 84ϹŚI$*I9'ӵeOo3zcCRi|z9ɞX^;/l4$8׵k9<ȅ7.Jw_y<V%5In-Z@%Xg`v_9ko/y;Y-do I'7e9?+vz+kş c oARBRey);z 댌_?j[=/ZeY_j#䯘\# % O$`5#^t7'<9"Kxa>e 3I`I+; cXECII :[17aIkk){#<;oxs$jv%tyr;0c&%8ϵU!Iwך= Ѱ mBN0"Vƍ&OZ.ԃ- ˑr;Tώ.5gK[$Fݝ܀IV>V瓸SQEK cOm|%xb/]dž:r7{8F8`m-!kƵ?fY͟^GW׾%5YGpD lj3K5rZq%ֱxo׳],af- Rwlvҏۮ7 scz_úg+F񤖶Q\Ⱥ)"U}룃ox?T!7 _جa34sۥsZTZR4l9 x]5_i]/NnDմ8|ᯰxUMGk0>g#p">#|*YMՀc9#=1\;cτ^hv׿ClҒG$IFM)r5Z_x&KPj Fc8| >.<7PԵFAgeKu 2þ"5σ4t+y$ 'pZ n~??_}lj:nĒm~>_?^[ƸE檿(o~>i{v-g(#ȍwBz&7ŗt-HC=Ɵ|IύSh^Y^2-3K%Ĺ`X};qSmk'|O]{K[+!q T44jF}AǛ79_j:\lmA?l&qG^snWŇx`O} 8bz=+_m6ksZErC2!=b8ݓfi5 7/~#`lnQc`g}OEK<4^wTk{Zk7nnOlMzς|/n4˕UczFO+˫JGz D"cwh/sl6Aԗ, `wub =q]?/Ğ ]׃G7}x_+&p\n$gH'ö,4?hFƣ2$wqq`s]g o~ 7oB>(P5Cq$8lqГ9u-RWŽkNе|aC(֭tKMc-Vv 9aHf~џo~:6յӡ( #3#=H~~6Ϳ=_ஙIJ/>+kL`n<d?>6[r+yl_VЅv?/%bqޔ՚/߇óǩkL,6o%$Ou1JOKLG͎ki/hz|a{4y0_[-|#`o̰|>ӣ~Zk /㧆m%Xac%]3co8ߴ/4j> (xuEB G+OVOgWc׍ 4aՖZH`p[ 1R- ?x'@MM [i*&0*xZⶻFkxCA6d+یna; Z􏉞Ԗk >VUkD2(c6y_Yt{zĚ=kao dv9rIܹk7~Zqu3G/#XN_qҺ/%O| ! XHa-s#)%q&֛Ϻ+-~0vQ#ɖV}GWfDo͝$֡w z '8Ke?e3ݑE|Zծ ;&{E'-MF:zp*RBP=k/xM#U!lEM,L~r^k߱~O|:gu-oצ:垡uC↖?.iFN7sz^{nK c\<0rDGf_|ծs ur\Zj̱kwV̧n.!g%@<_O~foͼ/Kj &GS/Eae#n<ѳ'nI|4ס{g+K ʒ,xX1+3njgj [Peq?yn+{ᯆ$[ ^?x`8#$/\ǚًĺ??bX7)]t%OssFRRSJ?T(.|%i?&^xTk{iZH8 5&efl6+E\D{']\y(\w.R>cC 4tQx3kFޭhj*VZWjwv,goZǕ5R񃭷ٔeS[J|\_OcKu;kx2}料 /ǚ%[{uʏ5>l4=7T 4DA//F 0ZMq22z=U*Rx>^aigݺN=ZElE|[6-9EF %/$xqKf8Wz^P׾gO[w{{W Mx6q1?uq/hW)cҜe`#M-湵v1Ҽ/d&hmȍHʘ-q_LxxHk3tx_#Kِ?'>ZxNKQYog)c'_7ÿ }\4;5^%IoWي{⾷)-@(".U;}Jم+?Zfh}UN:? xj=sNU"fmϙcTk8a/5iN~Ag LFd krhxLdԶq?+ jM[Id ֽVctfGWb|1u{'s>C:q jZcrwF+zk('gq^׭e a^CQ"\I -Oʬ:q\ruFlޢYʿ/S'XsIg:{䨫־vQܸק5[}-kMUEFB?{*)j8Ϧfe4=/M|/*]agGw.5}KB;Kƅ-.z=>|Ay=wGY&V'xQ޾zwŷ},+.uiu1Hձ۝9.i<<_>;-ɵ)|~+n!]F8 qR|O?oiV[5i#h`!EHC̱ěWpH;F彿 VOPGo]žb63o$GP ys[lJW^%.qGZDoy==+o_d!vCޱݮ3)8ޡ?FHǥ|㿁^^M)<0jVzi~]cb;q[4)H# ׆G\AI#C|DkgGCֵ 'jW:ܳR=ŬCx2Tc>P۝k߾xg4*Β2cFr :V<&Ѩ_>7|jM'I"y`#% 8"EƤHU4.]CPaVpm 涧m|0'%ĝjPTm 1>P,NzEw3|%ycBb?*:ozk }ez8L_htBzZݽ1K v.mšvԘ>T?4u|=μ]G|Kqw]fk_ :iʺh81 '? WT~%ũ0ϲ7WL5? |Hy\5nh)E`?>xUkit{Kq$zvOsbH4SkKsǶér@мXY:Ӛ)6MF꺧[]gzùl=anU?,6`$:dSw_ >|3tT5q!olqS~.ߎ^.]ukmKFf9c< \|Ü OnsE75f%6ʒuWſ4vzí#GwS50mU۝ݣUg hQV>)ψ,7}M=6 .գku;XnNxOϞ E|%ҿd>*㍯mw¥xw'Vԭ~YxcO[lc;6i6t_Hq2zջ/ῇd=>; zC^P>> r2qSK: x3VxO55W2n$siz.lgG^M-GUFQx{zuo߳4?hdߔ3UNsa?X4鍩k׈,#~h82(ɼyt>ĺ?m>?4^C\SALWK\3)|UޯͦSK+"6VNAYƞsYOԶ.%)1y<¸Oh/߳|8o"1h32@#IFJVx=^o~+^m|"dcm~.(6Q8f( Z|K't<]miMc\ScSώIWxs~-$nx5HUOkfF+=z[xXsHi<>o [KK:rE y=5w,j>>u_]?ZaogokoV6 Ix*zψ-XjΐI*7 ~lk>0|-wǟjIOpu-˵y`6&6>|/i;8t+_cFģer2(ooZ,tjNW8y lՠt$l4C^7[6vn'=Xϔy i:߉ÚėWm෎oyuowO$"YMI5 xgÚ_-oZTkϗLτz7m㺵Wڭhe?lsQS4'VETk囍&ӦՖ X9=yxSe1K2Hty j|w^U]I%吩ӎ{nz@׼-x Y[;ֳY#[y6۰nՃ_j>ы?So%ʸOY H/%ǗnQVdžx-.W[͙eg;{+ٽM~ŐNtf]9Ϸ_ }Q}&E~a"킸kǾ?>vjhw{\G"x؎~ix?7_ ln]XXE1ddF{UF7{43^oi[ІݽI{z xq{VY+E^"=qij_ xK/yU) wKO^x[XCYH 7RyVtfEx3&w֗Bρ˦Om |ǫC}'Rm.w,S&ն%z@=p3]-,|/۵_Go/|OnW|rMfamO`Nrrm<7kW<96eZKktq1\l- rjVM&.]Xw\Wg_{hwvw,Ta!iF? Oxc>Oy~կ$S3 20ipO֬NQKK7 (ukx-Z+{``WOeY!ZZ;c!fMp ?hk&|vqYzw-ZaVW 0|GH=R\+lm0k-WeYARn'8|-։iT?:Ұ- NHvU_$ВVWBI_1l۳ǯ(#jᶃs 7y!Y#z׳ռWFUu?wrs]>eK%i$lRR7AF\?>n4+a`ڢ˺Lv&w6N;U~_x _ j4HI'z$ kp4N5sK)-txG>H.$ݞv`i_/eܶo#_CA?̢Fmmֶ~|>/[tG5H?p-<4Ϲ8ckټ]óz_ý7R[o6I^POՅf|~ƺb/>"PGnH PӾ+aqx[EPS0GWs>"-ƣ>hp}n׹a[Zv|L15̉n.|/#擬X.Ǻ]W{clp3iRGlN4^}n~[h^|?C[Yc=Puv7wqotAc}I]dRTx{Pv m]?R)VGuE~^񕯊fJj1OonC(•j||e wccM&iG;A pֺKĚ2< ]SkRQ&5Җ׽zGR-Upw#s:Ƹ |>_y^,kgd--_5)KHǛr? ^Zû] ;xc5km;zu_x hw`ѹleH5|e;'^zPѮ/.$Vy6^oyKj~;nшX-#th>*s&oi^%Ɨcy!& z]u=GdMp#̓H_kV~ 8,nVH߮H`aQx+|3Ǥ6j./&m}DZRlyc_K5v._9Of~UsDOgşoYGDHqk|>~:PİX:67957~mP@\#jC|e? 6G?n^+uːsgs|o^!&[6qȌÀUOzVz_[|*cMᨅƋv1"{+m>ڿEԅՌ!x@#Icշ'ij7SՆO./~D(л"A9<ڷuHsC2ƪ B(lQx&geLԵ{ ֛4юyߧS\aWn7G[hmB۳}vW#qRz#EWfjJOc%F rڹ4(.ojC-yhd(8xR}ݎ6i l#^Bwv# |7<H)hII. +&Cۯ3r\`bW_o]s Ŗ0mRr7cr3B9P3OSZxZ~yc5]F̸>Fwb~Q_FGj^&]ysGookS$ G'>Z= Z,?-lK/{A{)W  ׅ~W|7eNmfa!U*nǀxCu~Us~ WI]O&o$h0 O NNh'Tsm}aݜ8rH8J?b?/\ u4կ\] 8sxNφ5b67giZHԾ/817BF>-?eKk/2F.[mp g8#/~xFMfTyV6ڇw&ߌugInJЬ=F]<:5όq{9m;~#ҵsQ<#]2mha?m~zd:DTzvW'xf LPKQryZiFodx#p23$͕8]JZ9ύ?i:eJ_5֡<SRmײL]XqGM*US ij|sAek#9#>xPƗAo>ڸLޘcW[/xcTFxt[vӌzg ncxF̸kWiQd|T <bִud`+uxcʳ&Ӽvk2\\Z6NOpj2zK|.K_C+]>eTL ^s=/#RFh H'~mXf@c̋+⣒VS<3("0.$0|z%k~UiG;R_b#F?=֭Eiw$N<|s+<[J"[ƨ$Vh v#SGq >}|+u?^g7?r|G̃Өa;W:'_jv֭ҿe*W'ţ|N|=5&r$C>k#Ii{pRtyZ?h ϟ#Gr2iq1A"|Ϟʱ j_AcNC[ 9B7^7}+U^;). \ԤxK3.!O޺)G>{_=o\״Aki9R;Wk?4+Hi4-f"&IoAW |Y7^ x,}OYq홈V!o5F|ZFi7DleѷǡȭC̼sH$ZJC Fy29_l<'m5u.H\f*0dc">8xKSs-tu6Ia3Uӻτ$2P.teC|뒤uhڙxF/:M%۩Ac2`W }{1k:< c9Iy392=Ex|=GiY!F\mǻcv7| ^iKd|Zy HDg ;}G᭷? ^=ՌP^}EX ԁlO05:36i^Z"4͹c$J>|@|Q᷋ȵ-g>u ֟kO0N6G𕏉?kPTXf/}ǩ5.Z\6Cho2lV}K=ɴg/U[lw+_WiIiةeP6WYd4:7gQd w՟ J i;U6[ navZ cҼW{qm jl-Z)ex[L}rO2\* ss+[zM 3D+dI2>i21\#mTjV?`iNCߗmWjv+|:׋4Ga~~_{Wh աInV:yKEhV.e?%4jƟwiQ~o^#4:lܤr?+3++v"_ghww˝ׯ+ʒu#Nc||5 FP1+WZlWzjbחxKĞ.aC3*gC6g<~(qTj{TٟWO C2}_r¯c\).6~[1ڽ.ĚFᖾi# 5yadrc$8!5;S^x zauTS3֯n.iY[;3>J[F0So+zǧ5F( ˷Ki ʫym7͍\ezuezgr_\*\l >}WpXt[kir_Y‚;^QJ(EES90]sV.Gn .,ǀNl@p:@|Q#~>*׈mo ckg;rtǂ~̚/^Y"xCx^َOٮ<+}$ѫd a]䙝FZoj~:#дuԴ=:K˛;km9f?1+?O4Y;t.7o>A\ a@'?Q[;xVYg0Hǿ5xCŚlp7+"+COR<+&)_dx/Mz 1| uךV$|T>sY0Npa̽:c>;~2h,DFYT#x=>Oj{[mS^k1q9q]rIN{(Aio~.;x_a{9 *>qu jZj#ϟ#)?=ҵ *oȳckK4~Gqȯ1z?H٭{Cwq޳9z=g^U{.so zIzgs9SPqwyac%P y\cNTz?gc֩g,杻A@@_ |pYcëk qt0I a3U9?Z 2ܒhV/;Sp:~}ׅ>)~΋MJ Y2kZtV^rF]ON?o_NP<_X!XeF?!-_ѯmS^<7 Gk䃎9~.گHo'T^%M!)SП_Z<ј?i=n|A5mc5k5{iyݱڽgWZ>._Muku msk?h_''4/V%&K!|~|Fƛlw}v9Tb*I?/⮇C|ftOOz=KSc>6?Z0`$`iFwgFO-lOF@>l>?,עV8lo<o=LEN1W|EY}q[xwͬ-c5 ;vg~!,X47Vz3k I!f ʌ$ĉiԼqË%n.Ϻ+R8#$\.oT[}q}u2}3D9˟J@6f$[ YVIJ3=z0x_ͷjoc%ϒ_];*Le.mSJg/֙o$\:LqfeBHcN^Kv,/#V P$ VĿφztZk$Hf9?P9JLGxSU$} byXl( w2EZNZ'|ji|cUԛp8lwv> _\Mg",m-obQW);Nm.iYoExGVXvggy5Qrt+_4K5k{4i"Wk>9Fu_I[95 IM-%d'<+v/hsg|!|#vs_4JR,i RmXvUԟyFω4;eP/]$Hv|$+4|K}rkSοmBT19V`6?g5Z^ih,`2 Yx#_4[Hd6E#G|F%rs޲]~ro>tԴsVnNMg:7z&=fVѿ7z۬R۞w\_?eH<+k%!a!#0t1yMC?S Ǔ͝8FP *ìC=V m}R}༑#A5V5n-ӯ%Y ɾCynݟf3J"V/".y$unl׈Y>@ַ;vVjEo62 Ҍiz?ǿj>DY5+db*Ob7/,g֤Ktpx#cYY%#MTn?c\ωtOҴUk[ˆms+k_/V>Xk`S!M+aXɣxm5-&Aqgxd<>q<}W¾#U.-c ;Ȯ;]k6N41`³H35G߄I, @yG^puƣw6{4ndx`+_.2h^}ux"vԟJOx|I6ĉ]-’UOk(} 8x{S|&^_C 3QЏjS!u뽌0^D\2~Oz]<_ uR`o7IO,oRe ]^ECuvwy 299u+g_ uktMO.EbF9Wh?‹Ǿ+K?V uYn7P7 c_1Rvg$HdpK{cG;-j@<^5ލE1.FF&R2v_:j W9f*ut c0R_waOi(]Z #FF@+o0;kk4=֏wZ yU#;f|LωS!K r;XYU=zu1Qϝ5;+e}2KM-[hGovx=|'P_W LUR>b?xQ ~wu]:R;2WS&UBvK[~:4;V!VE1 ( %HWZw}B}WsxOV&_ŧ$orK1A=A_o|Ip̫̱6@r~% M0> imqhd ku x{ƿnVmO,;Ь^Y7_#qO474܅e図}:g+x#N^ k[.Ƅ|dD[p>G칮S~#4>xTʨx _`hő6+3[srVF9ko45/ k 3Ei$w7m،6{6(z&]u]#Xߴlp'ix Suۋi.<dmYQ1[t#==4ψ&,4tg26zcXԔ}M=F_i{sq٠sk2uEA*K-ĚLRYR-a`s|\yM_x'Klo YjP[]2~ ː:ۊ_xwQ^i*2wr$  Rt䤺 ^MoC æۤkp1zgP;麄`ʘc90qk|#c@vq̻@{k>j~2{i}Pͥ^P^QRU. j~@lk2cbZD1 a7nMkڝڭջEz+n+jbf^Ehr>Ґ6ߺ}+KywW {+vgp{dy7}+Rp7S\5Ԏ]$7ەX`jzrcTS_O{ Wwcھ~GsyF7F>SNecٿ>sskg;X(fv}~| K=C[~cdPjp'n}XƶI>G^|+9F,϶Ma5CF?Z4GVI!h  +׭cx9ca]lx#3YxLZjOoxΨl51òFy9ֆP)J_g>+%GpA+*(Ϙes,So\̮?0M?u}UH~y#W|nSTI}u^-X-x W9Ty~)Zַ%7?Ʋ`6WҪ\LJ3ӝV,f7wF΋"R8I1v?Z }zns\e=;%ׯ|7W0 ;_.NByɭsIhf>ִ+8 ?Ƨ̑~x;? v"*9fdz }1&zX\RL~#EѴ{x59/5 b?)4$xa~9kK߆ "SլԚ&1%2 2->ݨ'RݤW $6,U|*̜-/ k|mFMg'F k]6??^֏Vy5t [:iC| =s^1EҵxTismo2Yxx8=r1]3ko}xOXHZkm;J`8~?еɢZ%Niգf8\|?JRFQU3;]nmW۷Y9ܷM{~xsJJI.f":x|2OxWР m<6lsJ].]WšuƱlə>a'wJ)ХøWφآFEW{`hT'tYF9o j2xFm-BOVoZ"@܎G?Zψ1x\|]BNMV1n s+< ſX\ZIFxf۔*7<-Y.-ԙA8*?~|eW:Vy+$m9_JgٽkPNu|Mީm[%+q*B>]~ҿJ0w-D"`?|{ڥǂ Wy$*t(s-I|d$_5{KCCo.4Uf#T| xLdӮ>6z<kno M_ƶQ![ WCK8Ko3wy''xf6ZͪXILBI]ך 3D>a+kzla&9ɯ?dq FZH[Ve@.H$u(Vw.azZo{+mՋJֶ369O%\'=^nz78 ~μMm,4 [%wRRAWOk|Gj> oiIx0&NXE:u~V񿃼y焮tyIk6rfcgZqpHa8Lkk=o犵MGx$wmuV@") \#wA+> 5EoG46QEj<.'\r3+COjP|Fxw azןfhǘ9ץti+Z<-7#iYb-cel07zT(>~/ƚ˫9rY XW2|w`pN8>#x;߃-_^KY^j1ۦ$DWݶFx1C]qsq Vvz(fXX2y _r'~˶L&imG 1"GnPc8' K!vReBch<+\şᖥ5-nV{8%rIy5X.k6pMt\+ʡUZ}W׬%]\F,{2Dx'mÞkJjۙT|-wk;[tR#$J x<ƿ>,~ZZ5ڒK@́u#u9}ἲ p ({=S]ͅVܜ=Us*CEvy4 wkǟ_ni#UX]=פ|Z̚Wlm =d2sO3ҖYM@޴Q73޸;PC|7,zf^vE:W|=u}-Yoc5fN^u}bKm啣yXaJa $ܾFHѩ5_[?,A7{.# ۨZ_MWLkk[{xp21gRWv!C]Fg KiHMق)9ֻiɨl K>\kZOܨ$W˞Զh/E֟| &QOAX`~}%’XxW]G2V*Wx4:ğ <Ci6KhSy^n#Y݇=GøQo^YŬѱFv(} {T^UAj&FFKʮdWn8Nzyk?f߄ $;.y7y8toPd{?}ZXif]"y:-gH渰%E(*q>cYulM2?4r n0-܌_g~ɞ[vwirұylr~\]'qk-Y_hYxe=}"+ßi.8\ƞL7̣{Wm*}ӥh uq|>U*HL_p*O>&’ZxWEYMխ-!o޾H#1]4[oe,bz7 ^?Oum~aۚS.+u4iv KC?JxcY=pqOו|LѵO'2$hS;M2@ʪ"3?\ ao&viK8څ|\:<+\1_j14WG"C׋aup~\_־;N3>l, m!4e[K˷y8lVüi0 r+ތMA+Eg8Gz|6G&ҹю=eVQVo2kidek鏆#$Js]."<:jQڦc=H`b;];)# IU_zmŴWiX+}όE7*dzv?y]^6yZlo-pYGLwPi¯k㿍-%͎G+18:['c08r{í$?[o,xc5~ZԬ@U}cxղJ^A+42=:2fÌWS:q:-6`T,7S]/4rLb>2ϸJ<' kgFQzY->j\IWφׇM%{'j7n+}:/y0Fl89^7:_ˍCIUMXn`11Ҿ7ċ~ֵ]zßnSi2- d9}]~1MyҼmj0$Ev709KcJ#Ickծ) ssto3C^^_=,־#gR9R<G[ Dմ3I֟y`3I"XQ&sqoQx[TǗvGt-lvgb.2Ǐi^Ѽb"L|@08 W5? Vv--qybt<{>◀$ּI}=բ&~h-8#nsFϡ5|9cewR˩_K1=F2Wx/!I%j3nWPp"Ow?WO%kjUG\N+_'ֱ|H|i^ڸbT/lg 7ƝCMUY򠙥X ߃xԼ93K_k\i‹q~d2cҜzr>#+>|DݩL`ۈ(n½×u3 *ĘnڵŝkKUhdǿ|Afljzݮ\3}63M=ߏf .ƶ/5嚓nf +j["([RÞjo X|E ۱CEVԓٟPSMx2/5oڋEx/COmn#o$^~|pڏ)!ַ$G5/u _ܾ-%̙?R1r6bu-W爿 Z9$Aڹ_z7!L>2O%ɴ_[*+fO.촉-o.ڼB21>mOCcI))"Vh~%tn;mN6_OmuWNqc;u޿ӭu?Ŭ~<6vPKL=}hYHu3iZ sJUc?_\ǏZ6ljRH8w ǩ⾔Wv^2[0Kq_'@_ Wök[[ {^(gN+sv-ه]_AJ~˟!MV>Y@^o/c`dJ2EҮ[otb7 cn'd_ =|3g{4]ZLd;ʒWk#xFwvMٱح?́ӭg8aJC[^ѮG8]]iKi}oj3Β0'=|s=vz]OB]0{n& $d`潒|l{?xiDfh-W%E&n/ ZU%.Q'cl~!fhJf[ȭźd 6+W]@]CUl!f_}{.flZh/m'$7*ƥG˞*?:<iOC[h**ƿUMrvْU\w[-}d|cx-~%|?8.,nmFېX9NJt_|u ,]kNZ8Q |z>2ucm2Tos <7<Dz V;2s󑁏ʥOs"џL%ȾVaOɹuֹ_i5]CgM>A$rE 9 f7C#˾ gu#ګվK[- vьPJb[cַs[$zW]g{0h"Tq_;_ 6MS 2_جq Pzzz3gշMZ־ ]Zu~mvB`^7D>+i&;xKpЕ[RA$]Ǚo%?j {[_xU,5E&@[yw`Ǩ Ʊ:G&~WlazU~|g'xM~fkU*4ASz{s5yR(,(0jg{Xӧ7Da#CŞ_+5m;n-Λ/Ft[mҌu^[_0^fmav1浸+8'J~7ig}Bk;6ţ'd`?qi"S*gGWq[߈+QRc!2Ua^sů{țW0xᮛLhԒZrP^sxrV"måv h4YeՓ\A2C6=3Z#flOIsZF,Ou!q$1R7c$s6R$\s^{-5'T5S OE_EϵS?0_s>",b]Fթ W'Pg~1-y\\=I;GT٣l2G$1`mˍk^2|>PU\+oz+ő&+wt!ōAcsҽ,ȍ'~^>6m0/ڽܶf"Uyc*_̎(9¦#"+45`RYYx M} K.ߖkg;+۴Pι]0ojq4sk3ݴ?r Yr|_u#RW'5Ѭ=xNJ /D6 ݏr8cUR-q-mc]7mbηpU +qfJ__IrsᕛtQTYIɈ}Un+_1e6X[>7 /zUpvY5:zѴ< eu<ǟrd4h9:ב|I-ź2X<YoU+ꏑm6mҺl==ZkO B6n#&/ id~>} fFZ ƱN+*.1%g!ӧ8][YF@U_\N4Y~ V.}hE!p9k[[ž&t;\OQ;HTd׵|}i'|C5;6襇!! :íkt] Rbާ<|/U֭k>(]RYEGdv:pAdr}W_>oϵ^l<@cc'7^x)u&8Fпf[ o]CAFđ̤^М}15x35 :@T5+߿Va! H" ],܏ xu W5 8ճ C}QX`1cϖd~U hVzjzkV۔WW[iv4Lcw9IV] j~.m"HU7u9=^O^yğhFV㷵YxV%?1cR[N9o"O*8d!qޫr9>K&7+0Z'yʑh8=ƻe.qofc3CxEO-۵KB,,FOi\֥~ak(X#a7]}tiwmKekaq ZѻBvf$:u݂i5O37O 6L􊭕Z[[][q f%NzסqtKQREK[GϧG׆{uȠ]5 ~fi>as4I-ٗu pxZ`֎Fc.F;j$x쵫{?eFuks{OBƧq"F!~R8QGOկ&ҥa]ˎÚtpFhڡA- L@<߾m_'4gSPm[VN>/7fץs.΋ؼGk 7jLOaڳ;osFui.M 2D> xjz%hd3% #=k~iz~k;} 8Yg+-Z4yhA$¾_DŽ5fHwG2!gŚS➍kik"ֻp>Ɦʾ|/^EZصKy„3Њ8i0u=4|B0I?nꟳ_|s՟t՚7S_NN* 4oSD},|8Bt6"X> $|ǯM{׵ cEV\¬VqOkBq Ex͏#[|KO ߻2ˌ1߀[<ިf^gRè4W2k#b8n8Ӛ֚5R5ϋnAMvnjLe]sP7qkS>#xr=RĖ֓k,rXzpF:׶|pk/t u6mw'ڤ+ʎ8yr9q^c7?xhN%T(VhW{rmaᯁx^ԭfKK;Hy ~`n+Qfo:Hkrqt5σ3~]{t*9t9Iy"A? fKg:{qj]dlIBAjwCjxż<vL?_ 4}K/y0$C 2A{{GQUˇpm߂{߉-krͪD"oG㝙2~QMW uY5YMnי \;OQam//Uci$ # |goMo?4H N{ǿ K{}Czg$: 63/l(nQBnaIz|4 ְ~n68~bIt&T`awW$G,y52D1=yg' m#M.Yx9'ߚ_5NŞ/x%y#;d{ gF'6PGrSGFkmFH٣`1?ʻaXU~`ĞeMsH>T{=vOmV6o_^6~!WUv9N|O=}!U|~KwRyqc+{hh׼d5X(Xar4 wvcbHdv׫Kj r4.7ouey*GOzKinu{ZG9}2X\"3+|Nq}sx^p|2 ^ꗶک[I6sϮGc|_Puj>ceP84`qQu]>p).מM񏉾פ}ĺ3Gd y{}+!bD}CŐj.k,X sݻW|[Gez .?c"3rY=t*4Q%x+ӽuCݗ+9%h. nQ-ϰV 9V/2M:¶o%3G^+IЮCeumc)I" VaaQ o-Qg# Xd5Ϫ_%ԋ}5kRiSpqNʪW+?ZE E$~QfxZ]a77Z)`țYH,y[>,"[dݻn9^kkѳ)5:N;{^edr2E,y/ܖF>^*[1 yΧ35Ӗ^+ǗcQKCm]X՗2jVU+ vb|*VE/WAQ@O/Z.X۞'KkATf ᜭvy?)mٯ;URA>|vܿ^s} FnSNnد*#c2|L8ݴr7g>huOF9bޟZPX99l+}5#im۶q{ׅ^3}v O^kOȚ]yg݌wRl8#ϪyU`|Մ% :/Af-W9k㿹:{pȲ`_l}7|CK]=^]1=kqF[6~xwtmL~X³dXYnuU[z6Z-߻B܁^_7"̸ڇ48Ө}|KW%\glPab7\;ŸGe[c^SK̭>n]5#SN[BZH s‹O~YcL6Os׾x;⥶_sWvZ!UT0Tikqo ?t> -ޫi-R-fŚyǿo;Vi&?5Zi\9qrI+OVEx‚7duE8*:|Ǩn[:EϧaZrTG4}Ÿ>#k7֑]5ص"=D2Xf7g<-,sn @Mϔ#m ?Ocһٻ'yc [9Κ=rO^AJ㯎/| ^leFu\ RpOLӌQ.*F~# u?jk^Gn]i胍؋) .:kCB&l 7nu#%0;q#5?hZ֡84Y$YUC28<;Wo᫄ѵ/P5":|rKk2fBNɭddcZj~g0>Vu Ts?cB_hƓH=-lLRD?nL`wy!Ce%jߪioeUӕ'3>x[o :;#]1ybcAgQ.N:b\њ6-Ϙञ2~ʚo|N.4VRf W~by.Uv3?.!xo^],,}q_ FѪInUU*vcS1ZoW.4I&8đˈX|5?ծo%aoj?|ak,YWv1ּR}RI7Hպ==95\ ҹW͎:a\jl{!7c^u U`_2$}E=@-+∆-<W]IGs^%/#]BYGQZld60Y0ZQq|nWLlFNPwweyvy=zWϾF(|=v_'&yLƓv_SV{Ƨr!#DV@f]M%mр'5;/nlk#u[ciw&2!2cvx✾z;;][EV*x~5 `.ذn*z< l;W{q_ex/Io|9ӯt溆cMY:rƒ .ui==+LyfY|خ1gLѯ[;_]Vk"E}+g>Z}k1?g6Qw-\V{Z[8c,?ǣpOҗ#?h*iYdd@Sӵy#xKhas4sa嚇=R1]X%@GXrNٹx3?h_^×!~p8YǶ O4of{%cFfy?h1m[88x5bmy~p2')Ē^`q^+E6!+9#Kč#zqֱ98'מ2oe]ŝͯ^M,y9pxݏ5Z;.{fO2Hv98\~=]x6 :I'[;\f߿5xxvO6+ gqC`30)=TS>nGŸjI[1g_y3Zo9 x#{z-|ICP[ n۝8`k?)>n}ΒƉ$?Ìp 6_Sk-jk봶ʧI vӣM-NzT=stUko }7$_,L/>!2iga=..7);82E7|;5ˏw^ 6iyڸ_ ֦%S[x;;M'LmT54r&CF1|\w_>'QWGԴoˉ$;z|+OiW Ngu 4JhRM?3IUko^ ڥ6m6Bp~2?^+8evS^_^]]h[ip\%ЅC|>e!H1_YxD|McO񕗊Pp Q(,r1f/e{N5,_,>b9q^sc޽0xv%եY[j&(CkHve.B[c~`H=={/xj-2k{aFG/PI|x鎵h=ׇa^Ff[Z6!+M+K:6¿MuT\h¬IE,O#֥a'>&AD籉Z,ë(9[A}k0h'ڶ[giF~b8kTLe;}{W^5m4ƆGdU*~?aE?Jw]iմzwۿ%V>ONjEӥ$i$L05xW8FDɥH 鵳\>EJGtirE_|F_/&u{)pU{(ByYƥuyk (8Fܜu ߭r] v.c$bnTXկDSE{ KvBU%*iyWlP 4 :ImQ؆sKc5[.!31o2-+}{HզN C߭823+#q:y!`+F(ǿ*PkS͖Tn1z(=iHU;~n xy2yGJJۤwm Y|ͮvn0‹k{RV;ԏIW=h]*=ꢵ :Ax kYV >sk⾮v%+'wW/Z=FӨ ,IYyZw|fwȏ t%.[nJ-}.+y5ryM+~WQUύ?ys4SHfʿ/=k׵E~K~g$\rƣ6hʯf+{?=KYkFxz)mǞ2G "iVII% m>*hr5Zl[t?Zƛç=2tgZQk~+աh޹V[2mfRiL׮X9廃3l"bwOF)Xɨy~ir+菃9{ /ʽ&E+׿+ɴ }fuXs}{E5|Ϳ)V+ ΊR{]Zi7zp>_ qimi{ۀ/[8#־?}uV ^exխ&!-5:S\1dwjU~+o;,amz ǯ.RRa$jzj:6+*\eJjU:u3J0BSuZ6s)^s?604f|Wb ļ=c|̣jmjk ?I#&݌`gsz ֿ?Z^?^Z7iwY݁eybk?ߵ֓?~Tw k+"du>PM>h%Q# KӭKs<0MW涽+ h晥O1^WxPUkO;oM_(x^7vFI]m^iD.e@^FVVl1k!EѴo#c3Ҵy'گd;yWYψr\yZYW- `o5 =]DI?i.i; \5ŝڥ>~T~y0o0*֮4(V|nm_Ķ*˻h$v7>Ij HWû 5zr4̧澀RS^oy,gg7~656xQ cy$$u߆M>`,O`Z3O3`sW׾kڇo435#N1cq/Ƥ.$dWBrF=+>x6mmTu 3DE-/VMF\_7=b8ytMo'PLs^>Wsxm; ;˩&AKp}-XK;vu E xjJ<=56O4_hom1Wc/aϦ#\EcP-V|Gsq όyN:t+xE~coCi^X[fòLq ׉>֡?ubг>Gpr3Y1Hń֟|\hiPkYc~w c|z dsW);Qsfj1$-n9GF^xNrWƾXdK[!}W<y_>S_ >#pYx)5AI|[ɐ_p1^l=?4jEԙmu U%|8V6Wx7M-tvXԡx2@HNXt\ިAi{ \Kw0_2 $x5)4xz塱S֎8"<#5wfƖrm!Yc8 E37-Mi~K_ y.tby攞s]eA&to#rK/!`SGJ[x7-2k.iNhX,*ۈ;ss~kfX/ ~kw8UݐX|։?xZ~/J]MF WC!68eh|HO4Ow3K>I2)S}ֹټ+X<p|yS6:{g^W[.,[rT=K U?u;.3e3W{&|@:m-K7b#9;q֣߯𯇯$ [?%HAJ/]2HWԗ |@5 rM:K/o ZW 4MX!7nuak({]\gWrhkV3! m'n[O@B& 2E+F(jko,F7}x7Rg-&bn7t#4m7Ƿn(n^ xǗ+♵_;U9k|ԚWe.|?YNъrOyֈ[+[Q^iLjSNtrU%zW|#֕o4-#>Rx7ch${XzZ_7-g"I}$wbU>Q~>]e{]_Jڬ샐)ol`^wūֶk}֬Oc[ޅۦY|y 9 _d,iIռ!jۀۤS U_,:̷r+Mz[]yOB_5 fl ]$ 8Uob9onf[ :S~)ku{,Q39e Oxf9,޾$ dcB*1I+߉/wY}+ 1h[Qެ@ffo嗍8yrQK)p쒻Wg]Exy*=+ K*}++Dq2;_Joؿr^-[ɵ[ڼz$u)6wzk `-%b[}qՕ<>LSu+#_|/olu?ğkZ$A0R0+mM[6h>RC24.͵r|+٤~i?-iW;6C_$4-qҿZhv=yV%ݬ+l˞i#UH?Z3NKZeY];Zh;N>^ z1ƋDM:V8o$⫿H87y{x.woy-<ooV6ڝqKSnT778Wʣz-uyn.?˞>l|(Y ?jiM2E6ߗ~Ora$1+w vf` F}10LBTbI犗Yҭ5+u}v:zDbxbskȗ͹]G\*Z_'vlv: Ϙ[(Ez[Ydc -aw*VNn ^yuњS{f?W_όt6;W%˶o^ȼU|.5 _Hlnem+^k_cVXq%<FM6+K?yZޅ*xҽCS<:J/}GaՏ+? 6ѼY\^IM%c ҽ;5>oTbxǸq迳_NBh\s,A#T\gx~;/'gç$ROk 7bBs,pNOZ{?!5V8.lfq3MWЭ5[O,,,巷mFX}6ַZ?U<7mqvEiv(=H__Oڂ$6,:-"gkk2x26=$<ͨn W5UEuCOuԦt~O\㯵ESNLyiǙn_ϋhfMxd<4P"]å[gmNV˻9WT%uŗ{d)ǖJ'[e9f犅\ʪ~i)5‚XLv'im zAʊ[b".m?*mBnJuiwP2;𯨾ZY5dsj'T}% s3li|mgJч@uk_mmg9=XME_1 =Y6=W+}=2n:σ.m|Ig5+qcgyoŴː0p1׽~|Boɢ鷶׫s!b!#6A$ |uofq|1,d9]Ib6 OĐZCKkH[Cy7>՝w2ƗIEŶ0@ଛ2GWiiiiClAPF+=Ep>+)>!xn|qy.#dv )Ox)YVᥟyPXw!Pڗψ&íi/ϓ~ʒHf^rsJ|)S_> 7.uKFe &–zv"_BŒjİYpدÂpI'Zko|o>yZǦwK 2JIc)}Jv=2_ ;W8~/nOG+5q!aaa_ʻogcK{/\o"w/UqcuԾΩy^z Ou$,?$}!jEG^'nU{2$lV%m ` $:hqx\$-cu[IJv=O G5ͽF,"gluWnz B3\uzW m][Zvc<duk쭴.kI)ǘ]{y?L5o-ӭpJUPE؀ϡ(h{~,oyݳ&)O3]8Դ?\bf]IiCa?+=*- Ěf%W6h!d-o8'޵džu|C{/,YQ'`  XKC)>!jWn9-HHؙFGɏq[hW>%54ZZCnqι=j(~"f#[XBczVvڃơg.3އ1ɾ7 jԯR-BI. θx[sڶ,3_)+Gz=͗7/t|`@'Gjv$7t52Yɘd9 Ʒw_i7NmE=sA/Ե=}wFŤ>bka}xPc_2+vر`x]MTv7smG.[Y/KW ]Wx2Jmջm.^{W*mzdJhpwU>߅úW-2>Wߜz}Sừ]C7?Jg|) Fm$)k)tmwCmN;Agvu_~#L\FdcRPgU ]j,5Sw1G}3zo|*S+}l8rd;_\fjmjQyW:|gN|L㵫Igk;ry\St/RCKdF"v6Fܟ0¹XuuM%f uf9u^/,viXugP+tڈ۹qT-߻[W>dVH[nmNir/@9]ߴi}MO#}No=Ed\:Rƻ"giTVݎ{O)ZŮdu9ۆpk+QK&pu,xB^2_3^J><A4mUzcofgv ǿ5@:'cy13JϗՁ+:2ݐd)ra f\gy]nTLׇ?0y˷_9|`Gtn^ m!"8y+'~֬OȮr喌QUUƋv]ŵp?6=ozo[n_8f^TWh:mۨ9שF^,zmפxV%$[n78rکxq]vd誋[v8eq,,(sXf!nTv>jL[ر\/Z:.-6֜-ą"G| "}vfee&=bi ֭yc,۾kO$Cm]^=<3YxME(ӈ>Lz|9żqOV/fy- &~;U+UɣhrWkv|=¨.p8FRh{#{P쑠 +|G&>Y%YJ+VI c}^QM]2Gb*"FS6>V3qdZmkEՕ*͈ƲN<92\\amƼzM3>YnqMƭq:ؚU<5xC{{{j dzl.tx0e6h#ě'?՘[] 4y5EkI&swWuɵ=GQXmd23=x+&l4J]HUAz=㯎xD2ݾ vk^t$z Jû}_"ã2}R$D` x=3Zo Ycx# h1zySUOC|)`l>dV;&Kr㰭V8qO,|'/oI<-uvLp&#$JWV;T]z~| Ki,l\YI *IWē_$1! l6e_N+Kk:φC>foHXg<y'E6|;寓VC/+xcL gwז#pnybi5:n(=&ѣgEFw x.- KĶ'P[ʶchr7aTh״-wyk*ry`’x[qw /hw {c:F[baq\9mϑh?!>}i^7DVq#sD9Q voZ]^Mq+wq._- 'k?⃎J^L8-|7ŲxKdVyK9P!s0F8<ף~^;dn+Iyq5͹ݾ2B{WѿWoմ$k9F.vLu* qڶDͭ#=ϕddꧺkWexg++MuY<:ô#E!}O)}cxQ-NŨ>qcW|G?`$znkm.H*A9ںx_,.o,80 اsq,N3QYX淬q-28il-Nk(nі- ;Q+-FYA W|0MjY5|ep"wRc'?OZVnkMhYO) {L}_ k -ֆoF$㎢mJH,.h|a[=8x~ϭiWZᣎH?g))&dt4Ucx[} Mek?l-ׁqhv,/!yG;^/eOxRMMZhsOֳTRMWYrZ[jKm੮גk,za$#>i3!l_/iK]V߽>W|^5 xIDt9y97̦jsFod6W z{FP`vwGUt*LÊ<@II#K/JU )pLSU6"h+bF0u֐@F`z;5`ǹ7*ϥ$+"h]mx|_>ؤsZe]27p~Р@w ǟMY~wCҤeR:񏽀)V2D<Ѡ*[h#^[]:[$ vZ5 gh7cv~{w(_»ҔeP #rZLf޿^i^]H2G5_[,&{+OZڝXsէqڎdf.JOz.4 #&FȮc=kVAUs8S#sngsQ}ݵ_Z}slnZmkvPޘg+b^ݾܶഛKmOK-)-UP5࿱O#9ovYcVܫWҨD9޾w9TG Zfingc;mT*ݝiZHO-ӎX*퍸VO,m Ehai7-(nNZ,?ЮmviS4G<׿|mxn5jH8`pZSɈ-ifN*+pu.!_*ENc 当9X紹Hoj˚;?Zw:$>zp2;II Sk˳zt *J8ۓ]o TWB0՝&d&J3߻j5."m5n*WEJnvu(_7g>> 81oʻGq/[`| +줵2q6힋oKfVݔbOp>-0@a}>,hYA1|?'Լc G=Jru~ҔH#+!;~4nfњ:on[Gkam6ƿZβmR7lQdjSqg< kz\F'GGZ4Jt?FnBWûa_hJw<^CYTX27kâkd-YȭNC4+ӴmkqH%Yy?cӮC1D]bI;:f: vtp" GuN^x&x⺚@ѫmU@18(mc\G|jxđ%Ɣ -=oW]M^Gkn,uk{i71 ]5꿴4K;R1q#旉ZAc\_ \KkZkxn8rHr8hSmX43"?ƻ_ Z>"\,WMp^8_.|`4O 72xI௯|Go/7_[\ѫAK'ž-e "XFz}kOvܮf,>(j|M+ggstFF{gO˝zW4#v%׀x_h4FHaU_憳>gu9%}ņc.$U2ZDm^oۖ#9A~nHɹG8sNU:nqV~8X1]>OL 矖 o _ [~"i1l+ugkTȇ4;%$Qٱ/\U#`!~+^;lTȻ(v]{&r6SF"1EMJ;$K!)Y|ϭzO 6qߙr=ExΓ^-j46PU3wW+w>md/,X[;%j樥=;R EB5$yʸ>~'.4?w;@Jzč>?.8 \ յUeYPuBzU5О[FC}U,t XGjpLLX׌s^=MxX/4x~a2q>:~ktRN${r+bjh ۉG,q rz؛.|Mq $m%7F=GУ<ƹf[խR״9<乵'U`0#q]iƠ?a1w\ s  `Zo^焼Q}][Tټr[""7<_C_ 7WmtV*ѷ k$ŋP!zқx7Si]yR8A=ީ^ ׅ.~u =]IZpNv#'<ūQ~{9懀p#=pj/}v1gZ/|H+a/=mwLK(#XFqto~q}B3kBGOn=6ͤEPv<[qNM V:KĚui.x"r`]Fyx~N4mfѪ#}kuۭsc/[$&aJv3^ͪP<֖-c7W1 b3Ԏ5!J6X<;[WPIJ)1*ccsuxM$[]2MQI <dAxNv s%-%P ks8A5âOeZmݭI8tӵ;s"]rLwdPJ* 0:V;[ˈ,(dHI Y dT |QmⷉnNynǼy }1΄3AijF]ο"/o)[)&Y6s8{KSE䀥fBV?-i|ھ_Y]Gi]ְGxnO5ߧˎF_;Ǡ#C5N\" %&Q%u6{k]ŃN}*[O-Xo`u&%|, # ӼQz^Eی4x\힝p+W`X  +Rfyk5 #왼q}+_}ߌUts}ݥck"(zcE]H~oiHZEc|5]Lm+}*q\/U i%K:su%*a⟂^ 4}Xnu,!\Ewׇ·:&G~l}|G|҉&ao?Q=SGKxN&invSs0u}Ca{4HLݳ֭|od |MrMEkǺvZE^4r3;Tm,cPqy{}=G#Fol*D 29'zq(i[nY'nգnֽBd-|'&"<<zΤyOW>~-Ylb@.`]sѿ:/_EWŋas2r+N M6弸v{`W+io=Q+$"\u WE3>y~lz pwmmW} f$}=&h%x4m=1T*)HܨbVM8 !:cy@|Nkh}?NDy!{.}}kc`Xʳmf=B>Y 0{w&"/6ϠTpWS@B#r1OtYaSި~34%~ {O΀"M CԄ;Ucߚ|{Tϖ ^u9*. Wߚ}T;N>bˑJY͖&{J-n.B9܊.$i~l wX[5۵<}kMq'$f_!ڭΊ|byJ7 &`w~m;mq5CWnUWZJ\ n\'#X ~FI+v1_C~>FCֺ̒jK~ Z>J1;q]UvV"AhV>J7*"6KޕR偟yuBR]VaZ@˰m_\Feez*1mJmŐͻoևDF?{3#!~~aJ% #n8?JZŷ[w]Mb"im8~6|3m]WdU0<־𞆣R+*êk>MoiV^]=rU,;2©Ǚ#Kg$ww\QZm?IEy(!zְkW7-P8=n=COy>{S%[#~󺣌P}IM{ۭxo>>*"3ّ>T=k<[=GյhYL[I~66O˹ɷ J3|Ԏ8 ;>FڧMX7w>:Wǯ㫽^+u;09}H~2-u95TBuy+|Omr'ڤmL<3}N WxB|Amٺb(MMM1,c$g_,xcv5:ܯcZ6>Nt}GLjѶ8ZKMڼIvUD*q^:Ո~i=F y]sF <]n95EXLdA_⨠`K}GNIbVrͣxF?Gwdض`Vd,SMοeoiS(=y5h]~ifal)5xo!0=xR˹,qgoxZѤhLac "ӵ}#a_CqoJ%5+g1&D%+>n,ڗYr1>ksʵރ.ڜşۯKqM6aqjX+8kڃ~"1IÉrFvZ7_/t{L"cExvFONnXm?Zq!s]I -#ghU_"ͧ.}Ed1\7<_k\ rZ.wm+Mg*Xr;ucIFK$r^_=Z*rr) eo6W^uD7^0F-utT=:W~߄,%/g׺*mMbO:~vҵmXXSN\YdF+c] |%=FXakτ:X%mdY|U \_ U'#5ɖHxcҹ*]jRgQI/۸?vd~k4kYG<^/˸uk9_)4{Ukk wihxם=\-n]q~wF(7VVZݓSY̫71l"}G%}"AhRP9O"$tګ~(_B$sy&ۗ^3ԊbBum>d2$1l񚠈ĵw^WoT02n"N`L}{HdU,—.+vB0q[Ћ/1prq\F;B!֎O V^v׈va*EE_#3BW/pk$*1qn,t}UݾP=}<ҙ$$e*SF%ݹT[p ֳKvS*qM])-ڗk{tc-W]NJZcyB0iڹ]qUdWv6j>#|_J\c'itbX R|la{pjDYf*.0M^A&.r*LfF+jyOCETwET21:,+.zӭ!vowv:1OB)|^n5uC(]HrP(l C؃w)nb6Qg+1M<3rOދWm"Pw7qRg)sb#TR[շ`mk3@@\鵭F 2I nk?3(m:gmiM9HKؒ^%Ǩ+sN*]ʿ{ 7/.<OR9O;lꣷKs͸Žer@˝G:1.2OFl+𶨷}B)2gyi"PҺbNƳ7o"ji05R=%6n?y;="S䬤H"*qҝ^5Eu?JGtQ^RIq+O>XƞZ6;q׊_S]ZO7^y^X)T*dmǥxb[Y̌qZb*[vbu?ݯ=v%W%XnOw.Xh6*2קw A vmGNҺٛ_H,v ~xF.yUߴ2 .6>_ddm]ȭYR x"@|#y`X䶷QۧL_KwNx{ L1c`c c9ҽ.Lڰk$@ES_w]7HcU$X1NQG{ֿ5 ^Kn_)5ҽfqjX 'XͶFId|㟅0#B|~_ZGpp~Uq_+|[ӎvMەx:WXrz#{W*IlRw̟Z@r͎=kk:yp0Wqw ԭ%|F7ihtƭg)X1RCk;k^uĊ[¿6gJk{,Ǵcr}ʺ9?96$&9YwMzǣj?p X VO}?+&Վ<3|-lKkp̑+.i7Wⶱ #9I I_6AJ~>Lۛ)zJXKV[G?G<~y߈-u.W̸ă=W:ֱR忴|5p!$Pl<>񜏥|K iZnNTW|nt5mVi\S#y|7&m/-lp<~5J\8I}oHo3h}Pk/^^ZBKB;1 adr88x,o}G*V(_-,ysԾb{W]r,1=g,vnv^GIw^H5Qqݹ- I.ba]ݝPtHʍ s:/m5mG@_1L{V0zE K=,wO4WG&VI02?i^O,ztk3|=Ec;8& aOS^T.r(Kym*¤ΫۓO= r[AʷlǽF#~mG-tQnX3ڽ Ğ,u=J٠ q?$, xr~GޙuPL뵒=0翵zǁ<÷W6WVe$v Z ]\jіbd7\U:dT wMCGm9lcg' ] z?,^mګ(a9'8j8FONN3G.c[xY`qҺK{tl?$o$f018ɠxuĕk!EҬa!PUO#5\xIB0xɘm{7AYF:3XSoUœ.inLA~Out̎FiUrqm|ڞo\EuyI  oZ.uZ;ќn ~8QTkğt/>I9!h  灟NM~x@uKOItc7gFbzWIfx1o|?ٗIkQ;)muͣ {{${ӿO*~@ēد| m{LrxSC׸|{yy ƍY'eW${csכ<ŷ|AX~}k)dBYw 9Ol᳆OTR4E#rw2nJrN#ŽG} qBO}0s"la)7̈w"8ګOZ ,4>~awnOӔHX ƭ8⋸1?2$][W޷%p"dtF' $^>͍녛pݸr 9(2/vrqV4'-,Fn]_x0ڴu}U<զo6f|ƐӠcJVFq?~%~{IbM ^+-k2r>fᨼ3oBvSgn:Wg}x/ÞHTqF3sgu].HQnwC{oً@m4!v'*s^dĭVcJl#Nh~خm\yN[Њqj]15&fG.w{՛ˠ}w/z l#s@# oS"s cZ+e?60=*xcKt4OYN6FvJTBoCBH͂0;~F:liD[w sb-Yc}Ǖ^y>^&uSc֣Xybsԍ8_^/2*v|;LZW~ҿaoY|g_[Vie6~wagZ(#F/݂MzF\n*--fT&m0_:#IU~ff+)E4|-)nQ|ߕ>_=ٯfƹZj,~lעUKS~m-۹|]:$&nv:޺Uxkզ.# e,I=nI]V6_9oZ撔hSwvs!`+ce1YZ΀78x2OqKrcFXou!vWѝ~p~?zkI Ş=nkXXپA^KG1Ǿ`ܽ=N~Tڴqhmta5æڍLyjVVS{ר||:Ibdǘs_:U|Aϵ㯭wFv9*ӔY j?m?zg hGLx}g&Q&핒՘cV~xoMwx٧r͵'+I8cJu_Cڲ/%sbvt}Sÿے=K<ۙCyW29~U޻ڭjۗx$ڹdhqwbŨX㜐Ozqڼ7v#dr[w ԭ-?xb*(zL4(u~+Y?5 ђ"#D*gi\<3Z~iXo&l·fto;JRXy7|>3Bwjsrmw9J|*k"C IQ?}K5o _lfI,g>@qi"&&XZTː~Ꮵx⹵2̷H$IH93]Pox唯#kPOh\,a?=)zKyR6,lNN?5s_n$ kݣ0 +CG{iwQ@4; F^̺-G䭽f"_yǭw 5[XVZ9̠oex2>_h>Gwwzha+<#QNJa9\:N𝶇wj Mq2Npp:*ƻ=힀b&'nxvz /N,?y1{ƥ\sI...(%<z4-]f*M\Ln'BH 0`cszs\3,NL};ֱeYB(dv+`]5$n | ěJu{bF&bB둂Oq*5K#' >9z;]گ$7ңegd^ J%7Hpϡk3yo\ Ǟv*}%3JWy&nSqNÜ+/Am ;={EIR[hۧ'95Z}GeIsunZ8Y帙Hf,0vЊ^]2_[kt͆{ʃ֜d-:6kփ{y$65h_F`r{c֛MwMм FnV.$/v3=:2-lWo$K38sAZQb"mVAY$~=k AauK)&T%c^w}e7Z --qpsfH6mx[Ҽ ?7Q $?/sP6:}gs Og$;>޳|3={ChhhW|mM gn+~|վ7gЌ1H,U<xs6kE x[r~l*=6vZ>۩O WUE~Y.lVqBCu:ֵqQ_4v>֐/!kѼ9A2I$ZI=G~i6 qyWt0{WI/Zi#;U=# ;[?Z~9p_ڕKfmO\HnsqSi2]]e[b#56?WYiWX ~q0÷܇{^.i8Y9?kYGV;ס|6Q_O|fëC8GV?_?~Ba UɱUX=}K)us^ lU76x(ӊש.gѿgv5kaD!ܻg*[U%}'ؚwi5׌_kUo庶`:V.vW˺%:WWWu[} ߊ?.+vgvg->Pf͙V_.E5"V;ӊ _ G5yA$ljgf3ξxyy]qޫ}ǝu)5k~;vMS>w#n+ t()fv>I#yD9Vo}j7 |YS3j# ]E[!1n-svڮX LhuЎncz ƹ?,c^OcҬHݳъA+GAPYN߮+cKG50')ǚHʵNHC|L\0G8lWKyn3(\]ꗲsN}.]['= ׭)1iIy_ĉ4R$i?¾Ӿ0j j/*VX%&Y[- ;N9 c,cz~h}V6wk-4=0>\6uEI ^ǓZ%M%Y4ʰ~A>Jºlj^;(f-$Jȷ~ 7IOsXյƘXm6=ץy?_ɧchB6}O WGE?o ۄ~oqҾP~-Qy[#4%֡׭ N(nZIk>fY۽^򘢻|xU5rZIuF䜀t ij$3O/W$v*T^e~ hOeTW|!t5ԠuI[RT.L㧽rno.dS0I9+K-= v^NB/b/6hP\\)I$ݷ QS|1c4w]ϲ$eS9Mu(džd[%d>$ +ӯ,t->QM׎6Cgօlz^6 feF#m_]-kZn[˩U' X\/$Ǝ'cm pHZVӵiPXA+{9Ҋ:,|5stEYx #~:'IW ?S%ɤյTqoUf?i/rjxJ}R|mnyiN#9TGkMvo VEZ9^kfG>+Vۧ3'̮2Or 5 x;N4k&B8ǵ`jvv?wDmE%m%Vm0sҶu "UZƤ,#,peW![}Zy{{e=kXmQ$v/iˠk=bK;. I~[߅-J8[\v6GӮ9cCon~fCMBMp(Hy%3RWӞO XYoqx\>cSgLֲ ۺGsM𕗁EvkGk : @4絸|;o6p|mB; ooc'o}|oƣgy;Ů0|P>w^$U⫏ xfbp\yG<exWI5÷~MOУv|=`tlW?jkg^efimnYj sOxcER;[7ֺ$oI5ގW|K=;Lk[ԏ-yOk4Xg^k =KZc, ;8k浦kt&0Vhʏxf w\qC"cx~ iRxA5[J|;zqzm{_-kB]{|B:Β XcD"]^CX{Vi{cĸ*Ѯu[VhɆ=cX |OnE=>⹮tSϯ`ϡo^;{~5謚 ME|Ě5-fhl|gzWO5a#E%C| BbXbed{cR#̻Ax|ъKIHGmnra*۳P\J+'-Z1)!ڻ+_i;#^ےɝvz&KõǗw-7I,d^|.?6<[rֹ7%lePF ~}+?|V;k?]"m"#eF=+H'o#^W=?ǎRkn&Ef}o KG_jz"XB$].?}7ٍmLgczӸԞIĎs^O/Mr>.0Ik(. { ⏋/*Y:󘙘¼&&1G[*d^_"%qB+&W{ՋϠuLv+v-z'Iѿx$Au-MBuzV#wmC\tFZԖUqʪNӆ`A*[2 '95nGk|wڣJ>"Kh?;^U}3+/l K.1{խ'ĢMA`  R33ggt 9-8^-+>xPG:)n?j^ }Xl9+@0XWFo5Mw>|5W<(,Do_&[dڹOj;Wz70{z5Y*v|I[rZ39rl 7ZѠy>>b3E%ª W-4 !+͒yt42Ňٯ.O&R˖cqz>keѲ݊x۶k؍wTxU+7; #L e52W$r\InF/lr?|'yc4;Uj{̣Muuxي5Yz$簮>=EL ?ykژ!<]6jrkYW#yG^ZF2Z(Y$Yf507~7mKʚiԄR2}1U8(9rM/GEcAB?7V[o}owpc" :2~nzZѲxV%qcHҧL#i3Aw/=Ar={epF®\떝K=*Ҡ1ir}iܣѼKC(իy\>)R=,Zu+!fns.>\ZU\i^/@Ÿ'QvZ#[8>6R+,sq+bmFӅv =' d٪a繱K!/NOGWPY%­ovoZ4 jWWW||-ʏ:qYQM̻F*m|9@ueUD}}Zk!D_x,j%h2,q c椋:uִ4w_Е fӽxſX[^75/ҮTdXyozԵE9"E\kGlu xHmfwIH5&S[]CKX&}2 3WgؿiL#B-#6m"jD0 ŏ~I/N#w+|x*s.?.rs=޹*2$zW:c)ǏGmrml;TWǷ~1ZUA1]' ci-q?TxY9S\xh-㍶**=}+:#Z5Ƨ=Gn )ǖo|B꺩,WaҳxvKSVYʮaSMzGKIx^KxxLQ=N?~LXhaW=yzdg[o {$ɷ'>'獬"l١V!s޺iPS1ô=ţ9?]ae߳78zVΙ_m[KhkGO_ |;&kIunWgtt?J|hoqEU-—u8oFE4mwo/Ӡ¿n\]g70zWsxS:CgdJk`>[YuNѤǞjo>{}#kn:INxz>M1BLd_i?ko'g "?oCҾ?yj=lΓg[o@MS#Y$EuRu6E)% |A 힯<˨dzT$ԤԤfYپ7H?Qڼ[=:[&:uf,U J֯u60"6=G=gӓm#ٓڌ&q߭}s;F.%;xB_~WR1Ynok쯵Z p{4l|2\>X\YzKmr/*r~Upܧҷ||I~.B ְV8+#`jia'y "OCch;Ď0[#~o5,ZpHciF|ϙtsq^7{)g㝸8q_S|Mei^\g;W׭|OGx{Y}I<6@oO;KS> 5ɾ=ߗ A$w>Z8ӓ.ﳲ)U5JVW9OXq V9Sl~k<#z!cuQ&imomϵmXp$oּ{@ܡmmn0WIJ0uԉ؏SH?lNbzBDoc'G.bEK }Ռb)H+oK(RkFq6ă\sĊO}Z^5}09׽g%woyThڿĽx]e-$Vb|_|+uCd_j<+);9fnc5/=sÓ0XDwc׬xvo.Tݳ?{xuKI@]qZIlP4J ݺ/:Vu+;s*y[VWLvwK Oo/dE.Hn&Yyl^)2T⥷s]ݟ&5+o7o隙aFr^!=- .݊N;*|EYcPkr޼}CQԥd~ qW4Ji{:$˕m.0= wg? D#5tPfU^.7sU2W;mV=%F O G%cTv3ָK7VhQ x<^TYD#' IgPX؞axH$2k̎=R˛rmv0l6tb6V]5ѯ?{{*7$jv[Z|2Chm?ZQQb; 'إ_KM+iy.$e~]ԓ~̖+9[/] }+^TETsҧBܣZ-֣p\xqt<myml OG xT-^ٰ8xrX ۣ*fiޟRWs>+Y2P\(` f#ƺN6&Uͺ_F<2?n&gef$W x^дmN%Jmڪ<57^𿘑鋅uz_FkJtJxgMt!Kw"|ambc< R[AK<2xmF,21>XiT4 W6{y·k4zׇdI#/ _[Yƚ̱~f9Ȩ[6zkkdGlI7{/6ɼ eM4s[1C`n}OBҟE-؝⿂4߇7e-AJxƱxR-\vf $l2qV W7˧2ݏºk6p|5M;6|6Ʒ|<4cۣP4x]}63]mRv_şc˖Fk9/u]v,aR6=|>()uX,x=i#xgNyxaS Pb=Aou 2UIHnTf9v#m0Ff#r+kך׋QԴB+Md*&{%2& ۇNZ4{Bգaq>5[vG!Z?}>x[{[ lN㛯jmiZ$E^־u:FKInA媪Mq#egsӯ'%s\.Ljub~^߭r>*Z|Ə4,iY |oiVRC1yXNsy}M) oqU *_9kZVP (z|⭀b>[((5N7;*p['^A~ٚ^ Nڬ3/bv#~fbV{v1N= 5o&fY"=yGǟW_ɵJ׵N\Gu*jϏ>2j^tIMxΡKoݹҽkQXjZ}FOWhvwqZi{ sOS<ѕZU6ާ5y|3S^cxi=WS}"OZ o\̽ӣ*'#;hE ޵C 7k>7_QQ^P?َ6i0p|:bve-Tͺݪ0f8һ/x"_eiw4#ZsTtvXwTns^iYmrR7t1ЏY$ivNwSo _vp*Ӈ!Y,5 cOۺ~J.5xòAvKfIu8kSTާD׷i24|O'V/ Zrۼ\M^ž]wWm,c̷iBG^kz_jԭc~aBNJ.g_^;bK( +~Ry5j:J7VqGu"`sڷm?k>ҼXPTvk+S^c.ַdw+pPcc&adXsjMs]ۍlsqu!o@;L9}CW7mYq{pqv:N[[aXzwpG5`O8>/bt,6%[S3rG6O23ֲl,5;|$Fic5 EZU|3h?LZDZtW\7xT(QW9Oh'F[=KWe~),*VjGQ ԟJ$Ɲs< OIOSU_xJLoiy1|}sU^skSNlt¤Q4drg4?<#e;ד\CgZI>e OjOqdHc>ԓ6GӬSi\weYԴٵ]. czgawqftj -*;]OOͭtm0Vʲt?ˉuy?7#t_x~=I#6}=[ԣi}čX xG][F=ׇu## Iѵ7Fb$eRzqY^M+ou x&rzWM ݴ -~"=X?MO6QHPb{"8LJ:`t.k=% ivJAT3w= uOK.uXhەסK~\)uotcPO1xQPj5 '}C68T6.5ƺΩXip$ٚږI5k?,X7ҹ:wLŸxݤW[jCss`lU5\xWof_7 $#^> y xK4poS|@:V&~&K>=ۛ;7LաkCje=p?CSkvu3Mc y$Y2ŰZMi?ZMWO<̱ɨ6fTֺG5RK.>d]TԏF><3?Eԗ?k}ŗ<^hρV{qڽVmcᾹiei%ahqqC^?7FA{i1YM2*5Y/M_|@/?!Ŝ2ö<Zqqwڝ݈G'uM5Z*tCc|#-~&xt{Vnf[drRjM|^׭=2CI<ř8W{.}egzl>._ ",/+/|{|g=5Y|M񝷈cKH?woD8=sڤTO7&%-bc;GלhU.(jahX5wp|rMn{}A"M]hZ>{֌?<Ko\I ̫?[< jRh/OZe㸱?gQ ou˭ꑮXG Qw銆/;i}kb!Kb~Yb+5mnV9BƂ״M Fk}Émqa5׮[efvRZhda&[>S|GouY6-laSJ%ekVOwc?29!9OjI>"Axr|)ۉɾW;{='A#Ax㴸Xx)>C՞s,W0{W?[۷ݝĒ+fX,_uݽU.g+^_g?ږP Ce6ul#AЦq_nض浚}܌.75(+F=y]q(TVo_dZd*;bwl޿m{+[V)^ʦФe+FlywG-Ǖ[(D~|#{94k6÷Z/_V^y_o6Nҙw0<)l3m571(ǩ~˿4㲿uG'+Zз +zbI-{Ca'ɐgiZ-bTe݁2q\xkt[Z}G㯆L2b0wş:E*mʎFk&ᶒ[rr8~&L5(B$6n/4R$ FU}j[_K j$-fN,c}%źqw NYj]WX sY1p_~]4LƗH 8kzӋ.T}a B^ۘ v;[+\G/,6q.S*S]X \M'ނG>i;mQ8b\Ǥ M'~:^:t,2Uq޾}4v+ b>Ř:į0gq ה|VE)Rp۔^i#pwyGI4HD/^QIt<\F"T#/S }kGÉAZH OM=)M}Y1TX~bGԗco3վZ(q&:lzȑn`;ohzvI2WF\NW)c4L3©|.6#WWZ@ͻ_ p.:v%|>M+P ~yvPfpVqxmt9տuE~e8V;ij U#$~:s]R1ȻfbX zWRs1]_?;+d9:YAk5މy$˗S[4hӳgֺ%ˍfn~A.^8e |3]}gⅆsocCsk 9$hR~t_$.!۹S=EsJ3{l~*k孴Ek??!iڟ5o?WѶ9Z7kZiq@͖tkϋ:9_ןuOF+u ;%*Oc]k8S"kRs> xEK15µaG{׍hl-'It֐^4=j߂P~-PHyd,rr}VԾi^ݫx{eeg+Cn&pv4*z?ygp<mko/zX:Ңj)gkorZ+cAOhiwVPR=v)za!I9''i6s;K'ۋ#:ހ}'@m&9oA|LjAk}gY=Kի߇W9JO`\嶯{<}n;x -zGYx(Zv[8XsIO{R[%p M{o[-{K]7ct%1A5 yfIiٕ8_k!y qÄh_o^'ݔ|n .\۲/¹Y/ 6^_F|֮=XCo0%Ŋ]'Kb|q쳷22:{sֶuRsZȒ)E#jDŽ<ѐ >jTȒ鰓/t穮_{I{!b6"9q5x\#+Y}mN- ,:c֮\x൴ZtAqq!xnǹ9Gы |Rk~ʓ5יGSk36b*0Vbx5x?|ahbbGǏnxHN[-Hm1$aUw^[o?/gaRv5ͩxPվs鞽Ү&h>n='ծ-o[\gzJq)uoh~q}nw]koq!7c9#GQ^v}JVg-}: )/c^Og$r;Xh|R<- mkYoO~|7%՚Y-xh۠EWms,&h@FHک+ԛYwZ> '5|?]M}n[ *AzVuejGhn2ܶ#c7:S'ZXGr2(4f1Ac⨣$kP#mB1}+aWCk[{mDP##@я_ZϯklW[y#%PƽH:|xmKMmSmG=tv4>Py^ldqZC2 F6;w8u15S<'-,H/L_Ʊ)Dԡu!Ly#=Ώ7=3㇅ GdHL03mkOx3zNo>-k og0[EYB7g># .52)!+/|aZ &B¶>J,vvqPz+W_Mo0UE[تPK uxZKm׌ e[~j7K} oGҫGDIxOsj2ʣkֽaG_.~hZȚ{=~_jgc`ps&j 7Gx-Y<o={V׃"(52L*Ҵ|=&x|-X%nOޒ:xvџ3r#az\c1]17'cma,kuJ|y{mwtlYe1ҽIS扌%(_Ϛi ʣ>O"haS.N>ק?'cnc3aq&qEZ0}Kz+jz2Wn'c6N bb /ʁ4w0@H)~oncG?+g#ڹ)"[K1F܅? OHr%f_z1K;;dM_1ҩ <-r:kӘx9 沌4y)[Φ2\'xzYЦO%3a'WiS&dy?7?켭_OrsmXY-SNƴ&:{oے0q|R*"ݤі|Ógz:)D+)Q$v~+#vJ2r#XӅx-3ڶ{$۳$=km#kH |<ھ}>"ZiGv?Ek c1k*>z;5lRl\Ru}|(,fYH  Wۛ+KҒX4xҾ~ZSR=Ϝ|Cl$UkןWLͳFt`Ug]7M,yU~$~?+k ^Ǜ'6+j*cџx]mO̶Iy֭+# zWBx6]%d WhMϋ6mZ“6<,Y㏥zzj1O3H᷂.5Fj|{ IY`nIf^3t|Q'uѬ+=k~ |J~՞#TMG*gYUK*6ڎ:Ƨ%ӥWjx?^<.*#;kE{"8_q^[S_E$X/Pz闪VI$UTj =WN9NFuxVLT|/XNP?;}+| }{Mb,ZD%Wj&>pGvW Jڞ\NoM_: a ˶RBYN\D&O||ўks²^ȿht|QOC9ja+pqknmu)i>2aaoR=nQظGZ"}<3'U`cmԭic1o>1^[Ş[]-m7߁k#hU?r~ⳬ%du͞cNӵ ˛o37сJ7V t+>:^O|E!T#`S_;~<7s^^ֲ6qXTh(+R"ӴaisNq#k]j>.m{1ׯ6g?/>3W <ņ!.ÂH$O|YC.,U5q8FwFy:[epi;k_*uHjD>2xm{igKk՘'+?L+}'P55YxGFQ3|C k>,Y˦!3zWm4g-e̮lu6v:%5&Q'|ztlZm獶ܗܞ׭k t.}7ĚZ}+*#ٿF}9EiO;G6)Js[[#|33}[kf%v3:/cxCy5KX뚃?l/$5 x 躞4h4s4\2c޺mtf'[i,.-ƏoOrsviPT'zW|3fX|yⓧi@U Xoφ Civ%ǘj\쐎H|Gګ7Iu>3)jZ11ɮP{yO-[Oڡ W5=ax_uhlV{P+h?ĝ3o4r/n"y!qA ֓{_5o{46JR 5->aߡM Gođo}hK)WACuyi:f^l̏l[ͺ'Ѡ>.jVY5k1<l]iϩkPgQͱВT)<94['s_Oךt~3g-'kR~P?ouxK >էᯉlSz$q jVx|t҇;}?Z&u7ty26S6y5H5o8&oԎ4ryt|=ac^!t ̳#GLW+?.ynYl7m޻8tMS]D˟Ur6AYxM,">NVrA  dֺ[(NsQ8j&ֵI+š}lR\F|c+;xm6tcFavsazuO?| uO\y: ̫Qb|@v:o/i$|lvz/X`&6gf;A`<=ޙ㺼eiZ=|FG뗃6tLF˜cUO<OO!u̳D<|_⯆Z4onhZ+mTZZߵ^]Մ5hV jq>O:𝌗\nvs]1񷈼Q.mѮ-yۇV5M^|=+θ3U]}*9뺎eY$m$Qp cW_5ֳ<0ݵ?MD(Qkx>V7=Bo6+t%*½Ͻ5S-tz{eUON*94Jv5 / }O^Klư=jߏCַ]Hd[V:dz?ɼm~HfU&WxWjn!n+s^sM0 tV>_,=hZo 5[uhTI* 8z?x]ƛ5v UBk5#x\K"O.:T3^ ?g{y?s=~O1~(&g.{of?S^;N7[tyѨ]Gmš]xu$i济0OBM=,v6ky5RX#i^H4m~8As\m<8KJn.۟z -B8tcyNOž|Mw&=VI WVɓֳf^-Iu|y%ǗJg.4>7vwCM+-.5 +F>Kw >"iv$D#ޕ<BsE{Ɍoo::xjp]&6,=$ҼCbBOʛzSEls> |E4v~#fiO sTVU {m--apU'ᬟ^hffb)ұ m<7G%i<'#P䝶qzWn]JG]ҾtCKVb5 cs yqm9daXz&ͣJVnAjtEnoa_/=HZ֟ $=ޫ7^#^Q Y#~"O7 g+816읯SLE+;櫙$cPK!Yxl/pivotTables/pivotTable1.xmlUMo0 tOb iSł!ht,@ In] d;a;b[#Eӛ* JhK X%\nfsBL&L( 3ބ?M 3{Kn 43Y[L\$IΙť\n:HՖGI̐1QƊB||/WQMoNvŬ-OÙ?ybldžf+\3D!e0$/mZG\>Ad.e?460/Xw>s+s9;2A>#B{:/兀H".0vp*TiHgk횒kc@?w!tp>. XN]JϖLwww3Vq̉{bX;qԝ6!R5q ]Ӑ@s{ ZfSwtгlm㉾pZyg(?[#5U x (q񒣟v[(gk9C+]l_O,_7U!DF6|p%D[4fn@K"-}/EF|YP3Fk~IV=x;jج C%bSMpQt"|E+::H!欗y( `rNPȒۜͽ{hQ{~EiES)#@)VKeok1`r6N[Ъ*0J&x7p'Pv' $ȏSWaI{YEkm'[ݣ6)Cue6Φy`nqw\G MGPn#~6v 6\mܠsGn Li<ܒr:Ġ $l!Wig\V8%9Ĭj6">XJ n`\p삃A;7Uox6ZiҘg}ͣvb@`bU \#{❦G}YH:A@瞿wmk}Zsc+5lqdK"Gc-a8$Gv4vQ8*j'hJcft"ЮϛuCtKs2kľť>\܄~Ѭ@m&ZF2^Zܖ렎tg.Q6GUz%bQ.SRSODwU;έʠ^~$(CsXc2rj_kwsQ'tg~Ȩ L ׶;;A㳂em [9՝=LFstnegJwpڼC&PU &45h4Si$vAI= k>TQNG>RB3tFv~. qQ第²K~4 OYsM͞ќq,fq`hnLl" e/$5!TC-9w rg!$x_!i6fE$h߈36{y-Fio.I*>^MhjVc\>$ebM܅&ؼ'iv6 ǭC$S$W H>d,rkGQOSx[6x>Gړyr@}B*,f&V}堮4RH3BbTi{ʔo ݙ2[iUl)Lj?j:~v+k]1!8S4B)xLpog<76֎蟈؅6談'[x}jGC! 93FlKPSHMM5/]̐~t ʢdPH )\EX{A%c;gqj=#)3귚;I%q&ߦK"ktGitl5+լV [*l5լvy^Na|RZz+^ WlBek*= ^ `C%gm&x&M&SxF wTHeTHc4Hc4Cc4Hc;>k^A'qƕ:: qш 6r0P䅜 h,jYىD(إ:*ɤO&SU:VN LOX(@]F(Iƒaȟ=?= խF!fr##o.EGplg#ψTg}f9Rq yAܐd)JQ !LeȃZ[~ӂ6S_Ⱦ&%1ߟIy m}4}O37'XZO&=MNg(ZT`UR{Sq$v]5IQ}c7$N5.$x#_uk(y%L&cPK!!OG%xl/theme/theme1.xmlYs84~o А)M6q"[I$忿d@ IWyH~jW~JD5H/[ U*2Vt8!JC$+7NtAKJpN`Z'*eP/EM+|72?$!ݒZX J$q>g)%SC29]s|2 )74 vXlhZ A>o:!e?8i6qgob?;fC80mO:W.n< = 4EM,>=\@.Qxюp78vvǭ^0[wwh U TpV-vơIC} . ?FqoQ 2,bBN`4^t)Fʙd` [JΒET*YəYA|wZP9"wt&cZ v@#ǺVc!rg]~ d)R狢 BQ\^ɖUg.>6{qeEgb^q4䴢wbAiD/vdJ5X$S6JwDb:\~&CcZ hE~Jy`ƷPHNA1!oV2 4z:\qF(Q_u%_!Rj|BsPQYU?Cȕ13<ό~|by 6@̛-a>2XBW{U3,{Vΰb~ۇVsXVWʞAU~?_m%"lWmMpѶv.ٌu_&3"⦀ƶbrՐi.qޣm$u vC[> ;vZ3:21k;-TmEE1Pm2pW5\XZCm43vw-HGF]ֱvkK&o#$a7!)ԃd~-k2o;R-]?6\òA Ucf}.\G hеt>ةR*ʛ"#3׀7dLm+y!cm*,MW2ց4֤:F䤵nQ٭rOWŦ3Lsx7)|$BB` N$^ځт oHIosMk5ˉdxBRzeFH򆐍(O\U;g򩩁=s'Pդ)w?&firK槶;p-^}(G>Smw'bh|}Q[= 1c2SqkF xS ]ya]0Raigot7|1K}7Y.wO3vc~15&5z?Wt9^Z/V޺ᮽ1sӿPK!ֽxl/charts/chart1.xmlYmo6>`Amm8NE"Et h E$#Ev#G}nmG"ssgwTi&(:0"9Q, !"'\ : 7T?4fK̴$ @l.)ݮΖ #K*o.UA E7Wz]$/PP&xfYF_lUParbdǰQ ȉX*NM]+|"wlx UTM00[΢RQ$E gfcAd)a[aգ0R<~E2%h:7P{֍+bt͆Sjy nhݏQ`>Wl9~J3oUA'.hsMKb73~RLv@AapH9 Q4 Ƞ3ʿL GF2LqĖZៜ߂5#=gfbw5 l.Vrjv V|F4 TIZr_2 :ʡo֑]iꜽ|^ 8` wMIGᯅ8Ʃ䠃ב郎L۠i*,^ =^ $cG8Q\| ],FSc8sY]Q`lq+cw؁׽e.q( 4qm9̕Feލi\`La)c)|yA95ګQri.%(ƟΈcbǩͤݦ '^i@;rL${  YދerZ%\1fH%rivޞoۏ<W)fX:>c*ԙ8W .;Hi3'I̚ޏfF-WyO 9gC*9>ǯSRœx?Ӏ $le-Ȝ_R` An}0G1 QGlSڈ)+<%׉DJ$DYH3Ii%h t,-qc763"1xxx F1-߸ktZ>Ck<1OPѽqcy.z6n:2۰nQnD'(DQ>=GVxmEh'5ʍMZwQ>E4'u1$5K[}Q)+rmdro?yC~l tq+J!W*&̔h|I \*_BDe%Y muJva_g;p|KB8P{VO&<PK!C xl/drawings/drawing1.xmlVYo8~@蹱.; X?0EjIH}|M/"C7hCfRTQ:J"D5U6XԘKAjWroKVQkL_Ʊ&-ɞ Xm각zk8KXZ<3V)ׂRyQdGDYqءS㬘ĭ*4p,/kpsF̦kv+4IM[Fn(0Q"rh!ZU4o2(p' w}p(9.eӠ]Ar=/( "^H0b b',#F oJz=#FuG(jNRR9ɍǑ P'"DkfuA]Z qZ&5L$U'd7ɡNsf O$AIJ-P%s) 0D{SntX]\2̣ ^c`FPN80nJ{ht";{S€1OBA@G^Ҍ!tv9_bg۳ "gbuDՋ9Az> ߇Kd :6~cvvpy:.8NN<-HDdzI6NaW|CvDG%c\WmakY}84g\څ9Whym[fh?kTKL {ka^ D.gH6ꃅa^y+j) Y}7n 85wJDҠ%EkMmlƵ3-. GR(V)K9DLPK!KrPxl/drawings/drawing2.xmlTn0 }K[#r%^6hd91&K$$N~d'[a/6EQ}+^hhvR"U}{:PHGacâLq+C@$|'Z 6-8\mR8 u+Y 7=$GQ|x$qݽz^#Y8Ĩe \2dV Dqpw |UAZU݈ Ws-rxPN@2\<ԣRrV|ۈҢfk*t&_ Z8QekY&CZO8[2?MVS$,q:ImxFWtq5u'GXɵ2W\5A?(~Xc!t Yʅ%wd?Kx:)1 @QD#:<0vqG Mq±S,T\\8\n߹,b}S=myZ0~i߲*/C5vvx\Ϟ#'O組kf٨x\x8& r|]O,ɔB h^@`jsepń Xѧg3éqF³sq(i+Z3!|\;5=W'>6C'Qb</kT|,7,̼&Q"TuL4'I|/Y*?TGGVT1O+vOg:~(۟lt@8ٔhj3}owZ}Ӿ꡽Tm7S i J?Hi]#hNv]#DHN[=٥R5Gs޲.PK!nxl/charts/colors1.xmlAn0Eq "¦'Mlb ۥp{PhC!;$/WH&HFkֆ8 >5 y!E2T(x[-/؂ =`vSr HJn@Yښ`jjglO#>Kk†Z1FN+݃SٛkTOj ,[ӣRQq}QOFOy~ t:PK!=J'xl/pivotCache/pivotCacheDefinition1.xmlT]o0}`=@iB&*uӴvݳk.mT.f[ p}~$;Y-+J (u&&׃)%1R+H Xz=!V[ XB.p@LٔU3߷z\o+,/PC`<@ ab QYJLd)5w!7KJoJLK0(w5 0Ә4dOƽK}kϞK8zFqMvkR:dH+mqt96Gπ5CQ'V֍^=znx)i?3En>  7_ЍG]uHw2o~\gc5jlSfXpڞ~MuMn ܽuF$߬› >`.AZēh}; : W M nչ~^؏8Odvq鞌DwK cB{?PK!+5FYdocProps/core.xml (|]K0C}mv@xؤ!v!79lu VQ⍨6G*\:9:E*i4W *ocBEB⛐!Mn?g`CA'CyIR(2|PK!-Ӂ^exl/tables/table2.xmllRN@}76m{(Ę0C^ݶ!9gΙtkآ'l.T +rZ\=JA ,r$ˋ)ZjK$Ft8oC諄PRF'7izPV Ur"WiJEq˗}Zv 8%٫ӭ$ ZeAҷ<}l,kJ v >n kԡ.~f("fp;QnKߏ (0?ELN:{K1i, |4d?wrr:ƝD.UUsWj[(O<3l PK b _zMFfPK!/d$xl/pivotCache/pivotCacheRecords2.xmlXn@|x޸EЪPl[b(ybv9s Y==ejIѢP=?~ˢEծ8Umeu>b/ۺٵ Rծ}ם9-粲wTty^,v,qHPE ͜5ꧧöQo_Ne4{ù֑ʣͪ٬ū=rv(ގEcM7Igp`qvmiY3#Q!UwV2W9#2c!ǴjP^m&%!=.;]z}f^x>$-w+B3Q9#( f0r3 6$srF]7T'R6Q_4!''{A%+9*{}f@i0'm !E9~=]ە JS Tŀ-7h14u7SR1#I@gϴ=u=|G\6&<,( -c@&hf[+֏IϩNŋ0@n,bN [+F `Z3܍I14xXE\:L1h@KҎUSF0F^/f ~MTِ DP9n8V]sue9>.dX]!Kbb0N%H9V%1ۮ0aP ӈ'2e$cH:8HV\ a&1v'L#P"Ӽیb)+HZ*Ui-h896d" uKPy ]/%fb&mwL!By icF%Lg dc!!uGP }%=uW"b3@8ux14LkyL=xpDG/^ge;s.GPK!v 'xl/pivotCache/pivotCacheDefinition2.xmlVK0W|'OC"EBj{nnvj ;Ixe7ߌ?b# g p%S>q@f*rowN82BIH }0,Z+-\r4&MJ֖38)qU gJ f)5,({`\@Ɔy*[ 1`!<%z؆<1oSrY5t,F8 ^@~?&N8-{]A6+0U3+6%aBFìbVtݖxOd졽[!wوxھ5Pɚ3`F=4D \S5f4S +-܂;|8LY]gEkJ"X!w)f׏"791qeMJ Oc兀%Sҫ[NH)sn3 ƛib}>4/5gb7t^u|*?/pAx6خKC귾:Uy ٹԃG@E~zQPܞOV#Le?.>Gn117F,A3gcz^Uml]M3~2wVWv/o˛NBC31ЛqU}e JL+͔axIShdXWU'Qz-PK!ʋ$xl/pivotCache/pivotCacheRecords1.xmlWˎ@G@s+=l@xF2`km>xp ؆{*U5mQW'U>R׏]Vs]TQx?}]kݽgITyZT8uu2ke}QU˱nʬ?6n{iTvhOJu \}RUQhOťN^V]*bOw~W9T$2xe8}n{?/@$#]#ٓ?Z8य़9ǐ>gRjx\bsiP~Drk*btKGcxn x[vtCqv&03K\ XRX8%1A͝`W xke&ʉoD:1Aw|h=!x c/'`ఆ̝cLI` 8,C4~Y'ATi4b& nZz;ZJ~d4r*8$;ZWat6142O[9Ā|XH,łWesAYE"SKm9Ⴌ :OU 4jB:ň5 >4q Ll1Z\ܨ̷b0`E5/4h+7tW?̊28-}MI'hnPb<3knl]&}$?6#$ tFgŝPK!ʂa#32xl/pivotCache/_rels/pivotCacheDefinition2.xml.relsA0!MaYx$m6/Ţ~sT8 LϓZ(``[ՠ(Xv> d 'dAnj4a.!}U(A 9ǝbGQ*s1@/н0H'uy?[: ˼3YNN @Te:/W?PK!+{32xl/pivotCache/_rels/pivotCacheDefinition1.xml.relsj0D{-;PimZUB1a̴2;% 4U ehAIp@Vuf%$ %)؉# EQGWIoz+7:9PKloPTXrrRF~YMUZv{PK! 'xl/printerSettings/printerSettings1.binSKJ@}In@A܋"LBf;tDt9Gp p#XMի*^EU5BHڂ7b'1&~$u},|ڌ\hG~>RToM8͏fuw?l _vs\kjKƞиtqG7",üVu+W)j^e!8O%vx,paVV^W}PK! kFgxl/tables/table1.xml|Q]K@|ǾK&EB؊gI.wnk&NgڈwA;@<@\mz~v"2b{ 0KOO !2d*\ kE G j#Qt)k-'0aUV_QH6y ȑ2mW۲s] ƐNՆ\B/esfS 2'\=y]6ʈ%ڒ* CW;|BŐL}؜<>Chopa w\v.uY5kߵbKj'sE-:ѷPK!V0xl/calcChain.xmld͊0殩.KS/O!m!% o9XY g}^e* &r>.(m'(#WRC/R _΃Ȱr#Թ:odL0z]|e_ܤ(rżrl LMxBCpkRh(* dJEPh)fPh(* ?_PK!?ZdocProps/app.xml (n0 .@VQ:`$ΪLBeY#ӏQi{ڍOHJ.B\.rAta_"C24>(P\&9-&jWR1`9RcXUm WyE P^WimχcZݴww9"Ɗo ^ɹn 99:\Qmfc])CPVX)Cv%G3ə@՗!-RbzPI.C8nC煽9Α]mLwsaq~$[Ӵpb"$ƻZC֤֦k Y6a+.<}e/IMW9mJ;^Iɺ6aK[EWї,SPK-!KT)[Content_Types].xmlPK-!U0#L _rels/.relsPK-! -`xl/_rels/workbook.xml.relsPK-! xl/workbook.xmlPK-!X  xl/worksheets/sheet4.xmlPK-!ʱ7#xl/worksheets/_rels/sheet1.xml.relsPK-!;/ )0_xl/worksheets/sheet2.xmlPK-!Z& *xl/worksheets/sheet3.xmlPK-!n 'xl/charts/colors2.xmlPK-!-1%P(xl/charts/style2.xmlPK-! qM -xl/charts/chart2.xmlPK-!EҨ4xl/drawings/drawing4.xmlPK-! D /6xl/worksheets/sheet1.xmlPK-!9/c#@xl/worksheets/_rels/sheet2.xml.relsPK-!Iu#=Bxl/worksheets/_rels/sheet3.xml.relsPK-! #PCxl/worksheets/_rels/sheet4.xml.relsPK-!z!fDxl/charts/_rels/chart2.xml.relsPK-!&ꇻ%#vExl/drawings/_rels/drawing4.xml.relsPK-!"G)rFxl/pivotTables/_rels/pivotTable2.xml.relsPK-!.#}Gxl/drawings/_rels/drawing3.xml.relsPK-!܁Hxl/charts/_rels/chart1.xml.relsPK-!D߼%#Ixl/drawings/_rels/drawing1.xml.relsPK-! A G)Jxl/pivotTables/_rels/pivotTable1.xml.relsPK- !iaaKxl/media/image2.jpegPK-!&8 xl/pivotTables/pivotTable2.xmlPK-!{U<4 xl/slicerCaches/slicerCache1.xmlPK-!ٌ? xl/slicers/slicer1.xmlPK- !I  1xl/media/image1.jpegPK-!YQxl/pivotTables/pivotTable1.xmlPK-!+#Txl/sharedStrings.xmlPK-! N Vxl/styles.xmlPK-!!OG%^xl/theme/theme1.xmlPK-!ֽexl/charts/chart1.xmlPK-!C (lxl/drawings/drawing1.xmlPK-!KrPPpxl/drawings/drawing2.xmlPK-!F;0e rxl/drawings/drawing3.xmlPK-!u&uxl/charts/style1.xmlPK-!nzxl/charts/colors1.xmlPK-!=J'{xl/pivotCache/pivotCacheDefinition1.xmlPK-!+5FY~docProps/core.xmlPK-!-Ӂ^exl/tables/table2.xmlPK-!/d$xl/pivotCache/pivotCacheRecords2.xmlPK-!v 'xl/pivotCache/pivotCacheDefinition2.xmlPK-!ʋ$xl/pivotCache/pivotCacheRecords1.xmlPK-!ʂa#32Œxl/pivotCache/_rels/pivotCacheDefinition2.xml.relsPK-!+{32Ӎxl/pivotCache/_rels/pivotCacheDefinition1.xml.relsPK-! 'xl/printerSettings/printerSettings1.binPK-! kFg3xl/tables/table1.xmlPK-!V0xl/calcChain.xmlPK-!?ZdocProps/app.xmlPK22Jopenxlsx/inst/extdata/build_font_size_lookup.R0000644000176200001440000000324614155600363021444 0ustar liggesusers# nolint start options("scipen" = 10000) ## loop through all fonts fontDir <- "C:\\Users\\Alex\\Desktop\\font_workbooks" files <- list.files(fontDir, patter = "\\.xlsx$", full.names = TRUE) files <- files[!grepl("-bold.xlsx", files)] files2 <- list.files(fontDir, patter = "\\.xlsx$", full.names = FALSE) files2 <- files2[!grepl("-bold.xlsx", files2)] font <- tolower(gsub(" ", ".", gsub("\\.xlsx", "", files2))) strs <- "openxlsxFontSizeLookupTable <- \ndata.frame(" allWidths <- rep(8.43, 29) names(allWidths) <- 1:29 for(i in seq_along(files)){ f <- font[[i]] widths <- round(as.numeric(read.xlsx(files[[i]])[2,]), 6) strs <- c(strs, sprintf('"%s"= c(%s),\n', f, paste(widths, collapse = ", "))) } strs[length(strs)] <- gsub(",\n", ")", strs[length(strs)]) ## bold ones ## loop through all fonts fontDir <- "C:\\Users\\Alex\\Desktop\\font_workbooks" files <- list.files(fontDir, patter = "\\.xlsx$", full.names = TRUE) files <- files[grepl("-bold.xlsx", files)] files2 <- list.files(fontDir, patter = "\\.xlsx$", full.names = FALSE) files2 <- files2[grepl("-bold.xlsx", files2)] font <- tolower(gsub(" ", ".", gsub("\\-bold.xlsx", "", files2))) strsBold <- "openxlsxFontSizeLookupTableBold <- \ndata.frame(" allWidths <- rep(8.43, 29) names(allWidths) <- 1:29 for(i in seq_along(files)){ f <- font[[i]] widths <- round(as.numeric(read.xlsx(files[[i]])[2,]), 6) strsBold <- c(strsBold, sprintf('"%s"= c(%s),\n', f, paste(widths, collapse = ", "))) } strsBold[length(strsBold)] <- gsub(",\n", ")", strsBold[length(strsBold)]) allStrs <- c(strs, "\n\n\n", strsBold) cat(allStrs) # nolint end openxlsx/inst/extdata/namedRegions.xlsx0000644000176200001440000002002114155600363020072 0ustar liggesusersPK!bh^[Content_Types].xml (N0EHC-Jܲ@5*Q>ēƪc[iiBj7{2hnmƻR U^7/%rZY@1__fqR4DAJh>Vƹ Z9NV8ʩji){^-I"{v^P!XS)bRrKs(3`c07M4ZƐk+|\|z(P6h_-[@!Pk2n}?L %ddN"m,ǞDO97*~ɸ8Oc|nEB!$};{[2PK!U0#L _rels/.rels (MO0 HݐBKwAH!T~I$ݿ'TG~xl/_rels/workbook.xml.rels (RMK0 0wvt/"Uɴ)&!3~*]XK/oyv5+zl;obG s>,8(%"D҆4j0u2jsMY˴S쭂 )fCy I< y!+EfMyk K5=|t G)s墙UtB),fPK!+iXxl/workbook.xmlT]o0}R&E@ՒL4MU/yq+fYRMﻆV:m/{Ϲ$dž;ߨL'>r(d.E_r!$\ EMrjr)icEMOdKTR5R<*JJ]Sja?b YUKYt fQuZ=5k]ib83=(r" ȖC aaZV| !,wCҶHc_DU -StKyO6T^uiG^K唴"7ka)ބ.JCs) Z;%үSLa˖%%ELOQobs沤/\72EIa3 0~X1zп iRR-0{V:Ex]mQoGGz@J32a ,>9eY>É70$.~}648gS ڡO;QK6Y#{sf/Nل1pNݘkJPI\'A16vҤ4ާp.F4ݗCaLJU 9B8cvd&` /}'<Ó?f;(_|ٟ_㹎O~s;&[x{W# >$1U|`1Mydc!b@ض^] ju[) xy~sA,#_b+r,y2u lc(1B۟@Ydau8‘!Ɩ&pn%Cr`$Ri -˨m=|d"@B~h.Pl39D1Dd#HG:Dz)sc̹ϵ ={tTC]Ę0Bʙ$B":63yq@p"BptUT$|2O->.a2 $9SONԳtZ;)Z;|?(=4Ocxg ;~^7څPuv7.'}x;4@Vj+72(iT'e#"`_U)MO3cVYm)j08۱VrwGhvm 4]ʼNnyI@'$Lu Ʋw$΅E¢)/C@mX?9j Qr_{PK!#Ӆ xl/styles.xmlT[o0~`hȒ RnNګ& Ml16/wsszs=1cVB*u._`uTUTh2|dߥ3@(s͂[J7LZIfGlc$pJ$  Y mRˆ:傻c,; z&H0/ID?F K|@~A~g3@m˅7:\J8?]qQi+g/'VVg/I"w*,5{bHB µ eF7d,O+Fk4 <&Jه7|#u ȅG@du=u$Qx}t-aI%?49j^A8o lb(|=SkN9Eqp̒Sr708qʎXa εǦ]˯0ypͮ\k&ŕk[v=rc+W?`,VR;@ iPi-;Q] ሣ%1 C<|PK!?A]docProps/core.xml (QK0C{U7BہʞN&n 6iHi|1s/)UG_`ltH4oԻE3-Xh(ZTW77mcz . $(7%{o(ƎA1ⶱ;l`;Ya |DO[1Ԡ@{IB׃Uφ^8Gv:ŝ}pr4my#'mүK݊ )|c+at'|5s~. R >0t~V6z,%8v4i6 uO"dNofPCTPK!WdocProps/app.xml (SMo0 0to0!>ΪLBeɐX#ٯeۍ#)qomCƻ-9i_+M"*W)]ׯ&QX[qu !BJÎ6.~h!?ꤛȸI+x[:,F+)g^| R d.<[,XFXWieBU}ȢNNEHr ֫`Cdm1o>0p;͹\ qZkv/Oq~BiBZoT2H-UnX?-X z.՝=wMLڷrq+BxEmTh M$&zy ۽?\Yg9&+PK-!bh^[Content_Types].xmlPK-!U0#L _rels/.relsPK-!>xl/_rels/workbook.xml.relsPK-!+iXxl/workbook.xmlPK-!Ч6f xl/sharedStrings.xmlPK-!nXz xl/theme/theme1.xmlPK-!#Ӆ >xl/styles.xmlPK-!EJ4xl/worksheets/sheet1.xmlPK-!?A]docProps/core.xmlPK-!WdocProps/app.xmlPK {openxlsx/inst/extdata/namedRegions3.xlsx0000644000176200001440000002411114155600363020161 0ustar liggesusersPKܘQ _rels/.relsJ1}{wDdЛH}a70u}{ZI~7CfGFoZ+{kW#VJ$cʪl n0\QX^:`dd{m]_dhVFw^F9W-(F/3ODSUNl/w{N([qTud.e(AƉN F Vi2cC#-1))q?+'kD |`FsdԀvG^O" *k#eEZ*M C}$11je@ KYz)OLdr,Ѧ_LAo6/LLY@Cb3%^(f%Lmedk[a/":T\o2d}bдd%#VCuWhÃ蠅{a ;DZtt],2> n~)9B.wqi/6VyGU7l?nB{ߙta0n6.j÷>|;}[zeP)Sd +oe~'־l+_7uG|Ʈ%_M_ͼ8 b4 ^~wv5᠙^Nf?p [CMQM~p݌ SͿMOPKJߌPKܘQ xl/styles.xmlXN0SXIK)cJSL4iڅI$l~v M*J}[u8C4bGG!F RbF&p"m*7AVA.)?N D}IʼnCT@RH0PH,h!oܥL/ @8 8ʤ`đ~BYЕ / U; )GAfyZ##9njv 0v6ďl]e/v9M+5RK'bS]҃.gWJHLk8ȺR'+>` M#+s- *ӣ"‚mT. 9cS@L&M"TYR&Ѯ95>ՅIfyo f3`f3}d܌6t09z_e/wۿdHmkjAm;-<.X'%e ? ^.%d]?:WKHWIK@$U9q&㝾ƻoRzͲ 6w~oPK@B8PKܘQxl/worksheets/sheet1.xmlUn7}WF+9v|J#WM2,F-gJC5vQ,ΐgΜ(RlId6& tBPph;T&XR!̈́'jsE"ͦ&>n r^G,7 !4)f5< N"a{ )s?ڂ ]iVcԭw)|Ifw'NT4'фYZ~$4Q&-+"x`TtB P7.Gyt1*=vd/Y'y{FJ4گM\-^u0nK jI :,]/󗎴$Vq:ӳPIh>9:H-zOe5_{-avmcl}_^T_+IA-4%[K-Ѻ튜ffU%pa5j캾kqepỀ}l%%\0~2_ dun7_LBPg? FlKbx !۬AB? (jGPZ:ρ;_ѽk+ڬ7X1 li¿4`:7O#=T)G`* պUT&+l2ߜ:QY>G*GgiFK؃FGZ>̱,5x^:K}!~)8;ۿ!P$vh,}8_YcK}6O=18o$ĺ˪ICZ̴ BubF9:H-:W2P 97Ho5x H!u~jͶ隯=GvmBlu[ZT_#HF 4%[sſ ,}=Z9+S`+VOu}BEϤ+`Q ]Bb4rHEAR.8u5vRف+Vw{-m_]fo`G4pov2Eb8O:OPKq !PKܘQxl/worksheets/sheet5.xmlUn8}߯ЧֲݦTV:@q}őE$e7R$Eyp̙3T2ڃ"Mq*G.n]9#LE|6E".gIb*f'A@S1GGK6xxTd>O*&T ``Q.0+P1 #vhaJ3xxzٻgx Z,$Ǫ P$v,}8/ Q%>;I=18o$ĺ˪ICZ˴ }B5bF~Q2$&>#_oׯߑ:xF%?#;[q"P6#s,mQhSãVNYxb>?k*iK0,E/hj\ bJhۣ`/c1՞τEjQPYuGy򬋗kk֘NHGD/.%O=rUk48OO^KzMХiyǝ*4XG6}5߻62XN T`B^lf,OA',gEGb- }\@hjm~# h8FX tc!q58ۿ `$vl<-|8%v CTʧ\I+1jF(aD3nc?X+\YLsGj0 e75*t;p(uV ,JvP=afQ?}` oc 7[J8qi*tN܇{eW3$=u 8Oˈ.$rۣgB{$k)bebLìc1:1fT111a6w̒[l_ i̢WPKPKܘQxl/sharedStrings.xmlj0 :IKqh }m`1rj9}y;~EY1A@! qtyHzb*_;r^Z0ap R|\.cj^'fTUj+RۀZV<>Xޚ/n(EaLwVk5;o$ RTlN*^*b+ۿ.dPK MmPKܘQdocProps/core.xmlR]O0}WC)l%ja4rǪPۿ`Sv=~5^ۃ6 "A<΅,-9e2ge-!AG0h\Q^kxֵmICJZE16| 3SHGnj]1.b0 ,˙e5N9-NA1Pւs=**Q}0b6M4N'mFlW50 h_n`^'Q>!~4Ȕ3:!15Z-{.p-u7 K&[x Yw1՞dƮ7:NG0F^4t5EhUq۶}|L#p>=>d PK()iPKܘQdocProps/app.xmlKO05#T 8UC@"coZ#doc@j{涣Y}3|=;[ e|Oچ mOކzIki!)DHh WsOvqEiV;p27řBrL[(x j#f0#x ' #_>c >V"NP8]W1Z$N|&x=B4bc~?V c  )cǽn9qzM|PKn8|PKܘQ[Content_Types].xmlUO0W,JQIiji o;S;5{K&kQF+0+d=n 4F|)*ӗجA-V̉ق΁ ɔt ]'אe颛z8}A*ETg2 UR3PJw3U-'_+h9?"$  1 gMqwTr:uI^Y<+^gT6@-(qubAFߺM m+%1hĎHÎ8툏8?؎yG#{p'>w`7;c8<&Nkw)D@`wW" A" .Ǭ9M~c[xWmRlG](=b ༨)j #fc8 $]Ȼ Fe ܞ%ٞi`1pBӐ0"%kTJ׷O fTGn'L Ki!{s8Y32H13L3`ցadFݩO8>PK!?Txxl/workbook.xmlTMs0wA{b|0LJi.L/B~`Yr%9'sHd-}1VBƧ#J@q]㷓KJc`R+Xz=ifzM@ٌՓ(S]“6s5XaKW(Σ E[^.[͛ kI HP-Em;bf'\W5R,-RRJiJp^SZzQmɳRHxnm'")̺pPdz(4FH<4hkŽ!,Y##6G`&ɹGbQ7ҁQ\+u Rcwn m6o'la+IcdFbm~ vt?Fl#6ʺg=gܻ vޒY[}Л&)[K/p" W3wet|;ƝxG@ wW 3< %!;vDg;to&g=8N~v}-}8kkGvx /6$/b|9>b;z׹}u{Fք<_E>hG͉tLr'c[o1IFPK!7Mxl/worksheets/sheet4.xmlTM W@|l,{FCj{'(, wl6iCR)11C!; rD1F\1] FQUN+#%;h-9nSB,k6=WSk#4j<$;29T(RskJy;@mEoOlC''e wI1,}o6tAɔ\Kv/%Y`*J@:ǫYZ&1&E6[g7Zo*c*]~75u>\4ǝ:eDuȌ> x ȶ=^vB>Ydl]͠}1ȾD<:D72D 8k?e>jyx4r-CB0w`$Ih˸ [# - uyC9$]ӆʢc/02~6N@,79-OQ 6gj)i%珰PK! x xl/worksheets/sheet1.xmlVMs0w3/ŮM&)&!NgEw%a{ƾؠ]}ZvzZ֖, y,R4 ==Fb iIf0t|4  l֜Waٴ"%XRZkrXUME;t H!LS0hf1 i)HHMŕ?[gۣ)p_6EL Y7 "ܭJZeyz}"khmsя9H4 !UtL@ٱw?J1\Խ>s(dI|̐ aI,Ա0m _!F}gSY[ I&nA՚Ccp*%jLs_Ƞ*~,kك݊7Oc~I-Ӛ,WXT7= kpJ ;N-7<͡as<͑f\0 ܁;C+ b,h5<:W8y Np8}ݾ;6y izp0=o[z\g MTZck:B 9Bn=;Ww\|LPDm龋'nDڑId8,:}r)a-3[G AqdZ^߸ M ZZF,CBj鉖u6=#@# qF"LCt(3Z>i4je7EAsQY^7WH-]"}b9 "ᬁvjs$磒Vج'4W@" RA#v(poZl3zQ%ѝ9Oce߼ `0< 5!+rUV2+'gg0`[H)aV.C斨x(prBon]t}X,\k_=VlX ձy;› HfsTnѕञCk- tsRw& 6^#/ۋc߭GӋcAiӲ'1Bt(+.|_#n샇<^rа[K#vsRQxYl۵?_3㊩uIF+L2!oǰ7lM _p:w>GCSb\Sw.=jAf%#YlC7hOcvϝS矾|o|ʋ.xO}O?t߾ӧ_̽>}{ϟ?_~O/?̕>ϟO>?׿ݏ|ǿ~핯+_Ï_O?~o7_o|c?/1~>;?/^Rx]?yܛ|c; v16͌vFy;fnۏ7E_ߚKGݽ$r QazȎ0\6b/$}`egCyTLE h&`$u9z93 >zәY8saRx[sFrOgw.eSv/mu?qw*uWt9ϿjGR|(ǹ7f̒\yv#1:;|IQ患f>sxk~4p, aQlT9PT\%%y;;x 9%Vw٧G˥uxsGKa;/zZ,!klrEH-tcfkemn \x8#0ķR{H?lz<<*$#-9KnzvhּkߦPYZ $uST+nwj5~cf,Eww繵#831iRZHI|Cͩ {2q}Vl\AgP2Wx]!Wg9s%ب38C7|5bxcu{A[j{#@u388 Fv/F)'qu: .a&AJ >1*ڍA?ϋ|+=%Re\ھBJ(s!ϬoǍ9h"7/ a_5I?aG][E#ͤ^͞o07يԻ`:1fbb˥i5lo( ,\AqoQo0s."QWێ1qs] r(1`S.^/Ar^-Yyp30M391a Dkwg8 p\!hJ!%":oC;HCXzx4DM=CmV׳/;ؕr4o3Me ˌ.sN!έQܘ󨀃f+ 9/]Xgw?'.j(+"<Wp[__r~`喹,ZhCJ Ak5w4~>! x%XN-iP@.080B 1۳wvy|QO1lq ٕdz,wAf&VTR4[DBdVFSܟY8QL* tĜ EêQJO(ج%.:^x*@Yqay$q΢&S=W~e_Λ͜d-l4zqu hTB *r 593<k52Vhh q =szDXon\AQ7G.sbə;h Vc xsfι1<] #S\˛389SB_DB$uYj;n0\'30R6^twfzqb„xpFqZKRd0|ɮ-K3|x>Ji8 ;J|nMwxUJaA fPoxlNnLǼ1 3>(%{a%?B~W @Tp5{"K+Q18)z>C_΢qޜAyq'A98㭆_<\ LxZ(i',;7 缇z1H!gk|o)%Wg Y>BnC5E5iؘ8)sc d&+Tn8 K NtfrDz#~n\G2~bnBsb6sǙ`żNELp6uF`}0.0cNǤ4|5rA 80<59ӓ"9locRX9)+ |Z r-s<(ˎC)=%1Bкog|gn5% dtL% 3iWp̗MZ>w)]hu ՍyTӣ%3䃄 ɨ il9)B, nXN_fV9݀Qȝ PдB!YcwRIo$k6w/%ojqHI )|;P™hOVH!vﳁZ8~H¢hR@Јu)QDmEvvWEc  MLQ>&yq§05ұ$ J(gERS;hf@GSPJM|%Sr?(dU0>J\ay19نaqst\Daw Cd3 :U\~."x]>` ,K8q C!ESZNu_<rkBJ`W)7Юdx]‡5P<\k~T$V9\Do$KU6 >pT%ѴA1)vK)p80(}Hm&;v b=?x#= b2AnD8K`)QbJgPB QLpJU瞒ْqmlh8յ5) YfTi\%(ˎ-@2K[6{^=*jIzd  'Z F8@*$Kfb)[~HZ\"l.SUB-w%jNG)&/Ѡ%p򺂬76pܫ սɿi'&%)kʠAyqǝ=aP(_:Dy&ܩ \"v/(+{.Qί<v"P-:q,`gG1%m $M1"IYQ&𢯛Wv'N-BcȅBBLTʮ֊bA(v0wbX)aLՇ.r(*v-T8Ec>FB) BץX+ h'+5X K!vFfyex=s'i# Al`Ěe[ ?ͷpLu9%$$|C)^biG$aw-ܻ0}81Q^U\{ݘkl_M;>2qYPSҗj2S| d<y<+ zʕT[~=G(SB[+|-zwwt(_Ravou>- &P"v1̈́ FQ1s"%[muU{7IDjA\Q2"~q8?'Ӕy/ ģ Om8".=~%jGܹsEA2;7zLV;0R!euJk!L5>^b&＀S Xt @ttzTzhY9%g_RZ~4Z1t% f%mE[[zWdSɪ5g ^ :א] -AvtE;45AyUpaϵ(f|gwg< #L}ۖWnr^ؘJU?)U`[Ca!:k4 k>// #Me IDr(-qֶxd}AT2 ] @AXG#,~Hs֢dR1 /B02Z> Bqx]! }Wvfre_ 5Ȱ@N#+Cȭv؆spdO ģl|Β`&=Yܡ=pZ VWr%BEYna$`D̫8X~u!(ls'mM )NeHS):JBn!Uϡjmt²`Ʋ/7-yKRd\:,V2^1x۰{#Cq ;ghQ\W\jL<{Lݲ@QT|AD46 *݋ @+B)E\uF'%L06.i;M-9*BzIpyuk hiI,!>;RT|^Օ1a[l"aߕ|KEf"BXuJN*,x 2R#TFiMҮs {z7\1;oG' ǒb钒H\'7J驠Bw5y.GZPr4+ MnBFʐbru[Y[\ ʰcBVc :w 5% >,'ԫN2rkCeH(p8Q0ܪ=sF dJf;\Q2DVCM Y5n\^-!Kz~˹Ʃ"iէ༰ nxR;[ܲsʼn `e%H?J&)* P_AptMS(? *ڈ tpK7lk}mP+P ]&e,|@C/T_MGO<祦ȻUP )V/@T)!Q^wqV,d nGaW=U/Rdb㭀:gͰ:H6y~ENDDX)ٟ*}%d(k:6 V"jG0Sg8jԘ9kS#:bkWSx.F9/ Zc*E4JĤb[3K\)xE$PX]8eZsgkKF(+\+u?' ǜK7 k(X`ϋvH`mI9WpLPA0abH@%|{n>7gp֧82,e Pb^lTQP.ccuFprgB` ׊ǫbXQ^sw6&\|^NdГ2)k9)\S0N] ԷQPXKmv@Dj׷b;(fKュrbT?T8Edā‰2HViuWZK)]|{ھ;ei\ µƮ$誦v>Wb 5+ N+smZuc}id eiW+/m$6Ly\H'ZIc-ޝ\i# +D*qU5Xo~5 .&!~}s*[z4Eo<> HZ텛Ƥ%\[0B1sL2GYPK_ _&:/l\\ 3!$!Sj 8t;O.B0`uT9 $PdX/\XR8 v`9jZqV J-`BQ̜[Y͛EMNI/j9VNʕ2A0!]PXjiAFAXYU$m(҈iХU]0|;&|,(f"v0,8QB\b竪o DW:In-SR)E{uKu2٥| $Єw`GQ¤jE #̚ZSKQ_{l\xp' ȪT2ֶ.,n87S6#(bX`bRyVEGJbjr%ϻx%<wL=@X5`ţBr(r˥Fv@.tK0 U3R4JdiJ%/d* LRD.'juC!;LQMN%E'@QbXHbqT;Rbagb8d_D1;_W.C_Q'>GQ;"J%k3NH .TTFIJ-v '"vQL9A9*Fjz98:`]lOځ%Ve68˧:f"D~ui:DkH [*:D:.ݷL G2Jpr7) tdчnoCLpIx,囓D}'ʩEHn1\Un? 3J:b.oc bk(M(p8Qx>y^Eh6[ڭu.,=4-BҲBTB.es)fapH/AlC_˜ب|Jb?A) '^Wk΅YypeDմ+-" g ?Qߴ*i 3nUC'3Tsa?$2±4-ޘn q5J)'J}iC?6;Sڎ@@o4O0.,~a@w"D5.{ʮ7y^DA W:DLp,5sYBw^ߪY "ڋJK)\1h]db e!nzM%VSEEʛhڥ^Xr`ÞkjQzaR LR {z")>WXNs}>4) ZuuhaV;A4 'tRʇ KXܕ۱\D3))jb;-u(Z\ݙXspuܡdC40"ۅ4oAcKB4\](t\Z{\A!%N+=|]. P_kJtaլ ]Q0@O!BuĞ+WQGs2DvZ.$ LvO}%#Z|N%S*xޡ#UN*'rgj+H .J(>QGv.29_ 񙺳yaBՔeROyi,? tWR /TGwJvPP dw\;* `lm]2p\DTPC.,ݦ4E)̠?G"8eR}|/`O_%@\Z 8A%0(|l R:8ɴ݇җ0d}h& ;k%l.wk3Yn K*f$u%yب'f:^Љ4n(HwK nǑdvP,{`c?pcXn*=`W J$^F ڥJ3|("Tl9jr'FYKJ& _%j[үm(]5fp*_ Tr-QW(%S %g(-k[pb.d;\ lHu(,&;N cS(2.U/s"3񺌰 Rbe?*v "_WVZI! Zdɔ%_D9CH08ڻA@Wً0jq*keCsVT,TIPK=s1p!W)T@(h`!L DQ;7*V= b5E,SPx;bu%MZ"Pe)Ʋk..YVJSIJ;N\ogC}T,y4g箢U|z=0ouBk!>Dzۦ(O8ȳ-}Xy n_lgP,\)bHR{s!`c]1ZAn=E ʓfB!ex}<\]߮eK#S(^K̋@˓ aXU#'1ADcV|eU+lC E&hacxεTcRdWYHLN܁s#FB_yW|+|F+_1'U(AΟvY\TʕUrR+ _9Уf>ZO@aRJT2(.Fs7SpZ!p\Q{^brK+ЬAϛ &;_:$r G9m\jΒ8{ hJ(J,gv$W4hVa9Ae 筕FqjE7[ ec N5.|Ώ4~D-65DuH8gɁD*@G N\QцKY|»ܽnb7K *wHhצU냁NHjj!'KM +,<D2Jj~$mSa@N7.AM֏.Z26Ql$ӻ }]W?WW Wr-شƖ>wz4 AL&8>͵,(4ѱW|HvCP}tPc,<\Z90IE \KG8 Ab~J@z j+-+uws,K0]dW3=s%Ycs}k?b ۜe 7(R,vPM`?+UX݇l5Sׇ~+W#"&sp-YL[fbNr(K0ȿv@ciNڏ_~x~ÿ?|?|cy*B"1O?|OPK!5sxl/worksheets/sheet3.xml}[ǭ=Ɵ~l{`3eț"˱2$N꺒Ht\جK,jǟ~y{k?{Oo=󟞾Y?߿{_߾0_{ׯ|˻|?Ƿ_?ŗ_>]L/>}5s|?|zOOob_~˗0w>ͻOӇr>ϟ>Oo߅a/~zt/y}L~ g7K/_A>K}}?ǟgӧ|v[2f?sSO&O k_ܟ?O_xo?~Ń0}_~_uLsmw~>~@?_{>tvh&X?{/_?}o}~\=_>}ؖ kYK9'_$myiKȇ_߾ߞ1_n9!6כK~Km}+yI[$ԕ„ǐ坅)țlT;Ӏf]Cnm-WɃ{ʆ߼Ӟ(0lY+` zеӒqGo^`D^OqJ#i0ڬQF]7M휶 ޼l8_i m1/̏y\60d8_i`L1e;v ڏg:۷G4@dtOtRttoZ~\G>4d:NyQXY ԛV`D_bkk6/1o^`DtRD#Jр/6}l7/0T:NyjOo&. Q.&Y (S >MdY+@dڏ_c;Kz3OpqJa]D{v1("F˲Bj֛QZ0V&ZQZauShN͛Qz0^&zcRzc2ި@8Ez0jg@j?.~x ݲ |oc4RR0T&RQRE46;aFT)T_U;kHUq oq#ͩy$ʪ㔂#]Īv (F#.},o^`DYuR"V=XV`DYE3ݚ {#ʪ՞%T;kFTq~f5z#JFGv (^f-Pz (S ]DGv (uǘ0yqqJhZ~\FQϐ9둒8Ez4jg}j?.Ңi4֛CuR"z=赳V`D0q5{8h:N)`tMY+0ڏ ucӮCvy#JFz-Gv (l9+d~īChSR"v=صV`Dٵ#u"gE]{(S ]Į'v (FYpC7/0:NyQXYv=صV`Dٵ8C;L:z#ʮFĮ#ʮG8^{hkߛQv0]O&vQvanjJ,o^`Du8mV{|h2kg5c(/4F~7}8( lP@oڇ5C&z=1l(1ۻlN'L>^{dL+~\JȧzsfJ3k13;VDpv/|x$ʯf_O&~Q~E8Y 5mvLY+0ڏubkBfʯf6;nb QEڮT\Dl޼l`)7[X7l"Z%~܎tiſo|JbIQl5ܾ|Ӻ S8AKQ[)R;kqfB<4 ޼R8w[Gmk%ߕ"J{Jj-5%.Eû7/0T؉XIqZ:JսMfd4pHujWwy~}ʑruuMߴhSl)@\=N)kgH~\LڸI # _(WS i#U=e{pВaF}CKpCkcr?Z(U6 ك#U #JHqV0.4 d(A ޣ!dL}dP^բHyPD)zT@IC(->KJq1݂{̛Q<([L Y+0 ݏ1nҁ'8C (C (iO21E6ye~\oK>o^`DwR"潘ZIyq14`=ǼyeqJbbZ~\i =Fb) [eL{JbHL(< (ÎS ]İvh ۏ ~~$?y{S ]İv (FҎ d( |^tzڏof)U3)28E {11lgʰшm :N$ s^MY+ڏ 66 e@ި@r812agjZ ~\d@oܣ7/0L:N)`t^MLY+0Lڏ |z~@qRjZ~\V2 (S ]ĥWv (ox#ʥ #D$1]ȴDɴAZa!%s$'OqJ."ӫL;kFLq#\E}=$o;JOHS"6شVDٴmӲ #r%HX9."֫X;kHXq~q2fNd!A~>zҫ3W)C^̍ڐ^q1+hCfH |Cn %Tk#Ò{{ƻP1fN$V8z8FD2,{9{Pq9BOm\Rik!kõ棧 Fhk#碲dʒDZr]e\RJ*4Kj%ÒϻT4eɹ_@<'};F5"a硲tQg2Bhfxc9o_BhzEihkK'ZT4}Bв+Mc$B  6]7g%,@UD&;黊$J3ɚt&[Qh2qs49l1>\*R-m_ w))2w(7AŸ9ћl{0UN:yeʚD'[Qu2qs";WߎÃXŸBy2.Wٸ(>AŸ9Q^7 YV~T(ێqs%"Ey%…*3500&uqʥG N9GaMB˟UuVJPtP#ǪjZZHn~?=3Ne*3 AEE(Ur* o@]8SD)x粵Ͼf/b]\|]pۀʭ v{P1K>IXezv++ÃJ[WX%V"JYfP1N,ꇔy%`]<׳LB2t~-[Q2u&iC}U,["JRglE5 щe,T(ݬXPK =l\Tb̜HY4u1PU=c 5˽ylYsQ2A-)\iqs|9:ؗ!1seҢ)@<=!>E T&4B^5T&~Ww]bHB 3.Yܫ+fud%$P9<>-/_w?ҼSJnܯY}?ǐ_ mj*1ɈE؅E r zxA=ؗؽB43t*lf+ffP1vO3nhG󺢜[J8*ڙp35;&يTS~gP}Uτ JWIhU"TMlR%1v r IMD8ETNFT̠bYTmgSDAŘ9| gKo[*3K>U6f.*qfێ1s"ʼn|zL+48L?m*:_b|pdo_n;Rq}ɖ83_b̼n ^ @+9L ,&ЉRps|5S*œMw i 1rQ31B[350aVl.Eq "ĉ:} QAmbL"nA%kb|`D2RFʼnJ'4gw%9}Pq#~%6*.jufnŨ8ıT:*nHB3t~;[Q3ˉf'ja%?uvbrlg\i w֬n2RN;Q Jr θؘڙcDs#av"NB'f lڝ(ޙA89QD~KmwOvHR| <#tq(P^QNo_|0=ϰRTAec䢦g*|[%#4ܫv*l\̠bܜj{Nݐ⊸'ZXȖ%6. |fP1N>Ba#caO|09r䢺g#DsF 61q& 귰 =Ҟ@mOxw;E\b}JsTm ji' ݸӒ:':g;3:K>U6O TB e3ҏAEhR(}%El2fNT<{Ewx4? !`sؤ<[/YǑAŘy!9yh۾x3c ϸd˒Jyn2# ;#~AC㏗jgKeMÒCec^.71Nė#ab/޾|1FP O`(ja(xmwo_@4<I``u/<3%Ѫ2oR)ϖiy"|'T*5OsQ3s"ÅyTE۳]XB!4xt4B᳹.J֜j"+hz$=m"QqOZNR0+=[y Veb\z QQ3%F OҦ̘g0rD\h9Lu>39'Bhpvais'UPJE *FΉ'ȃ{(Vd^lFmX h#g#DCoN`9'ױB K>{{$"Δ$Aok\9,?YL^Xyl$]M^4@g :ԹUT@dy"m|*[\͠bh8rh(Beeish4T,l2O=:D ޾`SL4*mMABE7iRP?]KgWa+gQȄ%v6P T,NBq;4B)tCCethGڡPMzE<]GWȇE& H:bL{1L_F(ʡq}E̗3'L:TCە1sh|*35D33'"h?T),_W.haElP6& F1y;=Z*˚+DD;:əhCKDkM¾u a硲rQM4r"'M rp5SEAH%ʖ45E3XҜ']EK EEKXyl-hKͭG[X""QE'uN7„8hөu7BP,R$a`̤X?ꆏb}c=ZK%N7My&@{#T&8?'KYk*ӬTThr S9S3MsjG2aH-/KZJ(OsIiR*LDqF^s45ҠB/Xti<|K*JӬT~#z蠢D> 4L3t*N黆R4ʫL) EUL4 :]?_2,|ޫLwT^5:00+~q^/6 $MJ^|*Gݦ*U艇G>ؗPQFfK>#\蠢> PLKY ȫջr4&ٮU&npr00fЃ_U<{OiV *M-c \yT]bꑝ/7 #qVЦIy!s00@N9>*Fq硲wQ4&gNA@2`U8B4$'HUE% lGt)ǩ3#zy-ؗP1P" 3]JT%"qm"THqt) ng\*qtQ4AŔHS*tiFQ/qt"iIUb/sm/>IgsR*fV^@鐦UܩtsUiLRkEMo_BŨBEW;5uH35':?‘K5W萆.6j.fP1jNtHq߯ipz~WUyG`:$i9ME>ڰ! uCaPU$I;&IfbJTIv$iU 1_O_xT9WE&6QRms*J:'0<8WWm(JDt\nSғto_BhB4.Wh(JAh:%j9D*@Fa  *&JM_njzVHAi=%m;_sޗl\"b t"E B^I;o_l;&E_8{{I)&ER ߆>;뫽vv]xB4<l<ݫt"F:v*G]*b`[~yLl<]#Ͷ DtyU*xҫOW"]g#ERbba`,Hyu%qù(Q4%Ήi?#JAS&Ap|*[\ ͠bܼ o JD Dr!|DH;Q4qr"B ,"|0|ȑvqi\yoqrQ4qr"Gݒweo_(r]Xyl\#MP19Ҏʑ.+I1RBSUڀWsh'fP1vNDFGT/?|V ֙hXNidWɧ 6Q 7`"a`!.Z80rK>mPdt+h܊όQ+kRwc JWU8 NU0Q)diNHۮGHBC4m\Tͼs"uȤ h%әh;MWx: NEBiWqEEc*i.i]0vLE4 7~Ts"fRjMEABDEtbQW:g*a9SE,B1nNUDܐ{v=ю+Zlz:bܜ艢"Z)]EEY\"zmT8!\T|BEnK *}@!څ%ʖ85DTLC#8zg+jdP)%E5 * 5@÷:A91-`%ǥS4l;o'Z _h"w|qvo;d%(LK4 L{3/SMsI*48 Τ `! 4CB((Ȝ~t`)vhX(bAVtC;!,"66T7>sxPXuBB4._현i%D(&! *F 1zQT>^]}-oҏ Џ&7Тw"7g YhFrd\]\C$;RGz<썗%H\řpq?k+FÓeT6~(7QM@_~TӘha'0 qbiU x~pcw:SswyRlHtHUٲi˲nY(6zb?2ER3TzV.*fP1zOIA;<(vLS(˦HډTERgҫXB4.Yd)"\!BtAk "`ʪiǔH2Dz"H3'J8ƫ(QΪP"’CeH3/Ho;7ˊy*xG2 ٫40g+UI3i'} U 6׫(ďAQ)eEES$E)ΗnET{X h#"i˸ERYn^Q$AFH*7I37'o VꏈFpɐ+9Bi*`Tٸ(Cĸ9!Gt oS,U*` )bpB%Cꡒ'l:*EUx_9w?`L4*|tH5ne!EAyݿjFqI/b\H|:;N}@T$v #"z[ѵdVhzEcB4؜iv&iDtүMWh%+M5I3M'h,-UQ"iP" 6J@%R 1DF)H;*ȹDbJ.9G?4aLٕۊ)~ *9W)sQ4sD:6ےU{4%Ҏ)vqV$HUPȹDA9Q"{$=sE̫)FӽThKDthq:W4I;Iu?|*[ ]$͠b)tIkܐ; 4IPi*d4ЦIpb3cDt1&)4t*4IO{SԳtQ4*҉&2ߒK)T4I;Iu߫T*EM *щ& ݬQPo_n@꤈pF8U6.fP1NIAP^=^BR uR *[I3['ꤛNȘ$KEc:^ xUMB֙LiTS6DᱪVWP^w̃} gOJPiZ|$tr00^VƁCc`_BE9z5AE5^%aJ*h%s[ Cp g/$=*E$TAEIL~5s *.I@ӬTI399T$q}U4fҴfT+IEULy4 j~4 P#P14E轓5xgۯö/?{lbWb;UG?'Fr]ҹ;A%T4fcb*DaD N/z鍦$.EG ] ʎ/POL3%JӬT%ҡ}f*wv!BAl%|ơnoceTplN6W M6`_BHBo4.sQo4{1^Nׁxe"(b4]=fl,]b,h}%hKKWhpT:"-ZX:z-JY 2 ;(R OL/=iQj1mJ_bܼ8JM\eN~8g壧 MaL*bi.h㫆 c䴳+6>0R{/b\!8f`(8m66GLqݠȢ*uÈ,8OJ~u;wұsډ~Us nU8Bh4<7لF!Jq20086W8[8+B=M $XlB(4mH!0إ}UDK{&ZfҔȈ9Xm^$;+ȗ= ^%_FE,)6הHӞ `sE8%E,F)4&9kW$L{)X* SgxaDLo ẅ́޾ YсeciDtqĥ_`2b@K ˖eLshI;MY j˘k%S\Ĥ %+܋R9\-uk@ᇖM`JZηl3^E~a7ɗQ }*+~`^j|gdM{Q4]ӭ*s]Ӟ隆5UyMQC8bzlڡ]jTiۤM{Q4qxm \8F<۠m%ڦq>#!`1_7\[BU_LCXpX(oX<7q˹Mau4Ӟ)")T뢖jd: .Kwko_F-ZWm,^;b,rΊ$YNuɝ"jއL46matoIg14kaaͪ%_frʢ/UH<<25)h}X!LaLZxiEIx&zF?H!|ԪȟL4*|M5OWA#X]"#> +P+%@=Umh}QBtyJdt,? V&+uywX%j汊w"~E c_LVQ{&fQ7tA&FTam*Z"3Q4_q˖xEQ8ՀeuĪLq>gk,[=݈,NRqV9 ۙ.*"eUU` "Z(cDue3w O*H(H V"Ri5ER8;QIE0t} c Tķs"(;IvzL*:=IEbېbT:\M)Rsy/RUfv×NTR{&#Vʇ5_ jczij| %lB~/ l]>M((S_sweby)߁~g[-h'2hp}9ES+ k^&ޡVIcˠh~nzU4O{ڱ LAEܷk'Hb3bá-v"{w%~-mOsm/Oqg.Y;hLhr,^˦~^*SiN0B?v!ۗSVXoE*b-F ֢G1_bĨ{K`*Suz.*;qw":"77髐+8Π(}Ie:*jQ{Q5ZS}i`jaE_{W56^HbPHmn8TTQqq@d*ʢ1NtQ Q|XǸBk,[͌(Ÿ;QF%\JPHe'4uÚ/V3#`1NTR-@Ohxi+1QuEq "(t;O*=GM JE.؉Bj r-OB*`*TX(RD#uE;04;thL#K.cNBTE,؉J*D6%e80v ^X1v'4jKgp':؂KTtRѫmCELv= ݔ:QJuݒM0>j߂Alu2@jcD!uXM' Ԟ)"v|wR}G}p;hG5trRm*2f_ǥm i=~e^ګTZ.e^&?ݣRW-'@W˼iQYBG5DѥrR M,OJ`JU`RJ(##X4fv邖,JӬX~x)EXxi>',JӬX~e"hD*q_`_EkҬXW&+bad Kֵ,ؗ`QޞfS(^jM)23-]҃}S^MJ`kTԪ,ȸ n|ζKh/4U%6k2Ϣ}PYv4*A'[A "O3ވGZ&ӬX-/f`\[#X[6WqQmI qVy`XJ4XpG WR U %XY%.6Vh(mmFBUrKoWh5_Y6.f5 `"/7RJv剗B%\+tXLSPVmD0> %ֶE<)բkFxipj<``">n?zKb"J/:.AE9-iؚOh Qv6;+uL> f8DOrY[뇫D TZb iyFQ1~!P!Y-l4~-vfOiߟw}|qY[4Q+sXD`'QGq@_H_1}_t( ŹI_Ofk M[+4d'lk[AXjuu D`QoS4`-gь~{} +f"@C 6o`-|bXѹ`*liZ 9u^yP@Ր *ضQ܉ބQ1oWG8:܄3Ңk|&$jՊ4Q~֙X¿dZru&jͲR?~MVr*bkg4"5sUFQr?kwZH]AWav g$ހrc٦֢ kZH]*+2]uixç̷idc5DqEs`!H++O tQnMӀ?E7(U_[__0,[>$EۊN֟JB?Ű`D[Xiw•_º(Qw 1jlS[ cZW}OXG-Dm烶,6Itl5W}֙]ϱlu+&օڮ8-Eb]1[qƲJB:asLu飱H+f= .;uHdDIٟ:W`3uV-sԊSZKZ&ԩ_'ؤ\$ҙԺ}5-""z9_{Ԋh Dj'f^(ykDkOW˟Ii" VRĪFJ2ʖRDzϊX4Q=?VD*d^k"5^謳Mg-"tMgp]t֙謲ӳdKtX$uW*:jpZgN頪hsZ c^WV^1gi5z?}V: c]Ij8Ä>ZqX c92V,nsZ "9NAga֙b\?`2ofc:߆k]vɨHj=e+OMi-LDj_ak\vjpZq?nҼt(^JJWgJnM7Va!cqRn [%.1P+…Ou>)_7*< YT_Yhj`u#<+пrϊ֯PUg"Z8n oq^h]kߢ"4[>R6$5c֙Z z>rDv~(LDB®|6U;M@/ZEp9J' LlV18_Lll,b"T||s.Qu 'y L=L6Q0sETEm*0;QATEE*G2GT|HҀ?9ŹF `?69yVKF("U&Eim Pz6Uy-suKPҀo9%z Su!j2VZuUj.Owд=K<뽱Қb2Q +j U(jiLԫe9ȨO9}&l>]q26~ɳZC_b s#HU@{ #)kcigիeTci.ONqg+kڱS؆\U>EbVC:guJu4uIƪS ~WRמz2V/Q̳b}U0wQ3|\:e%kcz2V7~FoC> 5{ISMIԡɟX$ӬdͿ߆1*1ېĺB~(Vk)XHմ U/6R5vBʓ6Dwnge|m,jZsct0iVKٗ>=83Xt*d|m,RiVkvS1raQhнUtkŴ-t쀅kuT~~cEQ3'Hn6TsFNGAc,2.~?Ӌ__O 6uXW cIգ_ B0<"5w0VfYH+뾠PX|iu!*ǔh ]ZE<Xe'%0(vR I7 ]i+0&m&ka,ɺ{( }ELVd;K&e/ƊZĿF4hѵ8?J,*.%mka"u<,Fzz(i^+D\t b-E_XA\4II"C!Mۏ+]R^1+8#;$dui7ښ/ծEbMkc EhօS>yZX*lSm*:PӗbuIJ{i"`87 euBOӹR)Sf{bA9U³H+:Bj/ @4e"WveTi63.}hznWAkЯ4enR`^~:lH7o>n j9!!RŤEF9&+ERb5J` .u!2Eb>DBDYgf!+`ƊIxl,6 +Kې%;dͿ7V  #) K8 ]QAW=si"鉍 2&sU~6U8i<*mjIWO/V$Q@:( LV/lja,늸;%D\QFcUGF$^W'@F4ՅHH4 x3NUt;6+Ws:]"T,nUӸEUGy]흝_Xd-|U*mja,銯|S_s({uK3i cNW|UCu!=|UPȳ:Wu骘NHW|'CIiSuIsY*P7ϳbLU d*|v\j0U]U'jTS XSuyJO7_#tS5Si,R銩@h~VLU ur0UӚ;+V c3UHE*]1UQ}f}kX]UF@]յ c06]0tEWZ]VQʸAW](5wX~ݦfc]ݢ:k 5Y_wU;H3Q*O[p0XLͳZoC V6v>3uw|5 1S󬖱@ԣ5V I+4*hT=C˚;ĬnѨH+4FG|nQIreͿ7V N3fQ~B_h#N঑jQ揬>8'jtQFᨅoWp 2(:nQ־倣"֝;V,nQ cWp7 2 8*BDẙ(m8ja"R :Nm{mQ_?ȡ̯{߈2>ǼXY "ULpYwR\[qq$C趓,2Vʘ,r%5, h/QCp|Q6Ɛp[dz nhfޖgrw WӚ;xVLYMkƂ'022Wd5w0GŨ _-E]Wq %mC] c *mM;}L$|U<Ў ZF]!j=WŤUwP}"85ŅL~\򾎤մߛ(U]/鳉H'yct1noъ4{kaҪ;+mE"ͮ0hE=jvYsc4-ThU4b(AõNEu*cz)bTčS?e݉*O 7} cq;Md皘6;,$2:޷"]u̢{چݲ|ԯlvj6S8}޾ GQB|ΜG_ zJ<[4`K ?EԻ"Z.Ncc5;qTǮC03߀W]8͏6Dv;Ufbfbh@l`ȟN4ةHo:vTx`Щ>ɠ1dhx HEj"@r5^2ȩ8u{EN$l>k^OsMHЃ)O66|6u4Ҵԕ*c:D6OkWuG#7_CYq#kbzƦEz]aSwH%E ꪕ\.czxVG>"<)aqHE݁MM+O HdH]aSG@y&+Re)$Ne!DC[{`yVM$р\`,̑Ku{yEK_b݆`>FWT-.{]΁,UF۰FR5 g5'Ri.c`6,'R,X|eDk|m,TjZ|klTja, "8| T*Zd*u5w0VF"PӼOVWX5P+R4 PhT+T mpuzRN=j[N=L]D$@ *O&>nKȨD~2QQ R9L=%*ctY@_ؿLEE~04A& ˩u 7L.B"e|YLͳZWlG%.:t_ͯ\([]U߰(fmT\UcJU\fK#?4 :}zԽ[ }r̪doXbUė k-`U7Q5௢{(\Uc4&V;|с⯢톲k|r$UlYb4&4I_w*R_]:2IZ⯢e]y[௮_]Ӭ _yV,=oWKcWB=A|WeXNc^X~ *%j 5w0VLrnH>L/i}4X+V9ĻXS&k4)wZqb^3'7 *Zo9oŔZ-E]VZ\:ku8XbuZX8rQܽxS@,mVl]g7VZscj+|9÷<)Bh kcigK# 1Viky>e%WyaSlzeځm?g]gG`3Ϭ126ί,R[ zVG׈X (n" :מKc<ʩk?H5LqdlkbP{KQ9.ϑ__6~x.gN8\z+fwSeƊivZKg7LWiMDJ݁IMt8:*m?jo@ڨ&P4Ɵ?T@)]TJV_NK-.`X RVд]$XKϠ.wk.ҧ5,YMh>N4Uy2; AEjUMkbj䪖1Ժ;mqgEI4Y&3a,R뉀z~#E5چ id=U`{L*MjY+*6amϥ}UC1V7Z4b'4y``nXؾMȔ;7"ixXAi}Q4|ՈZ.ƪo#4X-=4b./$Cٳ*rʹ RX-EUěZim *'XE=bVLÛXgWe/{FUDTg4EO}]E{Y(3ҁ~zZD^ZQW7|!ȫdJ= G⏌E*A^Mk[1oWˍH*^WQ**:rJƊW7E*^W?"j$ҩu)`5w0VLś ҳH+1Xrzix`E~}Mٳ}`n``ubWZ&,:/jJmx~-4KN" _1Xg4!EJaH;ЫiBU,nW "*L%Q]ek|m,rPQa2<~]h@XisҬ5wtPƢouvЫ2ʎz })PULWP/~6iU IDϬ/Er]Uјr_vb@U ߫x Z*䠥>>(Q?ٴŸH+Z*N8th 7,1#|n4`,Hk7EKM{٠nDKͳJ<+V,cR "h3>FTYԠnDK"9磥<+hiHkZ*.xwv(ޠnDKEs['2h7ʯ̇g5꛰6tYuyVLXhWTP/TkpSҤH.cԺM-Ej]qS?} eaԍbmMx6t_TڮGe{q8H߁ n*.g=wA땅9c9utBngkR7([]u+R+|j]oyR:p0~nMͳaMD.'ZD*7t(凨tg*LS]ƊK(3*ds^7Sú2VLX?N3en>ԍbcuK6t&u=Ĭ":NY]`hS:XӬVc槊g}ETO{kV^EY4en=FRE ?2Ugq5LoQ C$UY]=g\1+mjYIuG F$UD1Gc$UD gn5k\K6$=nTtt&ZR~6$մߧb$U|8E1|=IN'm 5w0V<&1"wS$UX̻b &"_T32k$JU Q Ȩ_XGuXy.48Br(xGeXƢ̻:QLȼ8.cdlj6qTo;iͅWT,j+QQfm_Z 4u߬m : TTMu[WӑWgǕmSDZD^d!E\űm29z0}Ubfn9Wm(8閵m[lG_XRC[A3]Q\2eX]]NF0 zn@/n@Wߦ訂5bctÒHk&rm@W)僮6t0G=/0\@nZ%焄?ĽZ-LDB_˼} *~zy8<*aH+*v0u_:wm`W7®"z9|˕w+&mja, # >i.˟\iybƮfvqR8C8jVq'CP.Ŋhlja"J+zO0 `d"@vJ0 tX^yE`q\)x?Ť X-E\VQ ZEyytƇZu+mja, O8.C m3CU.gh<ƊV5|\'˿rwr )a[2QLۀŸH+*Hhzx ˡ}Ub"m#.C>ac)*&e"{>ORـX`ᅫ:tm,JYmMрҙX`U3Tt-Ϙ`u#*sXV]0mj6Vo*>UsY*q(mja"V W-3H:PXwuTeZH+* `:ei5ˡezLPXTEHVCC 2CJ;(PGPUWi@wmT5jE^ޏ~۹'?'F_}5e|Zϳ+מ3ý0V=q? a]j=jb2/j:'񵱴ZϳZbƺcjX8Ě@+XZY-cR{ z,ԤaeOOX:g+Ǡp*O6%k΅XꡒyVXt;Nl/9fKm=VFFUDHrw@VeVEC6:,R rz ] p^gq_d=VMUZsϊ) ZzT0)wNſ8[<>rw@TwYsc*`p5+QGo?sTh1fbV7~F1RO߄JEX([4GFa*3T1nRu2xkќ.]'.jnz@ȩ׉R֬=W_p2Q7~E&2/^D*]qQ?W*Ò`$eL=6ZL4D e]AU("*㩨`c}68[LI=3h5`.g5vQ1*X$  i,h45O~,,jZES XbWXy,T,jZsc!}#m(׷ TU#jwPe*&mjO$5 B)} 7`hãExՊTDAkE0Tt+TAڷH;p2Sߊ rZ¡N9ǾՐCγd5wU-4tˮpB!}=k#TC-E*^P!Mxak|IŧYMpC"<iU.kcOUX݊`V_8X=wGʝ ]H-~F&r*w⨽*ܿiEYDQAZM g'#G APT343RJ]xXԴov!# ɺmyCT LI0f^?jfofp㑴WT{dsܧ|#Uc* ڎ>:+H-bo>qO#\J<$\ƊI[F M}~~ce+}G}CSkSwYbL-0VN9QWegq#]PXϗTeLpb%u*O&Ɂ&)jU\VчjZsXE9i| iEI~2C(mXWUe Ou'5K܆TZȧq;E/C6Vf!੦5w؆1/lv_YOuZqbӟ KY{OY1*ӳH'Yj΃n/UߔDVQ65޳bdUD HkOSEZ4|78HQ`wuGe^9HW >Жz~c:]XOo?"TYiq5縌6 St]SuyTL_|ӛ£H+~4n++O>zmTw"k|T&kT-tdn0VnE#D55i.ß|T?O-Թ⧢,:w> ~*>u8OZrq+m~ja,隟kj)IrT.ctO-E:]Sg4qvۃOE;(2d-c>uR슟eIu(F!ǒ645 2l p4''jS7"T^|;ASGbԕ$F R\\e46*' -8ߴG(RecFƢTF,5QGEc|T#]X\k|m,*qQӚc|D\QAН >N|TYYƊ5~H+>*HoT*W7;Q4}|TOT?̧G-E ]QÆ7zLsY&?QQQTuU&u'*|t 4'劊+Z4&rԽ652D@ cQ.AEEi<Kfa7c"j!NIԌ[A=Ei<vv;븕 G9?U='=Yvťbz-ы'GQOC++'օ%%ELLo:,{UE:'UzʶNSxaaPU<:UNsũNw{j~9)EW^')ޘn.J4d^Aje^7Sh $ OѶ:t!OCe}2yy.;SXYz x2V.OH)46wmuy?/vhkOH)U@:z;ݪ6++}i5Ooj:];T%P1jhj`|2 X?XPw¡‹}1jX9P(np|QK- C|0L_Є *xq نhK}:ԝ u.'ȉAPC)}ZO P ^O˪A la=AvH1l3@`}1tp;PʊP#+b7CU+teh &|γwp;PqʊPCvEPqqoK(w?$f /0ְ{6$nqyhXT wR4Ct"utpT񻼏+b86vZ!PoZ%f{ <ծ'Re.ϭ˳_Є[m9o՘՞>6)zVsBƺUV&Y5rjǡ*DjP ib=P¡Bd(͏_ (c,,mZ"󭧲uuϛ 9cnҍ?Tq]0QJ g6_3ѨjwzWgn5PD#OjWsy&̱s(*"}Ecϖ8󭱬2zV n}*=WG~<ɬqEHx$ϷfU3uVϬWKmVCʓ jC>w^[cY5_gR!crTw+}=Ej}E[24R+GY1$CVL%zH+\ %3f\ZcY<}2VzqaW?:3e,[V##pDho<՚:B!dqV#&2_F+@\.|k,[^gc]`Ѹi_D2PNDz@}LHYc;͗c7ý8e,ee@6n5{c{X$Jʣ:U"oةIX$ˬ I6Iea.۴PʟuHة_Z4jA\Y$vz&҂\+՚$}ZzFLѭ7f.HZ˂H}ZcpT;0VNպz=C~|W jUz Dp=^79!+F~YODT\.<`=2*lб9^@_XlUic)w6)wV4ۊ_^&mMDz=W(Wn>ȷ3_D"|:xՃFU#R!W=|ZQ ^i3dvEN@U˛qjipEN7PU$6_g\󩕅Rk,jyttUE6 rϷ"??ctWU"n˶"P˲HxvUỾ3#{Nxj,«DJ5sYەE*=WF+}2-z? \uLc\\5 <x8zFb3 epUi,UZEpUZ\UzIܿ-Rjy~+}2xWBzXZ' aYh#E$Ъ,^Nuu{|k,Ra*>Y}H [/N;e+ea.cV6$o0/sR'<&rCel7ކ/O[sTqI gu6b4|Z[$}F|!5Vi|h c3LEwNaSXwSV1sqLr"oh8Ƒh?Zˬe/hX[%9D#$Do0Uf sxZWSom8h=k:ԃuVoe +h^hU" oduzHY0 `*w LLUƢ L}m3Z& LQ0~qF)'Rմ(m븟o<aR)W u)w)wI|A^I|c ˿0ppYz̈́|HYmäF:U:=Q.SB+k:^#*#K]]S{!:*؆ANT&y!]}&אuVXr4a`A>~uмc('YPm8L_\ӄ 7muD2\Lфu3o2Dq^ hQ>-~$ -HKEr=M-7X$ 7u}WLn*bؕখw`\X$ 7?tB ĸm+xX$ 7Т݉m:ԃb{VV\*c\7T\zUB *jc 4XHضpH?׾< Tѕ.tcʕUe,J#K7 IB;3?7@BS7`\rG*H7Up=|A"E-}c}wg$ /O&ghp+zd,zOCJo T;fy?ߟ?O~woJ80VP2Wq7a@ ,+^E+n=X\?uYl zme';A⢵5cՉ&CHКx5"Yak(U&oJym`z!YcʉɪE YQ6V48ApV"r߅jc7pyL9RY¿nj:Vjc7pm~F?JXtN 5f\ gU"8+M~FO'G58+n1_yw2']86{( w_b]Y‹a$W[Y0jcQY= fGaez^,`H>fmW~Yii!0>&$HkY}XjҪWUHQ,QYqÌU@ˋqG#U8vCfŹ!DfqC5.li\dVm,z]R_z{ Xc&ʉuǪMDbXBΉ>w a]G*1$Ka&"n(@tzTN«1c$Ka"n(jo;ՃثS-L.{Uث-J?}EZ޽ԡD_: D_+']6IsC_E[M.wt8 @_rܥjc47UpjqNM \.%B2&ȉ ?riZp,9.WY7L_҄z= 7W؈{])y}"͉*#10헯C\=ZgrVS3PWj"=n胸B!D\B՘r%jc271->O$*Ow9*GWW\G6{DLS.gU'R憳D14|%ˬ/dirV /ӈفiveQ<@\_ 爫@v6Wed[L=%Oiߪhݗ7!R/(ch"^߰y o\QtEՐVgܜre`-u/z&/jiYE-" fi#nj5BZyDʠ2V+"lO*m -qiSV7bEMytIyUͬtR Ao"+lصY[Hk6mfSfg+cR]MZjT ~/aH+Pّ<^+Qejbf2mM4FtNejjjd=~/{XeWϷ-c3Į"uA?J^f } 46kwVԮF%{)?YiVebH;-ͬW6{|fV;IKَ2)*5kQ*̦~Or23ĮF'&)3~;zrPwRVJYouZ&+p :5,<YR, e*]i>VTEp3bbjK7$V>Ab?cŸqbfV]Rی~d=~[gՊ'!f0YO7S%žeUV#,S5GRn\`0+fa@_;bߥjru۶8}}VPjbONȋIZX\]gounlRLlԿLˡA$<ѓjbfA,MOfw4A6XPG,ʱ]a@%BJ1s`&2B@X'Gtſ#(-XങԼE?><晗f9+~:f_I]`lƑ@Z_|͒zM u:4T=+ pi/bj_80WLR;6{Kw8P_ϫl\_֦c+Ʋ _oZ&տK}Ֆbo ,5EWnXbwRfqJ4QϪ?~- Z2_8oj^_a3N8՟#n$V p0`w=8d,sS%>CʡCwTÖ=Xu#,CKaմ"am!c3ebfߊ|M_=|]gt::IrYe yTͪ]jݾfS d'6K֚DQ' !@MF,Y*nw[Ͼ_W m6`dk' O8v/l ά]y!fdfa3EYtذ ݶc_tb"llK.rV[CÜ]Hgydp,&ly&Gd0g8(sOh&sP~]_+#|lW&Cb% }сa>Quu~Jn1u[9-^9 x[mnZϾ]aDUS?tO81ͣAT_h2z1oĮƝx?@U-CPap58ּˬ38#3~{fRv:j>H9w(>/l\╃rzwpYg('?RuPx#l\^R1j2 0^1]؛b.|P&f.l"bs8\lzy|]"-\m)<ۤn 5qt{:j?jlayQFf/;r8/ Y{uxq*}nrY6#ص8k36EU:as2u$׋4;X ;tYڵ8%;mڌ|.]XGhו'"ɸΗwyй0G( 9z.FL[(gv0[_|:ˡrOwܛ ,w9 F:r#l>23pŕԆaϳ~gr/Yeyy6ɹf :wATl:iso=Ɲ\pٌb'h묭*+:]9(h^Ok(o@ѭ8'jf nz.׳wH#2kqͬ`$`m Of}uAZR<jq$`Xd3ʦf ɋn&k|Bh8^@|El6Bff#О鹫s:nbo9@l9@{|\32 Dϟu2gz7̱w8.fm!XEffċY8xg67}g/g#9'yo8 Ÿq7.tmf/y;{h _.}p@_5.mf8ʨf0(9XVRo!J[w$zOӨiEڙ :^e_s81l3k*mx6#j˼زCmvVo!+}\Z] X_NAf/l\fY0O4;xMgKr^C8_&vm6N(;W:=fќ{^Hjbf4{͸Zjzatgo:+yA}CJB&vU^3닦A |a)U`_ی?R gg 'ӡL쮮q>G+=Ɔ2lJpKجz?ȫaUf-8mzl;wWM箮qu>9Db^@exy>zAbVo}KZO5}sub4aCEmP8@!U4`EubǙa\SQxlFс =QsSQP'vm"n >!8AٌO)pъZUG2،:kPYF0IpQ= utjoRݙ 06s:kP=PfP 2*ϟu"d7N,1l;嶘͘ ,C+Z(gS-CJNZ-t:Z+(TjPYi`@ʀfL64$pÀ~C6 @1~cR\Kb+WpHpP,MXup}X@rK{G$/>f +q`p.yjqtY', g'lbf4`NxC ʛe>NڌqPa?B`wtB[_^]2,uGȤqR Nx[?+aT&vm6N_H ~%o0wczB&\s-uՕ>LX.<ϗ-ױY/0a Ĕ1pw%V6c$cɫGP.Gp2وd'<8ae5V N`g=dIſ#(G CkB&<VVcP_M}ZM.VWdO7k4Ό`Z|}V3Į\|J ؏.2妓ddʿLl\fla0|Q{uGƤgĮ\,݌͂?L'jm=yӳ8ׇ|;1_ۀܛWQf[^*[^]|^8ߦNK=?x5'ש=9. 0kjm?808ȕ= 0nh8ߧPa%*)O <4{%)p#ꕙx frWz`ڃPˋ˓`8X !*[oYK%>WH7x~z$"_x6Kf}g Dd}ɿS+ b,OU6,!2Ra<ȿe;j3&PQ}9Weg##fR_e3^ٛyz^ɿܗ5.q'*KQ&D#^=\GphWĞҏc;2ɿfoȿYdGũ##J_^w>L*58oh3:HOx~WL~Q%ӭk}]V]Ǯ[fOrmO[^|JK~'$k}`]vNEڝoy6K~lƪ~Lֻt0U1o_*7'آڈpz~"1ofI~X/ǐ,Y]/ӹk\^?Iy~Т*J֏~ˋbI~b/ g]F[:##~y6Kf}֯{=]viW~5+#>WYuOw&᮴^ϋ#t EG,_e3[JNƇiˋ |^H57h`e` (0 Ѐzh@ ,}Y@_w[f,40O%=zh/Ǔ&F,,|DΦ׌g{pp`j>(å:.DŽ6X&>6x+l3<l0>-Q'܅-dS8z0 yjzylS\\m} W8 qD,>dC&!Wlg"eQ}86cjT&vf Βqn3#jKGN|cf3X3" 3~@S#z0댣3~0r[g(g+qA`pUnfOf<4b%h咔80(h߻ȕ8zNޔ rg+q``!nx,`v$DHuGX*`e)>;0mC'!A)3&O;tvk#e,rCX^|?KU2)x2j3)xbRpS#lT>)X3V H0>:&")87'+7`m`R0kf w0)X/24"\#OL {3!4Β`X7g}l8ޭɽ+ DXx$+xYjw7`GZ=豂qm',/>fHg+q$@`zz@<1+|ӀGc3 \j# O\P%'fEl6.HN# ^yV { ȋ؛s\&Sቚ9h |8^ ׉/%#uo2?s?+?! ɋY2jq$`Xga;,}ّIj0H̋15X;Z*j02xuEՕ`8o6ژSb '\g 5uۉ=~`y'o!UO>?X3VмE;?둄'& s_lYR_KV$|ZC;Ixb0\\@ʋgI0j{տ! ?xۼ{g|! adX7$gg|6{YK?^ =//fIさ͸dj Fxh$Υf//>fI/>CٌU^~p9Y溻q9[-rCǾlq%ތ/jvK遁s ĮGwps'>X4 8c<1,m~##Lٌ"ZOMIdh;물hpI(xTݢ1c)\K p IKݍw~R:#ZD_guuԉ]:E pMlbR`}%i(107巌XGhG,}Zi ֝Gh?Kq?FPc>XYUag3F}ڵ8admv=Ãg߳۾gx l8C_t3`tЀTG+ĶN}b=b+c``OՔ  >90(af fd`|w;CϷU|vvzmQ0Y@u>$A=&\'v b<tC6{o n R~qcY2hf407&˾`lAC+  b6K >VXٌ+d-1*{Pa|(ɾH͒PaY=!lC~"+͹/epy{3Fqf<4Haբ긘mc3"@#l |ZgRNF J~T=0wׇd$p~M 56կ*Zݙf2 $p'ɑA r:MZayνX^|LF>dXM d/\$2,}Y ! $!p'uƑ CWqh2 r,du ÝmƑ oؙi9Qz7<3nX_g#l|ܰڛ X~$'g bLF>nXٌ#~}>䏳A 8 Gܥa8̃Y7 *H Y2qu1nմ =3>-Gh nae3>hpï^=\VGc0pXǝ$`8ZĹ@BT|?gzaԂp8''>pX/8 Kqt = [g|P&bz8fff4|A5 ^gy$xxf xx}?y :t{ EG,ye+7n'nG聇Y< ?8,?{xKڌDG`sgu ۻȧ"WuGX*R xx[U`Rg< Iž?c;2f\Ԁqnnd]+vflT>vXٌտ/!ZSV1vX]4ތp/+7akt{ᙱs؋b.Qu$ Ýƃտ! z$IN: u擄:coH d 葄g& 21pl%IV6cohi9pQe:A<ˋXgO V6coh .Z h qU@O77yF-- o@<38N}bL\>Tm˴Lg')8XuR68a2НG ,}XK_Dތbl5^\mρg|٣L _8q?D7cXj`N +XR #X~s6L&}Fچ, #l=B{@++-3φ :ܽٓL ߀q׃/nƟ%?ӂቚCA+=F0j]xGF1FpkL`X7Q`&o~/ P1JpjɔO VV㔿nz)%xfJCCISXC\-+gJ0 #l\ ^Z/C >Kyc{_^|2}FZg #9cw5Fg:7f #C1S650iًYeB{uSc#`yh7 ; 3 #wrK(3S孾PLF>%N5j7v;I{Qh\_&v"Ufw#[f)^)+=60zD16plu%# VG D~w)=J̔`.1ھ蕘pa/Ͽ?oAi2ВHaxc zV[q0RNnH}y_ן?/~F\Z7 R*\!gF _pm b6K"{>3v<m}}0]=CD[m ۉ=0<(92gn8h7 ՞+6p1/q #\cEj+hd퐋6@Q>܀o{afEnpA%\ܰ7 .%uֻ9`E!?,ys kq  1جwah a6K.vXٌDMqe}=07#wB͒w\찶ܶI%z6l;|:,PA% ;mE\"#t0zhUf",yB@@Qև\W߃/.(@gyZ#yB8F0a,>l="txa0\\0,F{dft@J,d0Q`&ub'(/Cf^A(J8^т 068N,r86f<:# 068N,rY.;qDf }9wdՉ]E 6 mFq }" P^UbF=Ԯ" A"{<bZzNۺ֋d|t%(6(m Xr2X/2c;\s-FTN/8b)2mg[ZS@[OQPvd4I~1S')xTb3G3"_s-5lIKygR?-zm(r?2،:Չ]7Ekj{2g3y? ^\AxƵ 1䷌XGG,]@"Eyxxa0z}? F&]mCO]q1O&b_&v8ftfpz5M[fĮƩ$xx_>ehK/DcfE|/!< Oӓ+6cpص8ݟolo2xX:CٟexeRaXʖo§Sc{B"Ϲ-АeRam)V719|k(efX-$xYd߀ϓuzy[fG`ˋY2b:coORYյN[f#al޼ཉ6co*X_({g#bq{3wf7*Kil? Ɇd_ ߲^ S%+.B;S. ;wL쩲]8ȼyo6cR *T ;{Yg#ta!,Hk'Cƺ{L4iiaʏ  zROB#YGuGX*]n^]n^q\]?Bke Xr1=r%BF Y0o (ѣ/L ^"`yhߥ9C ~F>V8ۣ/L ^Ğǒ>.-Xٌi~MoDϪ?B ɆwifTѐpxGk-Ō:iÛ]Aό`RmsK/6_|ĎLj}Wg-#ğqV.ذ|39?//>fI2XFnUJE>Iz#+Tͬr #'ɪLMy K#g`=B~(;pt?bjc3ebf4M|\Fޛ #]pfz\ҷQO tah' ?d.nы$ǒEc1fnE 1cYP#xሾ_XRJ䮳v?:7c`ŏ.\Ƚר?ŰPr'vm֮fl3#xP RΑY`xf- wˑu`#t\ 4lm[|,wMY\Aπ`nDsUO`X~ N܌͂uwbSm_w>geSbejj rnjB޴}Ki[`4I 1}@<V0Y/(pn|_X$<0IX{Yʠ>lY((A>{ %?65D},6XXXzCr'K~ l<  ~/[ n_|j޴/gZ!_q̢34 I3& Ù=GUQD9,f39\یe)NFoW^g"d@2k}sx%0wR0u"PH۝:\[uxAgYuay{ #bJ"׮5Q%aà m_юd‘`Æ| ǾI†kjf*,3 {j kWqC4yY_NGp2Y@7<\hle])*SE8pø,oђ 7EeM!o[[a~('vfM 6\ٌaD?u_.lxЁny, M2lxwѕsv6ޛv8 Hf]p8Pa0ZMe 0vX{QI0I,o$a*lD}*3U{S2ILv֛g?lawH{3rpMBk$Ip'uƑ؉ XiNތтCg'D7|Zp9ebZ薮M6X \vgZ2y8Pmy R{bfNnf*IǢ/ehǔg5h܌ܻÕX+JސGA}mf (b3<'+זYl`*&aD1K%U,^y]-60V#){bw̎ u!rي ]gGf]S27c%b c3d6pصY;pu36 ~]0 ,Us>ϴ#Į͚6;yCgeDVb{:,20pYر!i`޳8,'-&)KQοLZY?BR}h9-5Nҗf/6k6 *}-wIj}3ٓfĮ͚i~8͂__m&*/ sd2@ٌn]57~A3lE/ R2@ٌĮͮosyH;%OQcOK1LZGJ/#_#y-5}g4<u]5c|>bn{ԯxUuF5?ebf4?n?&YPN+͞ ,{K2@ٌ2ebf4~ZuƚzW@^,SÈlWb i{bf < w;ѽxۻbtCU2@3V.-lT>ZR|pq:2m[j%CXG2Ys8_&wgP3!/O__e3Bz%OVXݛB(e&6b 2Tیk ~Kٌ|Ov5?Ã{ yo4gx ,F[ϟ*u.j#fp̆†1켕J}pȌ>l9%>f(6 1ꏍ|5A{w e3@ /ZQe:׊@"fd ᣆ'i|nbϨP=OsQ+͌#ĮBDfI裆+q P@G+̧K*[jr A /͒>j]VqԾaGVyJd԰L"Ub6K>j 5<#4fX{#"a͒alFCDudZgFDP2Y%5\ٌ _ `GF ԰l$(,ԖQRh׀6<{jC^ŗs̉4<G7T¯6fD`MY26a:Euj4?NS⃄bX&kbÕX+~?#bXja$boRe3 1 wK5@3 AMx16\ٌ6a[St6;e3C 5N:~Zg~Zk{ӺDa:kwp ~3-(lqh<нθ9bvI0лB79M ?<D7gIx~ ?ܣVs 8 _͒>~ڛ(02 _3i 7L1V/e:1=\&|=I06cø3h;dD|=|x? ݏ8f-0<[Jn'+t8_U'r3?L q6kO„D;UsH| _ŎL~\ y(/fMK=k\,~ A`E߹KaaGsYgr77/k~ FVooZU@ جObG\3z`x[Uh`E6fRԞڈaC- Fb06tO)QNwEe \s-j"T2ÂŪ_Âa @ߑ WWDǰ1% l ?hSrX1UmaE"88fdWV@,nLnkmh Le<6{s <#^kRf'v=Z̽\ٌ#Л  !7Pt7cKHd4z} n^>yTl)ebw`,C :`. .6k$(ڌ#xiqՆ0BXˋ7؛I0If(0"bRY !<2BXɷY !I0B8 oYaTֈ oΒ (4Ck,:m[, f (pE7ƀ8 cL>B!ү~+lq@! ?,8 }#! OT,tg֣3ZY 'Q; .SVk  ƺ4I_qݨFN+ ݣEN^"m"m:ʓP3H[L$}'&5$VCVHYeboQsEZ>|z/F %率ua,aybᘥO4F[{4F*}/Gck+O J Y4Q{ZlXz+1{&C FHih^++zbW3@5xP^%**,J4fJ(1Zlb5P ,1:Ј#Dfɢ"F\l4bx:C'5bшчm&,tY*O#lG FGKEpfxd1\\H!F#,H4f\ThĀӬӷEF<2"6 )l $|qe3.*޼}uV byE} k\ A1 K$X pl*% , f|3 8pB{A<2X_g1qfɆ>{6\.=+Y@<2-b͒O l @3>q?I ; M>HPb-oK4֙nHAQE[gI/HoxlOvSe4 ^Xg/C#D b}%~__ݾ/,bY">[}Ϭv'IЛF:j2S2__gpI%DH?U`)}q]OOd].D|?Cfם7ED*-@ 覽p d >BRA*|ZT(_&vWWl?BfjG?lf4/6kIJH?UlǶneĮ͚e6sˆ:H?UlS?5*ɀ͘H\&vmvxeNOVU1X (i! qصY3\gAD*:P[(/6kDe" 8`*dĮک]d|fb3\R'LT( )'l&/%ēO$+s[f#D[,}"qe3O"Db\ (Lg\8eqMDIyz_"CG2@ٌ{bwoof8@_Fg(+(QxzLYB,du%տO!.;)ĸ_eFI@usZe3V LfI_K O[awZ xڌebwGS(8#/e2X5l{N5 P6cOڬ?Pտ{1Iџ^ c]3gpصY;}36s Tr 9=3~6j'vmN'1“l_ank}ͱ5@ٌ#|x%{ xW6ckXq27]CXG@2Y/X-x4aN0f# a͒>HZiH'K5@ٌ$yxbw&|pe3> ms 2Vf3$,ϹPu|paǗsБݍ3jq$˯7Y21•@a!h7@d/Jtb0  Om0¡#/ܿH뇩o9"a&,ycuFU#gufEDMl|pe3PxU9׼uƑ@(|xf( wr~ߢ#FV-k,G-l:H@2"k I0(0p3Pb޴D@wS^:K>PZg(0@*Jn[@ቁ=kvqMh (\ٌ^:`t-,N,ayW-VWRb)O #]ّ,Ê`@H@ڣsgY$|6 &@ $r&A“l_q@+," D3AH4C/I:\E͔}o<[L> 2ty` 4C;{q%O.6cSuߡ:4G "alfC-4h,}l{[4p8E)'-"D2CE1{&C Hm:N )[A_'g'&]B͒!O$l!ŃH&]9Κ#&>Pe&6RGB߄]+}:xGۑB\lbCe50L)0y;2f\:(݌>lpFZC^vd;} y1,^P  t$>"ayV=W*Wa<}o'fñE1ϟǑAe3= :9,|C%幗:Y=Ř}p`#čM7ˆxtcJ*}7\.V7<#TZ=kvJ&OlJ_/%|-a8c,}pe3V0X׭Gw{bw7•X+=h޴2v?/q\jpDͽ̸`O(TY;|x͟Ýmƚ_qq%oRwF;g~Se(kuoh]3VM$glalOr 3VPɿ C)I0lf 1wXeSI0Ifwxԃ,޴?s^lN'wx eVNז=5b?Kr';|kN'tT#k1Yie:RM26\Yտ w{2zU$Ĵayߑ1plu%տOl_цpEV lfmxV:Tk8,#2_8 d-I0I3mX-p`*z䯥 5< ׾\Wem3U[rfgp7.Pٌ5|*hٰhӆ?FyduO*+mzdK&Xig0t#6ka8GcXht خ1,τayEcSIh0.AKW`t%Gr˵c4xbho48#`8880ZN/6kn_U6gD0\/^BO.{Kyflv%_5q}7",eD0* fI#%^wXpqe gɊ\lƈ`x\[Mno!'Fݽl8W,paUo  `1%}Xpe3>P`-._ 7 Q'L&a lq`\5@ٌ,~qXU<`;b@ڬ|79a'FEf*$*$ `T 1#(J ~_DXg8G~64?w3Đo@ GW6s F^c-4aEN[)lƧ&zz4Y \ٌOճzƕ4udg#  49{/O Je5:P1LjQ$P&v" YD@_ K~&:[S[e:RrVS/mv2@ٌصY3YO}6̕##-+H]5;#/CO/6|\@7[ /CըLZYRJ F>XH(QP&vm$lY^0>k. xڌebfNٕP;d|S U@52@ٌ"2kjf۟h$/_Kٜd e8p$FQPs9,*ϿB|v,aϟ W$aL,"lvw6;Vl{ӳ x_ a8!iς4owx P[Ç{bϞrÇ=K8Fwm xrL?fHD~rf@¥6@BчJep!Lf@¥6xG! P6@"Bɚ,WPLmƁoh ў-X2xpÕ>ju 7be\e5"a&+w0jTH$Cqp|.Y\2>}3 jÏ!B͵9@ٌÇX~: Dؾz]یd}aeYU[f`cw atՎ~uٳŷHVu5@ٌc~_$xv:^ ߇[f#[,]pm3*$?[<l?BZ$}xHȻttDJcR#u[wb`KYYP&+]pm5:MXɢHy{b{)/b%#:\ٌ <28sڝ#a:K:\ی=dvY<S923lCӟ*[Kv2@ٌ~0,bI0fL3aXʙ8gaf)G! /@a$ óK&k~EF;" 6 \_͒>.ak~EFw/ q P{7 D)$a1{5" w=>ZcAaX\ᙹ0u[wQbͯP kQ5@.?0 lƨ`g[V; ]ᢁ+K1m;Si'JHLvěw=x,.wT ZD ոŞpjA  Ɲ,p`y{I80HL}Y+8pk@n)/#K̚ f5u.juwY ܋M":FnZpNBp:uk _PFxYGn/D\]GY:h,0=mawAb5`VZ(Qs,$Qp^;V6M^|V;ey,tnWpxOFx='n3gӷ8ȬN%5\&8mLz\یu=mysku&)$xvXk0z,{1K3WŋSI/HЋa=JwuY N/6k߳E{>Ë)^;fٴ,̰2CߘJ~:uZX93c%/)M˖M˖x?ie%W40 O<|P-F0Oq've7(h_vJ.#8p`È`t<3#Esmv'-fd'#Xڗ-#ڗ-G5pvt3ڗ}5~iwDR=.f3Į=CC̞ .?ֻڗkh)E sq@ogWBM.? \CKu4)g`ye b+2z׾*Q.3xP,j`  1%z}qРwV@FkZi| B ol,r8|Pq_% 戔6~& ć p)ǰ33|/b} iʋq^=8ಘ33^l* $3Р8P%Ku= ,f`y`8P_~W*gf-Y2p?c Fz˟άfEgI2+13]oo#//`G&p"AW|`4_*/f-θ$(_͒.FW|\u7>BY"!X+>0T8Ѣ%MH^J*dxRd]-,`yRrI첀},`z_S^o} s^]}//bu%m^{]b܄mQ!p,<K\ی5\ P!pfIﲀkW,`tC6s& .{5,wY8`?:"}W\"/LuLck{|giPuR}D8.dڛ:_=$mL>q(fĮ͚6 }-ܫ wdi2kf!ig&SeIq/Q P6#_&vm֬gI2#Կ 5wpebe]-6k,I"|fLbeN2@ٌ2kЕHl(w=قgp2i3&]5YD(b34B'A'vft1L ^7Y.X(pfл:5+ ~Į͚eQn3="O\ Mu_S2e:RMꢌ?h7JG_X{6onfA _DuX%kW~; տ.T!c6;eW&kbQ%6vIaqkeW&kb\)9Yۏ 7LnWl?_GC}e꾥֓r2@ٌ{x %-{Sy1 Y6h2i3F/g;p3_KFU»Ρw̄o]_/9@ٌ+ğ%{~@Y=WY_C3@ŏԷU, 0N(yP(q$dMl|oe3/Rq:gUA> hĞڐo;l' 2ߜfqFV#W&kΒ-6ch P6H ɚ, pf|{8ł{3P$5ٮ3_`$p_x?]6<\]-ӹ^z^,}oX+/~-Lϰ@w|1Yͬ`=cuEMͯj}[$N$X+/yțU:=о3y~{&֙&P?O'1*sn%[cV_wBHasc6K>/ 2>:tq,5$C \lƼ`xw;f5@} _obd+qHP`4*I0/X+:Hd$ɅD F.cjuPe~,I iYP)훆/kwA(Poen"qyw$E bҢ/ 4F,5YgIS_S{dpwE ^ 8HQC^f֙{㧪Pof֑,t8fyX+^0H D,r`l>9،DOi?;o{bOɋ$wf9x~y(F ui5&s.c+-/뗓 Q:6O , 2$C%И!,CwЬL.6k4"3v((Y:$x ^vgRqhw&yCW(CS~eѴ2Jc6Kywcހh2fOxa0\[@ex1K%#'\Y#'˦d?f& /<"j:' W6AFc4(ى,0ڛD Y+ 'b(jw | P^]$ ÝlvPm @z̊0\&vmj$g' _{sG0RC.ae-0ތc͒' W6FB0 \o|NܛL|7$aCEM|Segְd[|5\35<~? u'Į?ka:cY8یKkbtbK.5h)5ʼUkxaְ#_Yu۾p$>|gêjGWn{+W{,|0NyE=nŒW6cˆƹ*.>xa|:k?0'>XڕX+D0?ιK*U^ P-Y2# OÝ\h{S&vͬCkz"&A>g` ÂĮ80]"fgBT fFEf;t36 Mי=-- ^VA˯Ț4݌kĕ?c@ϟ>V' {P Ip0JLj!V/Z|UMl/ .{-EIp⃃Ƒo{E |&/b%|ppe3s;Z{=B J8P`T˖?Y 00-Ŕo@>0G r;e_õEl.pt_L\l`xGX.5m,`0:ތD1`plo&>00 ~{t]r2:|lH?,8Pa+Ҍ:ʄmb͒}tpe3+t#?akWÉ,e\X3:wY-tꏠoLD6{L;cԘ ^,+WyJj'W{u׀g* AM( \ٌվB#Dҟc(nP| ܉ܗn'z[vћY_jau !c܌M+q{%>4D/7j|=i04L1hplO&5 l_A{m.3 YDǠ1%5 l_A7O*w|-h0OYDǠ1%5 l ?h}l٥ -(`ư #zVdCZd__s}+c`q3ϟ*6qUEWlF́gVz1{~H߫>XFhxeڲq2U7U IDصg fܱJ,mF -ܴ7g P6kebf@"fܱ\Mftl CfPdT(QHQ&vm:6,R3PjJ P6c2kPPf:رꃠe͘?\&vm*,R8(wbM5=WM>'|jc؀n>l@ـ3LKzn4hexx"))Рofx}JC.|+)ۼ}q~^2>|FB1 ?AQAQ=r -gtpxе,ȷIm B4é8|g!D)&nAB}os3D~s~|gdP4$|:%mU&=[$7Ӹ(חͨ6D5:V"nfWN;q|@y+1ӒXWNZ6:E QlF8\T3|JħXkQwh}!6hZp+q}ٔo3Y +0ze(33y/2kPC>S ?-w[Y27>q0">'?w80Uk @|@0Ӓ ğq3ӸA۠II-3">KVcg\8ac((N#h37߉߱WX?ţ#&:{Xt'^t嫓gG짏,GDf$`ogîAXtDA{>?}[ޖ-yIDt%p3N0֏۝|0nNRIynEK7*{-x7B.~vxg?EqDLxWI?ca $ %"kqɢDl.7RzXGLmpXRv J,gQca=#gGdfZRv ˂>㤿^@^Mma+ bt48pzU3L͚i 6єJ|@IX | `,A+'Azޖ?Jr#T3Yr,F+'Fg߅& JÊGkL8uZi]~VZeUGgT"0]ydUxFLyƻ"Px5q]YZ2k7^|i܆(M`Px~E wXFb+kG ! c~D>g| .&0D: v-&52 |ϒXgN}[n__Rp7^bJ(T""9wJH_QO!8//#J+yS^^?%:hF &3VeckTNyYD lϒYX wJ7T=J)8^~ EJ΃qQ>O67D7&g 牳ƆkV7{"0ЊQJgx-qq7`yn5F)#p" %oq%}} IXcNgx:A:<}|3 t:Kc .z\,8}拹U1}_|4 >'',)7bo N*Bu>rRpiNjGn8dp)\^ojj߭70Z=SX<|ЋA, <<8No3Ń. |K cg\48`ԳuݲSp>ANj,T4Y`,'lՔdX,P› "el}< "2Tpxp4NKb1% ϸTp 7A$U/}1ϒBTg\(8/nf'%?Ewq$߯۽id,k%+_riBΟ rX2>zP3_hH"+SX2|pXN>㜿 _qWXی\ ֳ`08&/J!Z*`t!$OͮLpSɜ,ܬ;M>J& gA1ϒ9X&L0*"ruЌz|SMCq"Og-\傱ꤘ\pl%\p3\@6jbq;H |B r[Y.>zh%㽡sq}Vˊa o*o$ѿab&N.*B,񹉒 \eng%g; و )Ϯ>L$8%"*7lv QQ?@_'\욗'傱W1UߢU" \p4-+ +R.HgwrNֻ h*W$cOĂC_&Xb9 6o*רX0*EĂE.|b8wb;zezuT Dd9R6H䜿 X'Ԁţ%sk^9 ^@~Pt =5oͬbLEtwܠE#]/l^if|/bN4~<}&Hu^UengLC4w{CY4XyeCv3> [>fmm]<_ >_<ټS,泶'=~Y+/ z*Db =yb[{H?_<?ٰO3: n_M9앿Q}لKASWS?ը$~ɭ.x8Bmc_dFXϪE m[(?_<ٴ=`&^3(g|fX)&3?_<ٴ> T]8Qxi9?ς9??լg/$E*g-uCOMN `=NOj /nZuCMqzY0/6mn.TX:_C_M&o&o f_qeLJ>|F7y}6/׷.oGO:?2NjG>'I X,&Yz8sk >|F狇>oX÷gДIOSGt{fGIXG/jOگ{ʈr}7ʽ8wʽ /iϋ n=^<&em fuʽ߿ߠB*ro)_g2+wյ~Eck{z8eI_,( V, vMD8gh4b-޷K3"*eS;Vm|Fzg6Q geos^TU|ѽh}o0Ϧ>W o3k6>~]^Sp> {X÷WNF<6/:P|Cm"C6>Z~}&8_J "[͈I9_CW^ n6g|}"+ג||ўJ&ዦ7Aן2X [ߝ^mQZQΘRVg`o曧Gy_?C=_ ?wDJBu/+rv\ŒP c+8N ;mJ˒GG>LCIgO3Hۂ [KI+P},#ΐ@L$0γ6A|3>9hԋf/$?7΍VgQ&*ϡ(p5;Qְw+ , l`U38w;ۇgJEţ?$>yP<ۄO3%g:5`oD*gl2stjz3'l T_fJ*{< <-z&q / X*>zİ.Ϡ( D3Vd 0 n c-|0Q /hC-YT]o}5 Ɲ :\ƙ P0G"`SͬgӁg\tB7hly ټj`lt:' L@zE`p7 Ǿͤ`w(Cw ^p2Tk`|B ttl(k'.hR~pX<`p}ٌlSln}ƕ@'{h¦jx01}z9C3Y Ń[q-ăumߊW_O3ASfZ `(+'Zhm|f|֤ăI^|*+Z4`(ŃN[} `_,ql YtME;n(oETM;ܴs3}[u1ھAtx?]?+~j0/v&h~9 -J 8^cy&iCv){sKG!ϒw x);jε/ z*u[Oq~ћeZ#.NrЈBn:o2vE; `l =ZT >_,?1>#Q`X:\Yrxn0Ȋ6/Oj8w=CM33p+ͳHI`ƈ%f,\=-PZ²R>sAN˔9-?Tng;`pVQ&RW4>NT8-)% ta\2S_ƟgH&i9-y0TngNq\)oNKCIi\8I`| .(I`PyV3ARKJzkF&pL+8-yg( 4>pNA=_Yibe}z` 8Uu?RR |LKEC7c5꜆ 7gϩɭF^Ӹ 3-S6Ӓwo'o.0gU _V~Y2'9-yh(~|$Ѭ:IIY"\'I`uOm!'~:GD0m@&%^X_qA$:puz?L1i{Oڦʛ* ) XϹ' JYX#Z"}Ғ{3ȇ̕C,A^ϹN' m8dļ,.k K@5Nk:>/5 N-^)KbMP13&Ebize_Մ駯)RleEN*>KE>wB]pot FyP! .YB]g 8gA7 08fr]u# zxl,[Lx<}:\ͳT5|%g |5p*hu[)].^i՚͋>UR5@GϐH5kCͫ6D khԊl~+#UV:1 ԉG);8q}6/ϩr2:kV~:y1<|@>c%NqSiy41{>9YxYo of f5y|@|?M|lϒ9unjڟc]h)ǯOY2j7_$iuqp<3R&n^<\9eb[Nb+?)r"/< =5/ߴ|3ؐVpH2VO|CmXqUd!&S q!r_=6,}ނ-8qx89bl TP .-7{o9q!>OHr2`U&Iv2LG~B~Os>Ur(?~;)4:-~!?lessȱ?zlN6-F>S>7/cy;v$1jo0FNfDM7\ E]x댰F0sn8moO7IVS`0N>#-CK7ߌςI>i㟶?c+CrO&8qzy N`ؖXiDǣǴ~1&? 7/l&&ΛiCŒ0im+m?АdHhA,w30 3>=.ᡒVO-mr/D=2+yg"L4O(?u޾@;4(]w;pN "|lDKcg\8athl`9?6Zb Mv7G'mD}xBS 5oTM7־OP!9`8q <>S>I7/ήyy6E݌ςyH:>׷L{x-]/l^ֿf|}g?x4̓|åg$ܼxyY&Y0 6<|9@:)!2yg,|~3|n~;@eÎx? "|lϒcgDhT9?ZJdVfND~'"b5kN*͑Sg %p38DuFK: s$`xjޅMO7Oq_ +B8vxnn&Ypᬿ~ܑM\Yoy4ڼ 'lHlߍ@thA6+?^LUu7ȝ~'~=_zm>gxmλ哓 F`36\W0po{p>Rp>? |0rr-;gN.]lPΕ r'>KnW_M;mg{?B,vbxn^ &X,58wb+=Lb.bxn^u,{6gMMݏz@,xٌ\c,XD]NW s? |浧>g6m9>gc=1 h}A.W( /7R.;j#8L<~sf+HgRƌ_,Ivaa?q u9;'W]n\F8a{h+C:lpʚqAS.sxof=z-[n\GtïA4$Hn[CmJ"涜-7q!8 b-}!+>](B촘qiqInƵDy;}ޏדٖan x(4T0CJp) :/orVp> " ;|$%x,;+';wtS!;;Wa< B)HNvV|'; WNjfQgIrXvg\}zQC&K!;p->~+dyP$~/Սϸƍ }FHUW53C)xgr>6*D1f%$o 6Ӓg EG5@'ANg"U "G!'̳-("њޕ׹;#' 7/h1< _,y W:b񹟒Pgo9q% >Ϙgɳqq%qckN"ƿ+!E̅fL8VhnMq4.:-ۏuHD q)P"sJ1~:V @(ZD -ܿ奿 s$ؼ`̮9HNvtaa _Xϸ8^0.+H.0báf|Lt@ﴇ7(Ȅ0BTϒXzg{B/LHg a.c1%Í8w63v+V2eήy9&՛"9Ol/}V%6"dk!8p/o2B;L_ 7Iߌφ$a[:-v5\T n^<:l4lR?e+@x C(B=wϖ3+E.tݶŽq6Na%GJfoǸbد?nRS?'0 X?? lWaev?_^rHYi3hfм͠56-~m ZcВbtX YXNJjmOyzhfм͠56-y̢m ZcВE̳4o3hCKna'{hfм͠56-yd^+{h{0yA6d4\d$kf^"Kd"w6絍 ` .2h@c5EfA 6.pk~E7h".23h׵@qk".r7ϵd4\dM&"wzhh h*h ݳZ2.B6h 8- y!AQ5Q\s Dm@c&"wג\Eph6,\e'&;\\dg.o.8MDN"w4Y.2x\dg^85 cl^@pqkh ^@pg=d4\l5 %"/d3h".r7CKFE^fD4\n 2bh ݳZ.25Efk.2${s "p@pyAI."l3h 8MFE-5Eqhq@pD4\nZ2.B6&"w clИ}6v 6.p".B6&"w4 y!A@pqZ2.B6&"wEEq$&ȝ{-yTpEfA|k]e'"p"8^"'ȋ"暈܍d4\lMDEyhh tEf5!7`A9t6\m .2k f^kyhh .2 l3h E^m&th׺N5EEq:$ȫ"ͼ;El3hugWy|6|g3D4\U"ͼv 6.p_kfP .r7{- y\dkjUpf^"6X\}Ǚ|0h`g_myyMDEƹ?h .2M" ^E^\} .2T"w6N*tf^@"w-B ȝ-I.*l3@pqZ2.*l3hpE^\}h ͵`'PE^m5 u׍&jEpE)\Upf^.3^@pzk]ugh {=4Q\"I.>}B*t;kI.*lÒ+lqE^\}8E\d~֠]l\\UpfP u׍& y Z6\Upf^Zyh {Nѵ.3Dm {=4 u׍&"D4E^7~pCZyMrn]<El MrWEf" K5yMrk".2?kЮN5E^mZyh".^.r7ϵd4\Upf^Zyh {D4E^7.pCn 4 y&.3D4\׺kwEq .rgs_h .2 mXrk^@pYv 6~pb .*l?׺@@p@pq~%"6׺xMDE&.3D4\׺@@pzh"\"?8MDE롉hp&x ]d@"w6&ȫEf" K5yMrk".2?kЮN5E^mZyh".^.r7ϵd4\Upf^Zyh {D4E^7~pCZyh".^MDk]ugh {=4 u$y5c\da]"I.>Cc/Tp;[<6.p*ȫ"͠ 5yMrk~?` ]nh@c& .r7CKFE^mZyM"ԯ5o3h"\sפ.>CA6&5yMra*kwȫ"ͼ&܍?Gƶ? [\dEfAl3hG^ڀ08t6ܝ"ͼрmK#E{"g^ 坢}Wy4Am@Acрm$yрǙ8t65[3^Acgи6lZo\d4l? GwP5ٜג\" ^\dk\G^@pYvSHrm5 ff' ^ϵk"\sI.>~7h 8$!l4Ef\Gw^\d?OJ@v+YAoN^F>@oTEr$E޴>{욲漁k {"`m,Y o53Ys +4X,;d;ESh fC܋c dp/kz `m,A6 ,k*lZ -d"VYٵ*"Cy\}5`7g-"EvMNo5kE~,N>Vsm^|Y`cX7Ȯ)k f߳Xd;E!{]ӏ ^_pm"EvM"Cy{Aam,UA睢E~ZXd*E~,*(țZ"EvMYțv`"A7Yw"//(t`} B ,k*(zAÝ" ,k*( ~k=pX7Ȯ)487Yw"/kS"X͵Cn`|kE~,ЀEv_,򦵹o`]7Xdת3YYs`}BwB ,kzAÝ" ,kkS[eEvMYn,롅E~욲`f!5"5wB "N܋zAÝ" ,k*(tE~, XMkY Y7ȮUAEv"Cy{֠c B ,kt`7_/h ^7ȮiA7n,Xd,;d5){}ڼSo55)2M dp/k ܋\ ܋553Yos XdUh"oZZ"Ev ,k5f߳Xdno`]S֠zAn"EvMs ov`"Р,;d5p/\A"^d4נț4<7{"¹"Cy{AaE*|noٵ*(܋Z6ߋYoY{W5ByE~ýȮ)kpn"o,Xdt`7_-p/XdTPZ E~Bn0E^@7{}^,EvMs ܋zAn"EvMn0E^^Pp/UAnoٵ*(ȮUh3YoY{Wy\ Y7Ȯ)k E|=E~Рzha7{"B7n,XdtE~,>Vo B "X vmB"y"`p/kkS4ߋ|,s͵]۳v~YpB>6 ΐE^OֳuXdTP)k#|,7ESּlZZ N5eO]S~zh) "zPyzh7EO]ShMѦвnp½Ȯ)k \SAo/ _te)k 6e-dOٵ܋Zem Yo?+4)\Shs7YEvM56!=k"țZ68EvMY "!=k_A7yXXdה5Z E>MS|m^ E^^P}ʚmZZ68'n"РZh!|ZXdת3|,țֳg'Ȯ)k ^C "p/kʚC 'Ȯ)43|,Xd`^,iVYA""7"oXXdTPZ""VYٵ*"!mțֳv`O`]ShS4֡"gΤ ;E"oc\k)4Xț| C p/i;E  ΐE^B7*""A7ycS, Yo5,k"!=4870P`74dO`]S`m,롅k`O`]S`m0g"//(c5~Y '܋욲` E^5?EvB"_8_Shpn0g"/c5sE>'n"`fϐE^5`Wi YXd'Xdת3|,)2>V!|욲k`7_/h655)Yk"g5`ݧ;E'Ȯ6y܋ `fϐE^B7Ǫs7E>EvMtE>Cy[րEv_,򦵹'ȮUրEv :g"/t`}BA""A7yXXd4נ,en,zFSh6Y3d5Xc5杢E>EvMs NE|}|½Ȯp<ߋ|"//(wР,B}ț 'Ȯ\ٵz f YYn,Uhs7YXdה5"o^а|tE>,p E^h| "!}A7j dO`]\n,s-""B7Y3d>V Bٵ*(ȮUh3|,*(țZ""ț|½Ȯ)kpn0ߋ|"//(wР,en,zFSh f YYn,:wE>EvMt`7_ n, ,k*(tE>Cy{AcUй, ,kUP`]f YoY}UP`7͵E>EvMYn,롅XXdה5Z65`f/ t`3Z70Bn0g"/ c dO`]kXZ E>EvMn0ߋ|"//(tǪc7By\pݵW"/Ϳ)rBsyzhw\Shвnp ;E#|,}ʚwMY˾)EvMYs5em6W"/ >VW"/Yd)k6ش|Y 2Msyה +dg3)k6ش7EvMYo\SƵe_5eͿ)ڴlmpȮ)k_A7E{5_O)򦵬,,k5`]6W"/i=kX4ew\Snދ|Bțg-)EvMA7yµ1fB܋zha756Y+dNOA7Y+dk`} =X}W5e "_!-kk,kEBy{hSdqe XMko ,,k vf YY>Vw{/`]\n, Ȯ6E{vE*6Y EvM"yk`/5Wx/Xd;E!|ȮUAEvBY+de Ev_XMks-d/`]S`m,롅"_"N"_!pn,\w{/`]SAam, p/"`fE^^PXcU d/ckg ;E)ymȮ)487yȗĕ5`],"_UրEv :W"/{ ;Vh LShNQ"_"`X |", "Xt>EY XdTP87y"_"N"_!S,UAnȮ E|!|O\Xdת"oZ-"_UրEv :W"/k+4),,k ț4"_"`fE^^P"Xt!|c7vEvBwB55)y|,cUy(d/kZe XMk Y XdjZemfE^VPt B5X v`/`]\n0W"//(t`} :wE1 Xd),,kk E|}X Xd4נ,t`} :wEEv ,k"_!-k" ,򦵹Ȯ)k E|=X Xdה587Y+d EkA"_"¹ț4<70Ƹi vLSnȮ)4"o|^3|,>VA"_"Vٵ mfE^5`Wy\ Y Xdה5"oZ EEvMYn0חE^]de )y|Ȯ)kM"_!5>Vo B5ț4"_"B7Y+d>V˿߇kCy{hțCxXdה5)kNeOSּlZZStO+}mBMe5N }ʚSi=k) ,kʚwה;dgOݧy7ش5'ohk5Bٵ*(ȮUAgYo}5`7g-p/m4em!|O\s4)7Cy{AcڸSt,}k6ش>ײXdTP)rMYw"/Ϛa>eͿ)ڴlmp욲_!=k ;V֠"!-4+k"VYY;dk+k"oZk_8h""М>۴ZSt XC "p/k {^s >V6EEvMn, ׿VA""Vٵ mfE^5`Wy\ YXdה5XC )5e fYs`}|n7Ȯpn, 05xECy{A S`} :_87Ȯ)k fYYn,Un7Ȯk,k"!-k" ,74do`]S֠zha75e "! Eks7EEvMn, 1i )k do`]Sh6yk`o`]k`fE^@7Ǫc7,?k{A?!=4} O7g`]SּE^5)r`zֲsȮ)kk >_Sr{|}ʚai=kY7O+ S 6"EvMn : >!~>eͻe_~^dה5?EvMYA"EvBٵ mf?!" ,򦵂"EvMYn,ekȮ)4?EvM >!6p ֳ_O\?)k6pMY >_8 +4"OkO"//Oo64[|EvMu5em߳k)kMѦ,Ev,kE,i=kXȮ)4?E޴Z EB7yµ`]S֠,'d5Xc d?"Nk m`]Sh6yv`?"k^Ox/c`f?!-4`Wi!1ƸQdZ5`]`f?!=k53 YN>VY!5v^Z6,k*(,'d"Xt!5ț4"^dTPȟ^n"Xt) Y܋ZEvBYO"/܋*(܋im,Xdה5){7_-\5e f ;E"X͵ d?"Nț4<7"¹|/'y{AaXd`]SAaX ț`]ЀEv CnBs56E^zCE޴Ӕ5){7_-),k,'dvEkNQ"EvM"`7_/hx ,XdTP87YO"//("cU d?"B7y󵂆,EvB3_XM롅X܋Bof܋>;E3 YN>^߾^а5e "By{֠c,XdB7yXi vE|=pXȮiN"By\"`}ڼS`]Z `]Sh6ySdHXdji=pm"LS`m"oZ E욲`f?! 5XY" d?"РzAn,4XC `]\n0ȟE^>נc5n`]Z6ȟE^5`Wy\ YȮ)kpn,롅XȮ)kpn0ȟE^^P87j!5ț4<7,k*(,'d E*|n`]SAX c\4MNi B,kȮUh3 Yos Xdk,򦵂,Xdה5"oZ E욲`f?! Eks7YȮ E|a7,k*(tE,B7Ǫs7,Xdה5X,'dg "XemO"/kڿO"/Yd)4?7شZS욲5emdE^5?7p`zֲXdה55emO"/ϚOY/L7g-Ȯ)kS䚲6~a|Y=k ܧ)e;EO]Sh 6 `]SA}m :wE~EvBٵ mfE^6׀Ev_XMk YXdה5"oZ6xEvMk*Vsm!O\Xd*k"oZ+h"?"VȮUf Yo5XA7ycvEv6 B5e vE|=kXXdN"?!}Xd;E! k`7_/h E~EvMn0O"//(t`} :wE~Ev ,k"?!-k" ,򦵹Ȯ)k E|=욲kE~By{AaXd6,,k*(zAs``]SAahfE^^P87ǪNQ"?"Nț4<75v{E^VP+k"VY'y{hp ,Uh#,)r_XMks-5 E|=pXXdה5)Y'dvEkNQ"?"Nț4)5vf YSd`} :,,k*("ZACٵ Xd*k}=E~EvM9}BA"?v Ȯk,k1MYn"oZ E~EvMYn0O"/0XYiks7YXdt`7_/h E~EvMn[롅;E"?"`hfE^>`Xd6,,k5׀EvBY'decXZx/,k vE|=Z"oZ-"?"Р, Ez nȮiA7yXXdTPZ E~EvMA7Y'd5"X͵,,k5׀EvBY'de XdUAE޴6B5e țv``]S`fE^^P87j! k`7_/hxn,,k*(, "Xt>7YXdTP87y"?"¹"?!WրEv :O"/ ;i!욲XC Ȯ)k f Yn,\A"?"B7yXXdTP3,B7Ǫs7YXdTP"o^аc\̦)4870MY?e,VPݵ-kGѹͺȻCXzThʚuДE?Ϛ}a >eͺE;E˯Y7MNѮТ/LQW^Nh ;0ݵZ Q])4CvQ_Y7MNѮТzTWhv B]Eݠlmk-EYdwYdwmdn`6wEGNikݧB7vg믄n`BE.{60Xs "OA"ףY vgSd `dvcUP"OA",2hUPgAF(\O\Ys*ȻZ"ף`m`"Z E4e FN1*( Et\vgemߋ Z9 Z6Ga7{<Yd)>? k"VoȻZ"ףEBn[롅Y+`m0e1*( E 2;E "gw_/h EC70ME.{ 1VAam,2T cQpl }ZA3/gAМE6Ga7p3Z7p| m\ૂ:kYz< E6}Y_"A7Y䲇xp4;E#\pY?tg}Yz6}YwYz<¹"=+k E6wEGA70Mo(tgw_Cn,r=ߋ >emߋ ZeYd*E? 5^d5pyZA34e Ȼvg LS֠,rn` Ydi cQ LSAOYo4\8\%ttE.{ 1VA8 >t\n"OYo{vg<-E6vW"/k+d,ai=,kʚwה>{,y7p`zֲn5ew\SnEGy7pMy7pMkחkn>Իf,kZ O7"EvM5tVsm!5~/ /`]SA`f_!~>kE~,`m"Wh ~kXȮ)k f߲"B3_3 YA7*7x,/xwZ"EvMXC OE~욲3 Y/Lݧ`m0ȯE^"ck/ E~욲`f_!=k E|n/`] ;E"o, Xd*4`],+dԸc;E), Xd E|XȮ"Z-"EvMA7YW"/k ^ "By{h ^ ^XȮ f߳Xdd"ף"`ݧ`f_!-kȮUfC>V d_"A7y74d_"`m"Z ^z<>t)EG"P`7g-)e "oZ E~욲`f_!>Vsmٽ("OYn0ȯE^5kZemf_!=4WրE޴, Xdה5"oZ6,k,+d>Vsm!5 ^ v`_"`f_!pn,UAY OE~욲Zxn, XdtE~,?WYW"/ ߋ\a}[k YȮՏȮ)k3}e\`_"B7YW"//(t`}6wE~ XZ E~ `f_! E* B,k*("o^а{+487{w˿Ե\w{7pMy7شZ,kʚ \Snp,_y;E) F=4SA`zA35eͻk!=k~n>eͻe;EȮ)kk / ̵+)rMYe{]SּqȮUh3|,Рw\yZA|p0׀E>EvM7ECv`]SAzhٹ"/`IA`=Sh E|=k5 ):{6p ֳ"5em<78By{֜7p;EֲȮUրEv6G"/i=kXEvMA7E>,p,zF]{ݧkE>^d4נț4\|ȮMѦn,"`m0ߋ|"/CwР,C܋c 56yȮUh"VYY#dEv_5`7g-""Р,߳;E"Xt!|ȮiA7{7_/h E>EvMn0ߋ|"//(cUymȮS, ""N"!6{} :,"Vٵ mfE^5}UP`7͵E>EvMYs`7_-)5eO7܋BE>By\s} |/ދ=4p/:wE>^d@7yv0MNț|,ЀE޴E>^dת"V"!=k"OY"!=4XcymeM,``]S֠,߳E|܋^87{7_CSd``]SAahfE^^P)ǪNQ""Nț4d`]ЀEv6G"/+(,;E"p/k o E>EvM`E^^PEkG"/ >V d5 E|}XXd4נ, Xd d`]SAZAC{]ЀEv6G"/+q g "!=4Xci""7E"o>"``]yzhȮ)4)E>{5)j;E_8X|욲`fE^5"Xem!|P"o,"VZemfE^VP}5׀E޴pm,"`m[롅k``]Sh6Y#d"X͵ymȮ5/LE|7E""7E3|,7E"Xt {`]SAX | 3|, XMkY YEv ,k5fYY>VkE>EvMYț4\܋, ;E"Xt)?E>EvMYn0G"/t`}6wE>EvMt`7_ n,,k*(t^䟐E^?k{~{țC?"7E)kc7 YYSd)k6شȮ)kkڸ6 E^5S|hzֲw\Sh 6 'dwܧֳuC/LݧмlZ -de XdUh櫂,r]YGSh ~k=ka7nk4;ECvz$((t`} :~STwofX׳&Yn,UƵAa/XOٹ$Kn@tKK҃KAsR# uZShAahW^l?7ش^ ݠ Ekܠ0k 1c)_E³fZ,k"VexZk"Vk XMY z7"OYgE. XdV dmi ܧNѦXz Sn~-A7" m!\t`Z6!\;t`Z6wE f`Z6wEC3ȮUh3\M,]uBw\Sh;E0_ Xdw XMkk!\B7ٯUn`]SAa6yXȮS4ȟE^6"ws7YȮ)4)y`]SAahf?! EkUй,Xdת"V,'de XdUAE޴B,kzhN`]S`.'.lEksEw]SAa6y;E]|5 f ~ :w?")k m ~X`XȮpn0ȟE^VP`Wi1YȮUAEv :ȟE^5)" m) Y|5e)rMͳA"EvMl,k- 1a4zha7cBXC E~<`f?! "`Z=s7YȮpn,k YȮUh"VYYO"/+(,;E"EvM)"~X_"{5)4 f ~<,1nE|}Xi țv`?"`f?!}A7ٯZA"EvMn,k YȮUh"VYYO"/+(f>6,욲;E] 1Ml,롅5N""/_kplܱBsE,XdV|n`]Zs`7__kṁ1MShSd6wEBn,kY YȮUh"VYYO"/{ Ev_="oZZ85"~X`6yݣSh0,'df`Zy6YȮ1XZ85 f "*|n`]SAzA7LE 3 Yo}țֲ`]ZE,~B B,k"o^p6,kZk0,'df`Zt~4d?"Nț4)2ƸnB4em!5X׳v`?""!c_Poٵ=kg"/Yd)46'Ȯ)kS䚲6v3dg0ue5eO]S YYn>ew6g- N`]Sּ;E Y}ʚlZZStO+5onZ-'ȮS :az,țֲȧUP`]Sh f =k>ONѦe'ȮZٵZk3|,N5 6g-{5ew\S YYs)kSi=klpO'i ov`OTP \SAs3dy)k>lZZ""VYٵ"!-k",'Ȯ)43|,l,_o!=4]dVͳ +XXdTP f YYn,_ͳA"")rM;Eg"/" m!|욲|yߵE>EvBٵ"!]d E޴wO`]Sh "!=k EkUй, ,kZkS, g`O`]SA, XdVA""B7y'ȮS4g"//("*S'ȮUAEvBY3de XdUAE޴B5e E|=pXXdה587Y3df`Zy6YXdTP)y""N"!S,_γA""N|y""N"!"ByZBٵ*(ȮZY3dg Ek|'Ȯ)kNk m B5țp65=0E>"/_k0תlk"""!=kS,_;E!|)y dO`]ЀEv6g"/+(,;E""`hfϐE^5)ٯUAnȧ1uht<, ,k f`7__kl, ,kZk0zh)2'Ȯ)43|,~ B55"oVАE>EvBٵ"!"țֳv`OӔ5)y"`O`]S`C 05,a ,_`~(dOco dO`]Shpn,k-<75587Y3d587ٯUAsE>Ev ,k"!-k" ,򦵵#2'Ȯ)4 f YYXdVgE>15Ӵ`60MͳA""`6y"""!}~ :, ,k*(zAs`O`]SA`fϐE^VP`Wi-k!|ZXdj,߳" m>7YXdה587y'Ȯil0g"//("*<, ,k*("o^pXXdTP)Y3dvEkUy) , ,ktEByWG"Cs}WNmZ-)EvMY"ה\!=k>OYMY˺,kʚ)klp,y7pțֳȮ)k \SƝ+dgg)k 6g-)EvMY5em ߻N5ֲ|ٵʚ*4`7}5>s :W"/+(|}ʚmZZvnp욲況kڸSt,S|6ش읢 Xdה5)rMYg+dg)r;EֳȮ)k~n6]?,p ,L*4),e< Sh|}W,Cco=?,pրEEvMA7Y+dg f`Zt!|w]'Xd<,,k țp6"_)4 LS d/`]Sh LShs7Y Bn`BA"_"" Bٵ Xd*EBy\`7=w/`]S`XC g.eB4t!|B"`7_Z8|, EkUй,,k*(t`7_/hS,,k*Si=2ƸfӔ5s7Y Xd*4`]f Yoi!|욲XC Ȯ)kpn0W"//(jͳA"_"l, g`/`]SAahfE^^P87ٯUA d/`]SAa6yX XdTP87Y+dXdUh"oZZ"_"VٵZk3|,;ES`hfE^*klȮ0Z g`/`]SAa6Y+df`Zt B5f`7_/h8c\7MA70MYwB5țe-d/`]ЀEv6W"/{ Ev_="oZZNȮ)43|,A7ٯUAnȮiA7yX C g`/`]Sh0,g"{Z)4xhfE^ShS4W"/ NEk;E!|Z="V,߲,"oZ{B"_"Nț|욲;E3|| )Y+d5xXdVNQ"_""owELSh0zhl,,kZk f Yk ~ Bٵ*(ȮUh3|,*(țZ"_"A7ynELShp E|=75"_!}A7ٯc0w8"_"l0W"/*kA"_"vE|1 EEvMsEBy[AEv_,򦵬,,kUP`]6W"/*> Y Xdה5"o^а|tEBy{Aתs7Y XdTP E|l,,k*(, ;E"*|nȮpn, z,YskϮ26u{ \ShlZ-;7EvMY5emw"//OYMY˺ ,kʚ!=k ܧe7Ȯ)k \SS;dgg)k 6g-)EvMY5emw"/ϚOY`ZBٵȮU~zha7"VY"oZ-W5țCvn`]SA}6pMOE^cРzֲSXdaꚲ6w"/ϚS>5)ڴl6EvMYsהqYY}ʚlZZSt욲況kxnp,țֲȷ EvMl0w"/ϚOA7Y;d~mECy{hOYMk-"]dw߳XdVgEEv~׀EvM do`]Sh0Z ȷi |yBٵ Xdת3|,Xd ,7|5`.~y{֠תl7Ȯil, 7Ȯ fYn,_ do`]SAa6y;E""l0S,_γA""Vٵ mfE^5`WyZ YXdה587y"`o`]S`fE^^P EksEEvM"`7_/hxn, ,k*(, "*<, ,k*("o^p65 fYo}țֲ7ȮUAEv"!=kSEk|7|5e LShl7Ȯ)4 E|}7Ȯ1`fE^`6ٯUA do`]SAa6y7Ȯ0, ~ :!|ZXd*ECy[րEv_XMkk"l,롅1ubBn` :wEEvMA7yXXd״֠,'k LSh0,ߟP Ek do㉕58E"O, EvMw7_{ Bٵ Xd*k3|,,1yzs`o.k ~k=|BECy{AaXdVkm) Y0MwB5oZ )55xC g`o`]Sh0, Ek do`],k"!-k" ,򦵵7Ȯ)k0zha75e ];.B7"Zks7Y zAs`o`]SAa6Y;df`Zt B5 E|7Ȯpn0w"/+(ЀE޴EEv ,kfYYs`Z6, ,kzAC XXd״ әECy{A/4t B5ț4 EEvM`fE^^P)ٯUA睢EEvM"`7^'dϚk}\vgȮ)4k 헯="|6pM Y7Lݧli=kY7xEvMYsהq6xBy{ּOYsMYfXdה5)k)߳況5ֳ="l6v'dgwܧli-k!Ze Xd*k3l[րEv_e XMY Ȯ)4?7شZS 곁k*x,N5 6g-;E~EvMY5em E^5?Ev;EֳȮ)kNkڸS,l>e 6g-)zEvMY5em<7xBy[րEv_e XMkY YXd*k]d*k3,N53,`6ٯUn|5f`7__ka75fE~By{AתlȮ ț4 E~LSh0zha755 "?!mw\YٵZk3,s_e XMk 욲;E"oZ8w.k, ~ڼSs(Ȯ)k f YYn,_ d`]cX "?!S,_γA"?"Vٵ mfE^5`WyZ YXdה5"oZ E~EvMYn0O"//(t`Z,,k*(zAs``]SAahfE^^P87ٯUA睢E~EvMX "?"l0O"/+(ЀE޴E~Ev ,kf YY"`Z6",,kzASd`zhNȮ1`fE^`6ٯZgE~EvMX "?"l0O"//("*<,,kUP`]f YoY}UP`7E~EvMYXC g``]S`6Y'df`Zy6a"``]S֠,߳XdVYA"?]d@7"oXXdTP3,B7ٯUAnȮUAEvBY'de XdUAE޴"?"A7yn,,ktE~By{Aj d`]SAaX  S`7_-|XXd״ әE~~X࠽{)kS,Y w1_Xd1A"?"VYٵ mfE^,ji!욲XC Ȯ)k f Yn,_6wE~EvMn, Ȯpn0O"//(תA"?"B7yXXdTP87Y'dXdUh"oZZ"?"VٵZk3,~B B5e E|Ȯio0O"//(g"*,,k*("o^p65fE~By{A7ٯUAg,d`]SA/෶˿g͵> ?CX/Ȯ)4g]Sh#a=k ܧ9oi=kl5W:oB`zhY7 Ȯl Yn>e 6g- "y7pMY !=k>OYni=kٹXdה5 \Sn YY")k 6e-d"VYٵ"Cy[րEv_e XMYX/Ȯ)kS䚲68hT"{)k f ;ESh>lZ/hNXdTP)rMgo"/Ϛ"OYMYf/Ȯ)k~욲6}Cy{|6pțֳ}EvMY5em<7,țֲ_`]Zemf!=k0wl0ߐE^"گk{AXi*("oZ E "Cy{Aתl_`]SAzAX/Ȯ ~k=5"CyZ}j,7d,Z)y(d"VȮ)y65),BnE| Y/Ȯ)4 E|=p3MNi* B ,k țg-"EvM? f`XdVgEZXd*E,*(țZ"EvMY"`7_-<7 ,k,7dvEksE X v`"N"Cy{AXdVwB ,k*(zAX/Ȯ0,7dXdUh"oZZ"Ev ,kf߳;E" m B ,k"o^pX/Ȯio5;E3 Yk vEk d"ț4 E "Cy{Aa6ٯUA d"Vٵ mf!-k" ,򦵵_`]S`6yX/Ȯ)k0,7df`Zy6Y/Ȯ0zA)"l0ߐE^^P EkUy65f`7_/h8w)48E"oZ"EvBٵ*"CyZ}i=k;E"Ȯ)48EawE^If`7_Z8c\k)4870Mkm!5X׳v`"C ) ,k 0Yo"/wEk7L"EvMk i m B ,kȮUh3 Yo}րE޴,Xdה5 E|=p6 ,k3 YXdVkm!5ț4"EvMn0ߐE^^P EkUy6Y/Ȯ0zAX/Ȯpn0ߐE^VP`Wi-k!ٵ*(ȮZYo"/* d"l, g`"3 Y XdVyE zAX/Ȯ0,7df`Zt B ,k*("oo"/57hZ-c_`]Sh"7LߐE^5S|hzֲ5eͻkMY}ʚwMYN_Ww"oZ- ^`]SAo"//SּlZZv욲況k ސE^5?Ev, ,k5`],߲,*k"oZZvn욲;E)kl,9}>ew6g- ^'4e \SƝiktE~Cy{A")4)ڴ^l6xEvMw\SAǝ7dgg)kNѦe;E/Ȯ)k>o"/i-k!Ze Xd*k3,l,_nȯ1ki m ""РZ g`_`]c`fߐE^֠תl/Ȯ E|l, ,k*(tE~X_"{yOl0o"/[k"ByZAC5~LfBsyzhl,kBzhN/|5e fE~CyZ"`],`XdVo]A7ٯUhs7YXd@7y """!S,_γA""Vٵ mfߐE^5`WyZ YXdה5"oZ E~EvMYn0o"//(t`Zy(d_`]SA" ~X""N"!pn,_;E! "o;E""l0o"/+(ЀE޴E~Ev ,kf YY"`Z6, ,kzAs`_`]ZSE~X'Xu6ON"!}l,_6!"oXXdTP f YXdVgE~Ev ,k"!-k" ,򦵵/Ȯ)k0zhl, ,k3,l,_6! ț4|XXdTP f YXdVgE~EvMX ""l0 Yo5,kUЙE~Cy{h,򦵂E~EvMYn,롅X욲`.~y{Aj d_`]SAzAn, ,k*(tE~X`yݣMTSh"!};E"Zk!S,kk-d_`]ЀEv6o"/+(,")k E|=욲`fߐE^^P"Zks7YXdTP"o^а `fߐE^^P EkUy6YXdTP E|l, ,k*(3, XMkY YXdת"VkmfߐE^587ٯUhA""ț4<75587Y7d)ٯUASE~EvMtZ/hB`fߐE^^P EkUy6YXdTP E|[A?2_5oٵZ"׭@Sh"&( Y7} mGl𯿇5)7?S֬Z_koPZk @Shvk=h6[_tfȠ)4CAݪ+4 @Sh v"׭/BnBS]EAݪ+4;EMlk-E>ppYO\YsʚȻֳvg E6wE[_ye iʚZZ EC7p|ڴSTvgǶ`dvUP)4^а8\%tgN;EGa70XYn`ֳvg믄n,2面E,2*k"ZZ"׭`60ȠUF(Z3Mٹh m\n,2TP#\8=OSnк"OYn0e1Zk EY=tgn'Y+46Y)BA"O\kYd1pyZXtgЌE޵Z El,2w"gG[k]d)y(cV}ZYd)d,r iޝEMMGa7"{^P;7BA"׭`4eNo?l,r0`6Y䲇c=08 >t|,2hUPgAF()2,2hUPgwE[_~Yd)kl(rn;E)k0,rce Snu+1w@Shs7X8 >vFl` S cV0l,?a7pn4 F#c"gW9k-k\*ȠZYp60Ƹnu+)2M)Ȼ4"#\Y,2Tйd,r i*()y6X)4 E}}u{ LZ`dvcYdl"Vu mdxʚȠUAE޵2nitgw_-)rni#\cB7p|Zklu+ LSAa6py;E"4fE.{8cl,2Ty6X08zAn,r| 2ÿ ZeYd*E? ĵȠc,򮵂f,r | _ͳA]"OA7Y䲇=?E6wE[A70Mtgw__ka7pn4"="{^P EޯY gcZO3ntgn"VȠUh#\v+k"VȻZ]tgРzhl,r0Р,rn` ZA"׭Ȼ4"B7Y䲇cfgγA"׭`60MY "¹"d,vn,2*4gwe-cVpn`ZYdj,rQxn`q=)4)2MͳA"׭`60MȻ4"#\c+k LSAsE[i E}a7pn4)Y䲇1*("OgE[l` }t^#du7hZ- `]Sh"FYYwܧli=klpw5вnpO+мlZ-Ȯiy7pMkm_[|6pe5ew\Sდlob)e,kʚwהqYok͸ Xd*E>By{h_A7yZٵZkSYYn,_~]۳v``]S֠,߳況Z)ks7YXdlB):~X1w=O.Shs7YXdה5)rM'Y`ݣg m!|O\^ٵz E޴ȮUAEv6G"/}3|,Рw+4 fYl,_G"/ q*(tE>By{h0*ks7Ⱦ\n,5)ڴ]+}5"!~n>;EֲȮO*4`7|Bsyzhٹ,kʚVO"!mws<,,kZ*(3|,A7ٯU d`]S`60M d`]ShS,Oh E>EvMk "!}A7ٯUA d`]Z6G"/im,,kt.롅""l0G"//("ZklȮpn`BA""РZ E>EvMk fE>ByZXdVw~X`XXdה5 fYoY}țZ""VȮUAgYYSd`Z6,,k"o^XXd״`6Y#df`Zt!| X v``]SAzhl,,k fE>ByZXdVkm BٵZk"V,߲,"oZ[k!|욲ț""l0G"//("ZklȮ0zA35XC ȮiA7Y#d5 Ekf``]S`6Y#deͿ|B3_emfE^SdܱBsZ{ BٵzBȠ)klȮ)4 E|=kl,aBsӔ,,k țg-""0Y#d?OA7\p6yw*(Sd B5ww_[k!|Z,kE>ByZ}րE޴XXdS4 Xu>t>EYXd״֠nȮ)4"on,,kZk fYO(t`Zt~4d`]SAzACXXdTP fYo}țֲȮUAEv"!=kpn,_ d`]S`6yȮi"!pn,_!| ;E"o^pXXdTP fY"`Zt B5f`7^?Cyߠk= ?EvM9}BY?Cy{ּOY`zֲs?EvMYn6E^5?7p`zk'?EvMY5em,9o>egMY˺")klg"/Ϛw)k~i=kl'Ȯ)k \SS?Cy[րEv_e|"Cs)4?7شVАEXdת]dהq63dg3)k~i=kl%Krc[SQAqy1ECj %Xdה5)rMYw>!=k>OYslzֲȮ)k>'dgwܧli=kٹXdה5)rMYgO"/ϚS>ew6e-d?"VY3_,X|5`f?!"Ol0ȟE^t`z fCXdV d?]dl,A .kZk0,'d|ٯUA .XdTP"o^p6"LSh0Zh!ٵ Xdת}=ٵz E޴Zv5e0uMYgEw]ShSdBwB,k f`7_/hSEw]Z`f?!'ce f`7_ZS,욲] l;t`7_Z .k3 Yok͸ȮՏ"By{hSdqe XMk YȮ)k E|=XȮ)kS4ȟE^^P Ek d?"l, g`?]dTP ""//(]dVgEw]SAw7_/h E `f?!"ByZB,kUP`]6ȟE^58EٯUhl`]S`6y"EvMk Ng "*|`]SAa6y`]SAa6Y "{>t>7Y|5vLSh)r"EvBٵ mf?! }րE޴,Xdה5 E|=p6,k3 YXdVkm B,k*("o^p6EvM`.'.lEkUy6Y|5f. g`?"l0ȟE^VP`Wi-k!"VٵZk3 YYXdVͳA"EvMYX "EvMk fE,l,_γ +XȮ)k f߳c"By{h)~ :!5=0`]ЀEv6ȟE^VP`Wk XMY OEBwf߳ת)r"EvMk NE|)2.k*("zhl,XdtE,XdVkm B,kZk0zAXȮ0,'dXdUh"oZZ"Ev ,kf߳~BgE욲ț4 E3 YXdVgE "o^XȮ0,'df`Zt B,k*("oG"/5h۵Z,k Yd"!=k ܧli=kٹ,kʚwהq68By{}ʚwMYfXdה5?7pMY߳wH?,ZSAlZ/hFUwțCfXdTP) ,*k"oZZ"WAMw]вXd"Y"~i=klp욲;E)k)߳況5)ڴl5e i B0XO(t.ڸStE^^P" m E^ZWAZ YXd*4`]XXd yzha70`]Sh0,֚q*(]׳vc5 Ev B5e f`7_Z E>EvMk 6]dה!3|,~ ""A7y󵂆,,kȮUfჃ9XuEVhS,Y ȇi fӔy6YXdt`7_Z E>LSh0zhNȮțC |5`.~yO.Sh fYA7ٯ:wȮ1n,kk-d.kȮUfYo"țֳv`Ӕ5"oZxn,,k, ~<,,k*("o^а| `fE^^P)ٯUA睢E>EvMw\Shs7|Bn,kk-dkzٵZk"oZ-)0,kUЙE>Byc`ܱBn`BgE>EvMl, g``]ZSE>By{AXdVOC5 E|NȮ0, ~ :wE>EvMn,k YXd*4`], ,ji=kl,aXC g``]S`6Y#df`Zy6YXdTP E|l,,k*(3|,l,_γA""l, )5yMk,aEvMk fE>By{Aa6ٯUA d`]SAa6yn,,EvMYnE|=ka70Ml,k,,kȮUAgYoO(`7g- E>EvM;E3|,"*,,kZkp , OE>EvM7Lga)E^h/SOl0G"/_k EknȮiA7yv``]SAa6Y#dXdUh"oZZ""VٵZk3|,_yi m B5ț4 E>EvMk fE>By{Aa6ٯUA d`]SAa6yȮpn0G"//(תA""l, Y߳_Cv" S"Cy{ּOY`zֲs/Ȯ)k \S߳SּlZZ6|EvMYsה|Cy{/BnBo"/g 6T}욲況k ?,pZnkʚֲ_+4`]ЀE޴Z EB/anZ-)Z5`]6ߐE^cРzֲ ,k w\SSo"/ϚZMYN"A7yв ,k +k f 곁Zi d'Shm Y/ȮUAEvBYsށE^7L]i=ka7cZy=4)r6¬_Zk";Eֳv`kN5SnɐXd)y6ჃJ\{t`k{An,r!4"o\P f.vm-;75"Zks7YyhU7ٵ mf~rEvBw~X Hr_k"O d a)i1\< XdVYxOP`6\o AF,k O74]z"onE3X |yv`Bs1A"N?]dZ3_65dBZ+h]ǐ5" ~Xw XZ0LS`6Yn`qXdה,Xdt`7__ka7 ,k*(tE,㎕5;EߐE^t`Z6,Xdה5"oB ,kji=5`f!m>3 YA7ٯ෯4"EvMYSE,N~6,XdNț4 E 3 Yn,_ d"B7y󵂆,Xd*4`],7dXdZyzs`"Рzh_`]Sh0,7dvEk d"f`7_/h85fE,N~ :!5vE|N_`]SAᝢߐE^VP`Wi-kw"VٵZk3 YY"`Z6,Xdה5xX "EvMk fE,l,_γA"EvMX "EvM`f!0תl~ ,k*("oVАEZ,kE,Zk"oZZ85"Cy{`XdVyES,կqp,XdTP8EYo"//(S,_μA"1nShl_`]ZXZ855 fЀE޴EZXdj,7dg f`Z6!5e f`7_/h855 f ~ :!5f`7_/h85fE,l,_γA"EvMXͷ Y߳u8=Z-;E>EvM9B0=Cy{ּOY`zֲsXdה5)klp,5ֳ'Ȯ)k~n6v3dgg)k~ni=kY78'J \Sh 6az 곁k* ΐE^^P}ʚֲ'ȮUWi=|Bw6"_~`fϐE^րEvBSMYfXdה5)rЙE>Cy{|6p;Eֳ""l6wg ~BwΐE^SAO dO`]SA}6pM߲fq,k"!=+`6y''Ȯ)4]C gk"VțC 'Ȯ)4 f Yk ~y6YXd״֠zAXXdTP "w|ٯUAn~5|y󵂆, ,kȮUf Yo5XA7"op6ٵz țC ȧizha7"A7Y3d5"z n'|5ț4""B7|E^^P]dVA]XdTP] 'ȮUh]d*k3|,Zk"oZZ E>Ȯ)4)Y3dg)rBE>Cy{hpn,_6g"/ ~B ̡""¹"!=kS,_;E!|w]n'Ȯ)4"o'ȮUh"VY롅""VțC ĵ,k "! 5XzXXdה5 f YYn,_`!| ț4 E>EvM`fϐE^^P EkUй, ,k*(t`7_+h""VZemfϐE^VP`Wk XMY 'Ȯ)43|?%E^Ițg-W5M)iZkl'Ȯ)4xX׳"|3|,'f`Zt B5f`7_/h"РZh!|w]Wk XM롅'|5,.tE>Cy{hS,_[Oo_/h E>EvMY`fϐE^5 Eky6YXdl, g`O`]SAa6Y3df`Zt B5ț4dO`]ЀEv6g"/+(,'Ȯ)43|,A7ٯUAg dO`]ZX ""3|,o,_o, ,k*hW֠,߳XdVY L""o0g"/ЀE޴, ,kրEv :g"/g" ׵=k;E")kS,롅'Ȯil0g"//("*<, ,k*("o^p65"! EkUй, ,k*("oW"/55Ȯoв XdaBY+dgͻ0|6ش\"y7pMYg+dg)r`zֲ5e \Snp,l>e 6g-Ȯ)k> =k ܧuBCs} gMk Y Xdת"VY롅X Xd*k"oZ-)EvMYs5em E^րEvonZZ6\"Nkx|,l>ew6g-;EEvMY5em)By{}ʚֳ_OW LSh f YYn,_,B}5Uh3|,МEvBSMkk-d/`]'XdהEEvMl,Y Ȯ0,t`Zt B5ț4 EEvMn0W"//(t`Zt B5w\Shs7Y Xd*4`]f Yo}րE޴EEvMYXC wEEvMYC Ȯ)43|,XdV B55"o^а| `fE^^P"* B5ț4d/`]ЀEv6W"/+(,;E"_"`fE^5} "_!=487ٯZwB" țpX XdTP87Y+dvEkU d/`]SA{P`hfE^5)ٯU d/`]Z6l; NE| |BXC gkXd1yzhl,,k fEByjܱ 4=lȮ)4 E|l,,k*(3|,l,_γA"_"l,k Y Xd*4`], ,ji=kl,,k fEBy{`6ٯUAw"_""o^"`/t`7_- EEvMk fEByZXdVkm B5f`7_/h"_"РZh!|Z,kU߾ZNȮc,n`;Ew"/Ϛ"OY`zֲXdה5)rMYg;dg ~B0Cy[h" ,򦵂,mew6g-;E~EvMY5em)zBy{}ʚֳ=")k8ew6e-dVY3_,XXdyzhٹ,kt`7_- E~EvMYn0 P`=~?t`7_Z8cE~Byۏ1t`7_Z8tE~By{`6ٯUAnȮ0 d`]ЀEvBY'dXdZycȮ)k E|=욲`fE^^P)ٯZgȮS, g`t`7_-"?"`fE^֠j d`]SAZACٵ Xd*k3,Zk"oZZ8BsE~By{`XdV B1Ƹn),,k ț,תA"?"Nț4<75v"?!WրEv :O"/ ~BO"?"l,kk-d.k NțZ="oZ-)2Ƹ|53,NEk?,p,,k3,l,_ͳA"?"'f`7_{ Bٵ Xd*k3,Zk"oZZ8B`fE^5 EkUy6YXd״`6yXXdTP3O.| fE~ByZXdVkm B55 E|}Ȯ0Zh!Ze Xdת3,Ev_e XMY Ȯ)43,A7"* B"X vck60MNii"?"X׳ f Y"*<,,k*("oVАE~EvBٵ"?!"țֳv``]Sh f YYn,_ d`]Zn, Ȯ0, ;E"*SȮzA7LE~EvM`fE^^PxXdV) YxGȮUրE޴VАE~EvȮUf Yo5Xțg-|X4"oZxn,,kZk@, ojͳA"?"l, g``]SAa6Y'df`Zt B5f`7^7dϚk]Z- ^`]Sh"S7dg+4)>{Xͼ=4 ܧ ux7L]Sh 6/ȮS :v7dg)k~ni=kY7xEvMY5em<7xCy{ּOY`zֲsXdה5)kl,ʚ*E~Cy{hNg /ȮUAEvMYA"Oi gڸSAyw7LߐE^|6p֚mZ_k/Ȯ)k>;Eo"/Ϛ"OY`zֲXdה5?EvMYg7dgwܧ)򦵬, ,k5`]XXd*k"oZ-;7xEvMYXCv^`]Sh E|=p6"l0 } "!=4 Ek;/Ȯ)k f YYXdVYA""'f`7_{BCٵ Xd*k3,Zk"oZZ E~MS֠zha75e vf Y"`Zy6YXdTP)y/ȮS[롅;E""Р,t`Z, ,kZk E|!Z,kE~Cy[AEv_5`7g-""M롅)2Mzhl, ,k3,;E"z s88/Ȯ)k f YY"`Zem>7YXdNț?/ȮS4o"/+(WY7d" m>EYXdjB B5X/Ȯ)487ySd`_`]c;E3,vEk杢E~EvMSd`7_/hxn`q44em B5țe-d_`]ЀEv6o"/{ Ev_="oZZ8B`fߐE^5xXdVOC55"o^а `fߐE^^P"* B5Ƹ~rMSA d_`]Sh E|}/Ȯil0o"/[kWրEv :o"/ f._w`_`]im,kq=]d, "*|n~5=pn, E~MShNț,jͳA""l,k YXd*4`], ,ji=kl, ,k fE~Cy{`6ٯUA d_`]ZX ""l0o"//("*<, ,k*("o^pXXdTP f Y7LEkUЙ7YXdת櫬i!B̴"!"OYn0 =4" ~X`5e Ng YYSd`Zem>EYXdl,Oh8 "!0תl/Ȯ0d,-k ?ozhoPЬЦh6߳a BN돢wЬO])[_tv@Sh vu˯Ьl6صZST fSk=ԭ/Bw@Sh vu˯Ьl6صZtnP fZ -c?'МEBsyzha70Xٹh X]롅Y=tgW`dn,!Q9 Bf8=zhS>6xb5iA7vg믄n,2i(+k LS֠zn,r E6Ga70XYn`ֲ"V?"VYY8=ڏ*k"ZZ EC7p|<y;n}_kͿ ]䲇Y Y^аw SAcB7p| m\YdiA7Y䲇ce Snu+ LOЦsE6;МEBYO\uZk"ZȾ\ SnY=A7Y䲇ce vE6\";E)IAaY+46w8 W)2h 헯?a70Xk Shs7XtӴ֠8Z2Yd*4gA,rQ ': Z9k=kl,ri Nw9\n,2Ty(.r E#\p6"{68 >em\n,2587Y䲇;E "g cVpn`~׺OYsZBÝ"g믄",G"oМE޵E[)iUPgA6Ga7p{g cVSd}*(^p6pJ E6?,p\gSd#\p5M4em 2n|E.{88= YY=tg;E|,2h֜EBYp60gA,򮵂u{ LSh0,rce fgγA"׭`60Mk E}l,ri*(tE.{ ޣ9 >t\n,25#\8=z֠8_Z EC70MY`d?2_ E_fZk#\vcwͿ BwFl,hk| ]䲇YCYd,r i EMkm 2n]d "="{^P Eޯ wENitE.{ 1֏8 > cVpn`87pyf,ȠUh"VYYO\uZk"ZZ El`Bn0e1"OgE[A70Mk fgw_/h88\4"=NSA cV0)Ȼ4"¹"= 1VAa6p|*S"Vu 4cVpn`BYC UPgA,nEC7p|* 2nYd)kp]Z- 556,l>egMY˺Uw"oZ-;EXdTP \SAǝ?~ ܧli=kl'Ȯ)k \S>8ɚ&kʚwMY˺"k დy7XYn>6,XdUAE޴Vu{ \O);qNqG߫j\ERE5N(A7%в`5VtEBykAk^Y-dOY`jA`5+4=o`ݧ d7`]S5־aIGȮ~Rhue'EȮ)k E^|%EހEvm,k=,.XWv`7`]{Ϳj5p6y5A"o!< ;Vh "{ E>=I_S֪O d7`]ӏt`_} X Xdה5=,ׂSh%k!w/FAEvm쵞EByKրEvȋVv`7`]Sh0,_ȾVm Et`}BOBy5=0wW_kIyy"OYA"o]5=pRԳ[]a6} A"o!<1n,UAEހEvm<"6BY-dd Xdh Ⴣ9X XdtZ - EހEvMlг["O-(t=,Xd_Ǡ By5=po,{-EހEvMl`֟,,k E^|5kIȮ1٠gEȋVȮk=,׬-2ȾVZ[v`7`]SA,}yy5nȫf-<)y5e zy Y{ f`}Z?,,k*(t`_-hxR,EA7yn,,kk zy Y/{͸QP`]{gEZ#Phpo,+ Y Xd"jha7yٵ̧'E!욲XWv`7`]S֠,_"Z! XW v`7t`_ -"o"ݠgE׀Ev XE+Y Y XdFAȠǠgE5 E By5e*(w=|X7~gRA70MY By5"/"o]jhIȮiA"o!< u} zy Y鯡ȾVAoȮXdFh=,QP`<?,ppo,,k zy YY[d`} !t`_-h8 A"o!<0kU~6Y XdTP E^|l,,k*(tEBykAkUо,,k"6BY-dd XdheeEEvMA7h!t`_-h EހEvMnг["O} kUо?EހEvMY٠gE5 Eگu'E"o"Iom mY_ߗkgjhB{Z"!<5k~R>eͻլe`5eO\Snck")kNѢլe`"IkZN'Y?5eZ EZP} ͻՂf,k*`YY}ʚwE+Y YȚFh"/Z -"]dȋVCxXdה5Evm쵞ECy^3XA70M dw.k gZ{oE5}k>,Zkٽ,kʚ dw`]Sh~RBk0CykA}6p 'EV ;Ȯ)k_A7迋,w8;Ȯ)4 "wf ܧyjֲ[t`_ - EȮ)k zyYk dw`]SAjAXXdTP=,ׂlE*h{o,Y3gEt""6.kZN_Xd_ dw.ktZ?,,k NE^|u;Ȯ1ECy^".UA dw`]SAXW ] A"!<(h"!<54h!욲'E"/Zxo,,k=,ׂ½ȾV{!w]SA{EށEvMIȋ Xd"/Z8w]cA]EIȋd-dwȮh5XB٠gEf((ȮE"O f.Uhl;Ȯ)k0^ OEށEvM{ NzyY"`} ڟ,,k*(t`_-h EށEvMn[;Ȯ)4 zyY/{ Xd7Z"!<54 E (dw`]Ȯ)(.,k nE^|1YXdt`_ -""A"!<1n,^A""B7yՂXXdTPZ EށEvMA7Y=d5`7ByJByٵ׀Evm<=,׬"ZwEށEvMYn, oEȮi=,ׂ;E"Z?)  'E"/ZXXdTPxôgEZPxkU ӐEȮpRE^|! XdFzyY/}ch5k""ECykXd_'E!"/Zp6y5fECykAa6תl;Ȯ0jAXXdTP zyYwE*hNQ""6 ,k#ECyKրEv((ȋVZ""-2ȋ"w]SgEZPEjww`]SAXW SEȮNQ"!,ZZ `]S5e E5Shjֲ5eͻkZ{Rt,׬l>eͻլe'EK)4?)ZZ6"z7pMm_ 곁57XE>Evmd XdF~jha7ٵ5`"l!egEY `]S5e E57p'EVȮ)k~o֞!<5k E EOEn`]SA}6pMYkOE5`7fZ"!<54} 0]RАE>Evm| fE>Byk} A]#.`6j .,k*("/n`i |yX4}7aKȮ)4 E^|5ka70XXd)k}7Y|ٵZ"!<,򢕂,,k]WC O1{4'EIQ""`6yլ'E")4"/Z E>EvM,_P Ek}7YXdTP"/Zа| 'E=|,FA{Y鯡l,UhA""6Ȯ)Q,,k*(=|,ׂl,U d`]SAʏG""`6yX4ȋ|8)Y#d>0kA""6 j5`4.,k nE^|5p`]SनgESh zY鯡A7׎෯4 E>EvMY{E>BykXd_!|욞P"/Zа| ݠg8o."/E>'k"6,[dc"o<=|,'ԸQP`]ShlȮ)48)yՂ")48)yn,,kz ,_t`}Z B5ȋ4""B7Yon`>'E=|,,Fh"/Z)h""6ȮǠgE5xXd_nȮ)k E^|a755xôgEZPxXd_o,,k*(jAÓ"``]SA ӞE>BykA"`} ڿaȮ0JACٵȮ,_ ,^yj“"``]ShpRԳG"OS,UA[E>EvM{ f`_-h8| A"!<0kU~6YXdTP E^|l,,k*(=|,ׂ½ȾV BٵQP`],_,FAE^B5e )ywE>EvMY{E>BykAXd_",,k*("jAwE>EvMwzYwE*hNQ"";E"/Z"`.k*(E>Cyׂ?Y5k צSh,Z - N~RB`jhY78EvM"Tv68CykAOYEYfXdה5)kI_況5V"lvN~<꯼P?)rMY`jֲnpO|6pMy7XZ""6 ,k=|AAEcmqmd XEY ȧiʚ)k_~֞!<"oXE+ Yx5FAMSh0jha7"`6Y3d ܧР,_CXd_;E>Cykh E ""ȋ>l, ,kk z YXd_ . ,k*"n'ȮЀEvmֳg"O)({ XE+A""l,⫡'E""A7Y3dȾV{ ""Iȋ4 E>EvME>CykAa6תIQ""l, OE>EvME>CyKAEv XE+Y Y16 ,kc,_"Z֟, ,kjAÓ"cUP"OYA""A7yլX4ȋ"" ~k5XXd,_<Xd_Ǡ Bٵ׀EvmW 'Ȯ)4g]Sh <"o5`f- '?"`6Y3df ȾV |욲=|,׬-2ȾVY B5=p ,⫏A E>EvMnгg"O-(t`} wE>Evm|#k"/Z)h]XdtFz Y/},_C{`}ֿS~5nE^|u| ݠgϐEZP"Z!| XW v`O`]SA,_ Xd_ dO?%Evmd XE+ YXd^ٵE>CyKA;Vhpo`BOB5'E"/ZXXd״ ӞE>CykAXd_!| 'E"/ZXXdTP8)Y3dnE*h'Ȯp ,+ YXdFh"6ֳg"O)({ XEY )5A"!<5k0kU~6YXd״`6yՂ'Ȯ0,_ ȾVgE>EvMXW ""lгg"O-("Z BٵQP`],_,FAE^B5e f`_ -75e z Y{`}Zo'Ȯp , oE>EvMwz YwE*hNQ"";E"/Z"`O`]SAᝢEByׂ?Y5kW"O Yd)4 Ȯ)k \Sړ_況5V]5/hjhlp 5=)BykA}6p`jֲ Xdה5 \Snp,׬I5 f-Ȯ)k~R䚲W"Oɚq#4`],_Cn`((ȋV ?"_"hjhlpO<~rEvMA7n,eW"Ok~o>eOf- .`]S5e=)Byk|6pV]"l\!<5k~R>egE+Y Yx,k#4`}ٵQP`]Y+.>e A"_!<54 ;Vh z Y鯡l,UAn|55 ȋ>a7"l[Ȯ)4=|,׽ȾV{!|)~6Y XdFh"6BY+dXd, dǗ_ 3)4Z8<>ZUA"Ty|o`"Ͽ; f.Y OEA7yY[C '"/kkha70xS4NL^OByw n,Uh}7aǠ+k0 zyW'xȮЀE^By瘼"OA"G5Xd^Y_ySh}7sBw7Ȯ)k \SZYY}ʚwEY n`]S|6pMYkCY")k>,ZZ n>м,Z -;)EvM5w"O-OY`jֲnp욲'E)klp,쾑5zY鯡A7*Y鯡9o>eO7Ȯf>ݠg8yߙTh LSAnȷ'4況k 7X$k5k~o>֞!<1}k~oh1Nn`]SA}6pMYk E5?)r, ,k#kh5 Xd"6 ڳw"O)(S֠,_CXd_w"O Oܧ-ս"XWC g`o`]S`6YoFe>ݠgE`61A""ȋ쵐EEvm,k#k=|,쾱׀E^X4e NE^|55o do`]Sh0jn, ,kkpR[XXdtw]O("Z(do`]^jAÓ". ,k*(=|,ȋV7Ȯw]{gE58)* do.kt. EȮilгw"O-(kU(do`]SAjA{`o`]SAa6Y;df`} !|O'E=|,ȾVOh B5= E^|uXXdTP=|,ׂB7ת}7YXdFAEvmW 7Ȯ)4"/Zxo`<Ȯ,nEMSjha75e A"!< 5XY4=IQ""XW | 'E=|,ׂIȾVOB5nE^|!| XdFzY/}ch5ka75'E=|,׬;E"Z?) YXd״XW "| A"!<0kU~6YXdTP E^|l, ,k*(=|,ׂl,UA do`]ٵZ"!<%k"oXE+{-do`]SXWC g`o`]Sր7Y;d EkA""l, EEvMwzYwE*hNQ"";E"/Z"`o`]SAgE=ct"gY{Bykh~F"/Z - `]Sּ=!<5k>OYnh5kI,kʚ)km7xByk")k>,ZZ `]S5e ߶L͚w)k~Rh5klO"VC˺,k*_ ,FրE^E~15md XdFz YY7LݧIѢլe,kʚ'EO"O͚S`jֲXdה5 \S{'dfOܧlh5kٽcz(Ȯ)4=,'ȾV{!"/“"``]SAa6Y'dXdЀE^E~EvmXd^Y'df NE 7YXdה5"/Zp6558)Y'd E*hRȮpo, OE~EvME~BykAa6תlȮ0JAC"6B"6ֳO"O)q NLShIQ"?"XW Рw_ -"?"6~<ȋVC g`Ӕ58)y“"``]S`6Y'd>0k LT"?"-r"?!<5kpo,U[E~Evm<"6BY'dd Xdh YXdה5"/Z E~EvMYw~k5B٠gE`61gE~1'E]dה5=,׬A7*k}7an,,ktE~Byk֠k,,kw XdFh=,c`6yߵȮ)4"/Z E~Evm<"/Z -"?"Р,_t`},,kkpR, OE~EvME~BykAXd_'E! 'E"/RАE~Evm,k#k=,쾱׀E^XXd",_ȾVoC55EyՂ'E"?"-r"?!<)ȾVyE~EvMtZ-h)Ȯ)4 z YXd_A"?"6 ,k#E~ByKրEv((ȋVZ"?"l,⫡Ȯ)kpRԳO"O-(a ,^0 YXdTP7yՂ"?"IQ"?!OYnh5kI ,kʚ)km7xCyk}ʚVuXdה5?)rMYkg7dfͻ5 f- ^`]Sּ'E ߯4fg)k d-d_\Y3 XEe ,k \Y7dcXW/Ȯ)45e=)zCyk|6p,ZZvR욲況kZ{o,׬I5 f-7xEvMY"הv6xX'X7~gRA70M d_ 4o4d_`]YٵE~Xࠠ"ϿQ ,Z_P`7 ,ՂXXdw_Xd_A""ȋ4 E~EvMnгo"O-("Z!c<~rMSAn/Ȯ)4EvMo"O)q#4`],_C"c,'/Ȯ)4E^Z E~MSAa6y“"`_jhl, ,k*(,_ ;Vh z Y鯡A7ǣ!t`_kٽ ,k ȋȯģk"/Z -<)5md Xd^Y7d<+48)2M!Bn, /ȮiIQ"!A]7.Xd_;~"w'E]d_/Ȯ\`]Sh}7a9XXd״`6Y7df`}! ȋcSE~Evmd XdFh=,,c,򢕂, ,k"/ZxR, ,k,k"Ͽj“".kB{.⫡""B7Y7d> EA""B7yՂ LShpRdwE~Evm,k#E~Cy^}1yJAC5e ȋv`_`]S֠jhI/Ȯ)48)Y7d58)1gE~EvM{ NE^|I/ȮpRԳo"O-(kU9d_`]SAJACٵȮ,_ ,^yjBXXd,_'E"Z4d_`]^XW ""-r"!

|#(Js|+>mC 0>3*e wT=&G'yڢ'tdR4-t*ݽFAԁSS&tqM ی-PЌ$gIT1IQ ϓ(sD陗&#BM.BMs6PN ֑C52WB+a2%J7mԃ?iQw][If4OC7#0񷱿qiNxOi)ۇs|h^AEPqP{VQ˨YY^qi*+_w7oRPwRM(9\ou 02^ۻ?oIFdP:HJ^R 6!þeMo8禗n8\<8$\r 5n\lxs֤7>Q6mxq9/xת&: u/N JirYGzPN~_evNO![~hҏ%?[ Q3壾,fp)-b#:Aknu:h+G֤}VhMzP>8 Yr(i(܂4e/(ѨkoSQIQ73 BZiq(Ԍ}K62u3W|'|*v]fp8Tڣ,F(H)JOHLQ<[7uWR{E(\z=Y7AJB(5Ε>I/V4yvʉk:Rt9 kso϶.-Jpe@q{gDy'-J d@e^jgK)-iɴQx2dgE΁ܚX8)SBrer Ա'S26Rk.^k/4BuTߺo}`?NԪoׂOCV .o_*PkP JOýӷ (B4lp Ζ~JqbҢc WDk\]#<!΋xhtmJ}آ5TdzҤ :y]5=5VEҒ*E /Z) HvT-IoGCWoIzM%ş0,ZH+ӄ`yP'JOs VbN,!Ri|8&,L"R(5N"*_nDQBR)8Y&DT3,2Ua{Pot:KcWԄI5ELM3T_GL _'":Y5̃ =(+-@O'xq;JB4dg!'<!ʍczk2[D! W=.8caJ=&c^wo50 ;߯Z^G!u[R'I?&fֶcK6q -Ƒچ?Ӫ!<k/x&@UmIʠ7 7y_X Α~ J%}|zc[WT-W}|6`яLH~* YZ/\9jvQkn{qEUSRi4TRP Xb4]װѸeیΑP *h^>{cFYa~Qs}~q*=xJ( FOx)B,JW|bh5z^B?>s2hޒ§x}E;R|WuGVMpL{ nt".Ndxo Ɉr4-GO{:Ez Egʝ▣h_B4dK!besw9q4 屬-Gxfgx?.+ŕ*<U$S]4T:SzK?2Dm\9hYz9j\SgJOHS6 W:ߙHn]DvXd,TVȶ"Ul#ʷS*x()Ze[PP2O~HTl 2#Y`Γvb+,C|q~ӣB1 'w)KKHc= gZiKCg1-#("*#O]X2D֥"3.W,6CQFtEtDg\l:)|N|-tRNA;k: _hxT'azP0|eVq Q3 2=c -ݹ1hVUZpW1E歇Hl~}|>U7lReR{_/\C/ZlH)* Aq/M=&.zQqWQ 37ZΔT}EhV/2&VE/M7|_R;5q9CModMo2Qym0067gtQ7ؠ蛥7YzaC [Ejzײ kYEO-M?C)CW:BFұ]؆tQܞR6 M?Ov!W^թӘXX7,NW S2D-PS=SN~ʄ,HOO8ZGb4#Wxq,=>[ZSb* \w\1Qx-M{1Qr F#ܳX@7=Y?O;SPԬ@QRuf,akc ,ڕJiH&y\ZC]>5^&ZPDG{ _tOʱA=O4}8)=4}ȇ6T&O>BԻTinZ(}zDf5(2<\3=RAJE) =۳HkLϻw/HԨkiEzrʩH_.=lhDMyxlk<ƞOV׃}cb-Z~,Z dZIX(;^:0U^] 7lזS a9u-Z:Oㄩ:r\N=;Ҋc_ FUӊkAǬ_ZW2uE-T 'W_nׂQ:nxO'M }Oފ/kyzMp^'M_5Qɰ,Z;}<WL?&.Z }k\QFʰyzN?/:M?iS7lwJճ~Ӧn-OӀxM?3pWCc ׸r~5Q4!K ׸ZFD q:^fjyzY" +ukUØ<=\ H!b/y OZE'KM "3aWSS<=ib)$ѥ;ky?KHz/Wn@v|<ƱV OРe#*)C:=5?q$45}~dw8[p3 S-=6,8OYӗ235(UV`/IأAZOs'fwS&4h1ʽVngT!_n>{TMo/zY|vlm(fzCF.V,0w(Ɠe}@wKhqz,EnȢ"7.=廰EI~ReW6&^#;ڸri[p-Z}%zo`&S&Qr@ `qYtwNEiݞR=u?vQ珋Ҹ[>-O+u֧Bw!LXש" LQʙc4oQ_5 O ׌ D)z%< 9QJ ኢ|WYσ@=2,<S?_x In+ӏ%.ugOQ~, a4[>)sP *b4s1b,ԡ)ۻJިBS@as_z Qn/O3dR%Pyaysp#):}~UgNOEM^Kӏ{-M4=|X A>k-Vlj"dN?+MG]N ,rZʩ4.]:e/2J0iM05?K[_Z-> i;)M/eon/T__,-M_^"bc(ZRkPSnI]__28㼧kizVZRT+NJ6\hbG!ecO-(d5^M*B6Ԯj!˿;y)sR§ 9\JuBNl[ʤ}}AĆ0 |o}+!`\ѹOHjf}Y{[!SmzIm1P7tAQ='+<[Nw|S%bzvXЈ%,&O'z_ JTH"u|l, 2ɗ{Dd (#(OKjƒc.&(2֦Bya[ &8iM_c8tgNOZg3le=(-iQ7!ʝ>{a݄(u/FP"ZcSkOܙcҭ#D9W(mmLWϸٌ9}mT{ډc8=@ i*.H9I!HL'hN*H9 H*z Je Ӗԁ֦Z@^Fڻ87aڳ0=2=5r0}}X TYU0ͬ>;CԱ0TߧԵpMpyA9uk`TN2 %}|P}kAphT߿xTIU=i`z}_VhQR4Ԡ:Z=Mv_ڡR4P6VUMKϫb9fS*&ʴ5k.^9GoQz_EXZ~J -J?:ezʠS:e2=O-JؐE,\(&6M,GV/w U +3=x YʜFX*}e >pѲd^ʺVJbڬHc =-׺8#{쑝׺ du+v҉dSΙBl?][8rݺ⬳so,b?pu} ׸^D ^EM^F֥oz-k%E5Jk%VR,ZdzC|{Хkz%Ѻ LoH?]饰ҺV 7RM/h]z֥y6je)}?ozֺŇ3}K_w\0wi]p<ǂu`)ӏ%=q_-LOvXlVqXg/nr#Nea0 \n>7x+ &6LORU#LHi^hnޥBkgDZ(0&ɠrç/^PY ЬjP N)BS f-M'6}\7~S|OG-EآƩY,= xӓJk:{+PʧQ8}ˆ'#F9WtXmTʥI{;oz>@JЁݾ=Tzb({?eH蓽_yӏiۭXmA>{G1/nw<=_ħyG= naC_:kz{D)3:iz 4i-LlַMOGyK *X-N?ntX:ezf c]ca݄)׫}n(q?+NӜÔS?S­EY.Y8-Ӹ=TNp>b (׀= [T_ caj,L3OCݪ_,T_AzX<:Xiu-祈ePkY=LjJxL[WUälT銏ԙ+l1.~L x 8[\aOSdJj,(͊Hg'yzFtr+:٣}XXk ΐEg$Oʹ3&@CB󪖧/ ،1z/5>-OO 1JAӦgaVQb4rjhHU$ vDݸ:UVTHОFhޯ(k^>=$gq^Cjh^B鵆ZD!p|**2 G7(MF =+ ߳Ґj=+yV^K)žoz*{VXmz],ezC}Ko/zhDF\mzpJ+M/No_blzC6q=m7e!+ozvӗOnu҉ZR?Mo(T8/SZ~D~,i-rZ~ʜ.z?Y}A@kuz,UN{Dl.`9 `n\5Z^XohVʍ!`V&ƱVpP=yT5fazʛ>7a*0)iuyO\_e?ab,i@O>) SŶ:=eƔƔuMq1}Tna%U[-NOQam4=_gy&@]ꕞٝ9=jIEV`MΜ^+-NOLd Pn߇zOީ-B#Zr4kiB$N(hQ/F>{*ԸBZ2E'qTiVNrIٹSOHt7QEpQySzЦ'Z "JauϪo{gE{V| ЦM~Ա'rXbJ8s|S9uK9u,NmzːF!%~T`PU_=5@-^b?wouuG/]-(SkYR[_6ׄ*qcӷio)k4&Värw3khVX)WCWo7u{g{u!NOSYʞRk"1}SS>05F=OӸZ+eΠ>>{,l;?^a{~S*┃`O"1C*- }Paq<<8POY-S_, ìPj+saZ L=&B=As0|Ok'J9! F׼N[U[ӽi2-ڢPPJwO;z%U|cR19G= ho?]6*uR~C)k-Hv:ҦJ(_fT>;|e(wy:zz7"߷H= f-"|ee1Y\'P{-R- q{E'E*N>& m\-_mBE8CHi!e{M*OsQR࿸ hz?\_dok=?W_H(H& Q *P"LpoK}~V%PPym:{Kš5+.Z|B&PU"JfCOF kME-rU5kV)T`vC6 UR ʦ=12E/2^0R{L/+z0W2X ˕P[spc[UR5tlbY1WƴR^|PԔ/3(RJ> /-\RX"YdE`2vP *.YicE% 鳰lJT0z ʐE!QI!泰 [\rVhE &l䟋T2 , -̓R.@}1h/3#8WMςSSPEg$O]J~)ubuɋЖhxGEƍ /Dj#~UwP}("Dy6!zH^FN)ۦToRTPJ}MLY퇣ou;SWPw9LJRL-i<3kbJ}RjbcyC=瞘FN'ZSQCOLCsvp?T(Z&j#QLodޗUTo5(}3R͚^\zdj^nQ[*ezge橬g^jL? ^*/zkYEir,ZLQF)[KFB孇M=SJXw)cᚺ0鵔P&V6Yc B_rܮ :{ᶲoj*6 W %To! ǖJxBW2bA޺Q@,,^@\%SoRRq ( Yע]=ulQJMt)~_:Z8;rt%;hQJKV![r/z ŕ”]0?Niİxk O(R*}JHUfFO;R! Pn4ePIұb2 P [AbLBQxF^qIuC$[uʤ?5%RO!K{Ei}-J91 DMzkXGv{ic= LY)j”3R,MMz;ЈJOT)ݧz- }P?YäRsƒԲkӸӀc9up^QNA޺qظ> T-XT굠?T_=R%U}|P艹:Xx&quGoAu-祈eW1)xZ@4޿nVjRC96 uR.hJQ5R)*Ty#˅B}uo W@~rW^ĎV2mRo)&7.6||7^R55lzMJ GU.dz*v"7\W6<$Sϛ(]Vҳ T?lh)-YZ,,`Yc VPß*?Q2)R?N+zªėוS"/:~ԏ%de2Xay=C*-TO%]&&it")]&$."O"|=/&c2f*1a{ːfV [යzy*f8"[tTnaaQ<=Xt-C>xZOQ=1 kbC>jHY;}^RM\a U VSn|W>|w&.OL9gihۯ5zG]Hm51e5[mWn8JOJ z~OL[,UꝳJj9M7R=7 ]lzC7!EH7J޿Lo!nzr/hzb*x'tׅjtʧ2tj! oZ^41kx[wʽbP-R?-R?H=-nG~O"LZ,jZ>ueDl-`C 6qym#y]2Eiz+:{[e"/Ί8\D!z{7k JC}`i85qU\cjO! 9u5F@èŊR u֙ ( UBE<@D=~^?, k")\81ⓩG?ELog]E=[>Jh(D0~7hFp (6\hIT%\޴F=#0jc\5hix{Z^aE=8[E:{kGUQ J(sEiE-T*zλlS &Ҹ;mzboPӾ/}N6LA~YkG -QODzk@JԿnm, Z?Y 6Yr꼇sr F9Y8/NFrmTlW ɪEV2xZuK:~ScɛU7|Ou}fׂSRNQdE[[ |S'Ro) &NVYCgiWfjZCQ6Kh^)Cv"׍,;gKԷF1!Î-Q .:[Bzmv oNOfjzL-QoO80AZN^D^-QgA&}u&Sr'Kb|$n~>Zz$f|K+]&XDU8̠ LY]AJ\DK$@VpRpo+-^k.YFX**CtHe %_AILB%D$k% $-'z:A `KeBSq<ݘ5pűsPhz,N+cf)8O S&2E':V+uuKo1Ttg лEhXh.FO w/+lib4#FnfQ3;h+NΣ>VB}N2Pol^R|Q+[r(ΣR׼4.ιG+[C@5/Fk^zP]!({^xa{^9vq!;F׼lԉjJ JP789jyz ,z ,Z'O :t OV)CT|Fv|cXAʃ:zA5hoT lawu SCJY{D)c=6UpQz4mo=vݪc/! |(x ARN-[\Yu:/%xrٯԱ]U. -i\4 ,ԭZ:~Z:o/kA/e_TG_Tǿ:> ~TߧLuKݪ㯯) WS\lJ#K[R}~8iuzMuV"(VSh5=7-[w1[ ~e|c7*>=(4X=.av,u.lTMoVmuh'P_:'rmuz@OsI~,4H6|ɥE-[6e3","`)\ WIRT*KS\RA~Ih"ՍuKgqyҧX튩hϦ*$XGHX@R6vd~. t컖qا|(ا|OgaI`R#<=5f! gX 'EN8\pFL'Gxh+>{3)ښPZ!X1Q>Mw1gzѓ<=c#FC\ <=5Z d'P=XtZAkhGiyzJ)ZR|kbJܣ{8>1myz?;RdTA>nzĶ<=58SƇNb& .OL)z}}ML'zbJZVri\i<УmzckE/v;1^| umz.q S0.K1 "ozO|[ʓ H 7NnzɷBحP/hVDV)1mǂ7h˛[~==0-Q?8d[؆۝A<-:>qhe ,eh߁x+{O)B5ΗN:yVqZ( V!6TmAg ғB=ukH+RH z Ԁ +i$jQbZ(:COOx(O+DQV>hXJU5i@g|O(%Ԯo?_3WYos5Z:fS/eʩce{}Gt? rL9u,-AuTA[u=jx :i S,Tߧ/DkA搜uCN^kS1_R|wU|U J.7\1`WX@Ќ}X>}i.SW9yw;}yf7yK6L/t[^R4|WO{M/m[r kizHE/iUܖ苨 N $rq˦{^"!YgdˇWVjfPa|(z,FP'uHIQlЌ2Ը%$ޟc_2FFpؗ|?ŕꂕ^45PM @ WFa/ E=R7 ӈ Ё)Z|OZ#ژ i(ޢ?K)ԻF=ih(`hZ{_%Dkg/~ؙR!}Ԩ'F}x@%@^Pexg$fff"@9 ~VΫV,(-"HygK x ]rY* l L=i(AډRU ڷ|m* RVg(Z~~KhܰDw|` h<,F"DCqDO HUGQ?e\FL'z_O3J!hg:LUcA:bz¶$"1E=i< R_uyZxj rj͍WYYxS:ů@4ը+jԵr:57mPZ5uO kAkon[ Mj˚kAn577xZEꛩ(Ku@C7Ye~,(zs+W}GZކbM,wE jz3dڸjE-@e/n1/HLɥ/~ _bH=򧽾[ԛ -Q[C`"v Nұ=l:1ԹE؍jyHƽE=5zll@0psY&A X٥F55ɇ0eQ?Y;> P #&i+m$),Hb$$#\LF䳠 YYXFhDr hOʉԲ p27IASxDg sa-B4d1#F#Ԁ; 1mF)Q#Fu(k FC/\ugqcA/tpP RL(kb'zz"hzL!zvRVʔ}T$5I±]j<.^D='hizJ \.e8O^{]5x]ʸui6{]%~^6lrkKBK[%4ujV֋]'}^6XZ ׮ N%F^ez#[Ƶ႗A-P²ߠ^'-ry]'絭O?cA=iEȆ,b{ '7{j>2\}3!E-V9u鱐0Z~҄+R!ԗ44wi\3.JOoPO9M)GFص(KwE˳mJ'7J}0;p =4T2ߠ~ Y /Z(ny-Ob+%K1KXkԓ N?<I4[P_*i yMp;a>׫O)c @G *n6/UYiRO2;(Zk_Vu57 F~ MA A K j]ۀbg5Qi8*\SQ4kT#~1Q]/Z5^c6qBGPO5:q#f`GPOܱ~4[eۿc8KɥK.W< /O?襂iz#E/z. /`8&J/za?@e-7nqzQ5 ]&i.lUC^Lbbs X&`e3 Ճ44% ”tR B)꧌4( N& Ҭ;3|zr(d|6TEg(絭LOǡK?PE)| gܯ->"@m_*D#79͓2=#:e[Q*rQ1-*FKhS1(QZ{R9he<Z/η } ߏ1LonTpBkezR:zzL!?{js;=Hei wR==58h{T}$Oӓ!Z޲!u{Yzzz[>رFY#8E{Uë^4(]^Oo7. K/4Bޅ'[Ovԓ=' 1i<ݣZ:}+:}E6*͢3m;FhOn*~zʡ*>O=Z_ԡӗ^'}zh5,O(gV'7jfިC' CQZ(juIZX71%ZUp1J:tzB_U>{ÇPF] daU^TNs?[R|O5Z{5u53C5גZ[ R- _ Zjo|O Ki 5O?6mLC#?xݡkiRyաӛ(E<^.`Ml tNO*Ft6huVTf64mC"4nnCZ7cnry^{E/ELwEr4iz#۰M/[8uc)tz %L.[V l) ZJB*=-WւN?y P1mlHQBj(< NB3VIMY`^^d!Krd%ex;Sd^Q< GIOP?@=i3"ȌM&eN~'K<=,8#tR"!ΰk>\ cϨň/m4MO?Q榙>$e3ٌ"F<)tzcv%ZMYFw KACWN [·1qv1ߞrʋYɏ$wւ°qj-{'FOOY\>&vkaJ=bGzaz cDU󵭢jh:vz2Y)k[jk[ErP/,Ԇ7~m؆^ @5d˛PM/ƾ^@e~׋^ޮ__UfZ2 BYٴ@ڵ;Eo-xS%֧ rz-n==i0 Z~,'}zҠ+/u|)o/wK5[5q`eg=OS Ujӫh{r,)NHo0fҸ[mJ)ҐE)zz痚҃:e'ŰgRLQuk]ӇzAE7=(k`@Y@DY}K#؇yzҠ[^mF<=r[@n>OX5Uy8ӧ P9(SxXe^K~xgnv)MϬ̄AjPg}vhCgxVY1AdkBxg"tPRZϤd-y o>~,N4k63*':k i3s~OJƦIUhb|OP}9U$LEX}2*k &DH=bWf$&#&e1؉~rzifDTL:~r [loO4=v,:CuMO)ϐ>hgPJrQk2 9[򊎛QI"F{h\-Fw1u3u@UfcF1>91fuZшcH*F> re9:狽h࢔j6=~х6=J/ _%Rm~dwiDЋ mߋ< x=3kQk)^}ւb/JcOGN_ ʭF{*ʯUp̭KokU\Qt͐ex#ux/SvT4xFx~кFfQvRQr4!Z1C-ˈǎ~,v.p/0;nɍKɇ nɍ#jOp~3Te@A+O~"6,ԛ6׸$\2ɍb5.`rH S{┧1߅i\-JOP { /.a'&kE#JyׯNOk])'/ӫӓ# w1!ѰQF v*f%}kKXοΧR~r_K' >b/n^O;rƋW|xo~?nSNϨۇ23O2bγ~C a|Y㦧-iyz,8}@Tt?~tz!ȃ3Ѣ3,'NN-Og\ .ʻAZrtӏ%=ӳ<Ӄ7=EN?iy=gD61GUL#J9<=# <} W9={rCtn$驛9.ll#0/pȅ)yZg*cۊTa2M[QP4,iU鍲Lo覗GO,KF,v)Ҋ\,^ f)Xx?+ 4U?D*SSVWbhP~dWд<+tc$0)Kd%() Å(tTY !WFАk2AbLO "ehfl Ff>!?Q K%LhLazPIzROazrti|VNF e"~`:`tm[ګѸ8ޔ}j4~j4bF#۫*FF9.qV^jg|.0]%LߪQy4꭯]?lFC~kfٍXjBb^g0-zGk/1fhz#|u64ɦpzո+67U{xna S7A/z)S`.=Eūbbdғ&. KJ˰4ziXY NMlDظV]FXޭQMoGL!dz1.. P\|.r( տ_ԐA|O k*N#h[$*NzLO99\+N#`{,#N#Y%,{,]!;h %Lo.MazˡʵbM uLpu JC rBt6}KZnbUM jWNL2=uLyٍ2Ϧ(?P5+Z)k} r5|N8&(iz-*gE->2iɫ7*pz`agSRx+6咼iD fzuLo9T(O~(֓7)5{ij΂!LowR=Lj %VLo,BJ+pz-R‡N-#H_]B*ӿ2LgBJ,q]kE5M5~ϵը ?3ԠZ5k?5'WRN RV`<_ _ FW m7B  Z^%}` k E-rkM[wzo*nZ4{=9}5؈7geِ -_oeֆ/lNke4]7-gLzkz&nezG VC1^z?L//i J"j"'k.tB l) Z,R \`C駹” e у0=]sbФ,0K?*hzAI]IqХ;hyyԥ' gJ1I8]# 7? G]14fNrt>v%1d-({.".# W`h2 Mb3tOK%֥KnKb Β[9MF hrѢb4,b4({NF Qjrnb4?1?[>֥Kȝ1voQy.=ޅrV3=%UX mAE{-w{-1^h< -P~^h\ZR-E1ܗuzf{)zxdzSe](^,KQF#T_ԲF S_=2XԞ_KQҪ?lxzju^|Pxo58E5=ek_d]dzyB=2}Jo8,R7qٳ le'[&JfKse˭L c ڋBKVgZ-p?/[>)܆7C/ US3\FexHLOnx*ܺ 3+`z{J`e]]5n])K}XxX$( }UFQ5}^~1 }KQW{^п* Ƚg9b4 Z0V2JITb/Ti]֭K?xΔ!h/W XrCrfn]zr`AN@^?,W?Za1. uKC ЈsׅI<3|J⓽V!ӗm]zf?7t3aQ)!>BwgR OT;xY7XUJP-K C"$S*g>2scDEbWUbM%KbAJu' ^ Zu K?:֥BW֥/uDO۔O[D\WZEKЛG~ F F4<ڐ%.u%ߚVn\\V:5uUZ#-rjR5=5G_R[\ KA4njo9.0XBoՎ1+)-Ko5)P]2au͐q k5gظG mZGc斛zkn]车xr.-O^R7?lz9[^1ZaGE/Ѻ-K{KqrSzo8.sy- YYD,[M ʬ*gRve#PVW|E' b Ȥ.+.iTY$S؟RxK//}I##5#UqHphK,}Г,g1uIrr'Yz~6͌bܮvZg(uK1~ZZe' J?j*=xк{-^ ר*+/X5a w)-y^^oKorD_^P /^EhoEhDCO-{Ji~-BQ{S-TJOYq#q# U(E-oB9|sR6(KF(ɍ׮S}ؕ^mzjza,O>(e:@5e5,h-ov ޛ*'EzXpH ?ktpeR+T?EHO xBZ-POSND=F$WhѺ{j}R{^W߅i\/.Llc~-*LC)Ux(ZF\euAᑷ v(njRs?HIlEhӑSNJ+:-H?ʱTdEXS u/zj"UO @L;Qf&<9_٬N r<YM-@wʹn*pےSOTR}(3\?ZX<ճOa+ʚH?Q>b؇nfҮO(=O k}4>:3J( !z#{Z ڭP*-B4^u)On(=i0[ _jF #HQƒlY"Diq9\ : WrQ/F ըcᦶp[(Kk%[kAn5;zQZe彃0h"_MEA"-GGj3)ݨ [2/#kOFle-Z^)ܸE.U mZ!=/Aƽv*qˀ~Jn5b񢀕^yEFmYnBkУϥ>z6مlDx8ZFTU-Z)5u|)`z4#ǂAz.B[}fPWlJ@ ٠S '=Ҹ\iQ#QE,FIbS7z)EO1?W;Bz0$T3z )5ֿãˢ{R'K1H!:1p~ lEoR~2cXaش3(j-zP/8RwVT~"ãT'&k 6Ovhzl:}k4-YQ6a?宦>'8K'͟q;7VZr#S[ }gC |fxܸ)y{豠-EPp-♛`}gіn얢ψݓzcXRU)_&6_[:ըc9HcMsC{Wn9܆x5᮰u~QnlU[PNMޫqAЗ׸_ ڠmP-w5/5V_ZӤ=_ Z->,57"ĿCZwvom )`TMj2tSFjhXG!Q>`L1q)R֐򰉍Pf#˓}M--Of,JԂpFMhr97vhv14(nxWi%zۉ ፋ /eVԔ_ôjIҶe-A~r;Hя4YD,JXeT_S ) BePwN-iP%e]AIZH2˛B6,8R4%]F^ hlD#E4`&6 c< - :2iе$XLF{ǵPtKgȌ똅fbW+ Vy~D4*e9 ?Cn шU D?g}h4>5!-DPDBQL=i(xbFY1U O9ܭ= ޞ=bE"BNL4?6]:8΢utusp|WN]kjԭڻK Mrja6xm]Ӏ[7pZ=5גB[ׂ45Rr-Do吨͏1k_j1ї57}񿕧*7}oeW.K[T^~4\|4K Y= fZehnyZJWkUKSMZj_1:&zR񈪕evL֐rJ#/ _R96V - eD.▭ j)Yf%b)vDzVyXJV(e 1JA| :)]M*"~***"i0GZKYhĈ$)`엣ԡA~ lGɮO Kן)$z>P6!Rl~ [l4{-*o,Fc/FCnˌrh\.Fb4s͐(|0%(kWtOP ௜g+FOWtGp_}Q^Bdہy'Qd(k%zJ5=HZS\v;2zRq$ea蛺T_;(gBQJ׎޲@_kkbQ_W#+ѸW3~h$VlZߎޖi-@k.֢7uo.;2z3d*˰ԯVE/34WOkvF[ݐnF7 K5P}Vܸ4ZrVFel;.z*B0*h){JXHS•\kx_ѽ33-F?@:&z:pM򯩾ɍ/@|qU~5z:Z:)NC ӈb1g nicL ;,pZ¶W,LOEiܝ-H+hP%FFv1QbccK\e[oRͪy @R)E_JyMH㹟{8? (2T|=1v^ͼBԕE'uо{&'>!>]Q q,hw:,4A!k5z*܄gToai[hČ6T_?7tOSE(MâBwq?M(˦AV/R' wFih>~lFeo}X= Fmhm,q,q f 7&Nx5@VN I5z987̦57 zX w57 r)X~E6@konh>)c#573S~T~ԸfCn-Fo"*57%c}A-Fo;5/bb5j(XVymA7e\ KGEZ,GWꨮ d5>s9 ߄$:$ܜ;KR|*:{JQC+1zKA?خ _)-z*d ʳmx-Wbt%anH/J6^1Qq~K3'1T5cFK-dj9YL,2|zʎ `cPŤ)+͌}Q$VH2&5II'UjȆQd$G}b 󈎗}JP54Gk]Ea62yA|:<}\ĠcY9&rYs2I!Ȝ $G(#9TS5o'tw;jEM't~6ԇtc)67Wu/ ]v%%-U]reM{-SAm*]ٕ5>k q?!l5/ոo JpYU~ʠj4`Y~`׸V~j_q?e[)HnWPfп4%1zڢYLFTHk 3+ +f.Ο2o^&n`?#[YN塖I:{ܠp!wAwIa%F8|KM1C/w/mIKIFgt_:c]^l%F]*&GuZ6bfRX+2,aVi0|s6L);̌De ([7> '@"D7,;5(JJFE>U`adf%"W0D1\=sΌ~TR{n0~2a0pQi4Gf)ܙ/K\F[Zb*j,Brsl2h$^Vɏ: oϔ+} /wZnjfuH}6/2yP|\2Y)'?,c> (`G9moMo<~D.C:*Sh[;RA2*S[8*]B^ do 'q)j܏N`q~q?mO56ULo>Oi4 H\2Mrꋸ1㵻Ci(!%DfE2({eLM)Ǿ({֨Jrz~94^g:[xF);QZ sX#`:)mHZQ@b:m:)0I\K1=nCGr[0(-T(B|SI˯L}l :]}ZQBs[P*ejqR/ѥ>:HQcѥNDFMY'eHuk}j ]dROmSr,wo])mJlD9HY2xѥzQ)F~RcF,W?L᫂:xQ~,`ږO{pe:Nngv8[Om`~CjA o ĩ?IkOmC5ǂ'w,5L56Dc X^ M oIS aPFb'1kO)5/ARF5Ek h|s#[nXJh5d|f9SKY ġv\[bʽw .'It1!:]{)SB-O[ S0.7Rqȗ| x~4|LM,ļ_ӊ1Ѭ]J)0K3M( h:Yl'I\R4,a&)LH<'Eh>4K #$"'HYf8(Nw"5l,Yd 縱,3W"f%"̒p3G`)qE2;*H-,R4Z:IY~7Dq,&|6 f&IY~rKNQ|EFnQ׷c`dž֥- |HGX%-O+-i w]/pDFI]/Y`[")]!_<&¤YaRT]I:+cgĶҔ&ηfJeɍ̞NifvX_ti 7Y^.7RxLt sWtoݕ5tArWvj(ݷS/|*yyk}Btek}¹qb}!w=Vo <dy*i:4{DRS).V6Szl%Ky?] L4%?f\2w'u]x[~q܎C캫-A|߲*`{/MOm<B2sIf~'E9],S)&ur6M>*\;1#Q嬧[Om$3w d涼mfLWac{Vl'(!]gJی(~eT9'Q*Q!fB9:8ҙ]ǬXcP>ge.4LJ=r (P"Rƀ2O)ӑy ;k>ՄTU)=T|ѕlFW&G[fQGٔ?g;ӒM?fDiz`Hglǐ.8R;,kHןc*ɗ(?Y(c-aKk Hk~Jg >Қ +Қ 8ܖ_Q%1|##kGȯR/JU“x>/[Jׅ77- WlI+(mbAm1a,'[vK`,X0-|Dշ |߶MX졐:7&]cZ f2:ϯW)EdWӏ([ cTf?2%*y*\p]jb͞ )NhP+vI/ߺm \ۗyNegWHI6ҍ` Rdf@E5!R4lR5l̲ 6+rYkR1CTsW>R0tׄqeE(f*KP4.M%㓙DRp4[f6/#LJ4}H #1adLxHw #2t0 #54Fa1v>FvJ*UUsw;{/ȪhY0ੑrY:]HFbU!3_H0Of%4/EF*wQEw7Fg&ƺfsC ]YģWs@3we^ y*ƁqSU|zȌ/yOSDK<;\Y^ ڗMx:H<$0YY,W^xVeLj;,W.jV+mDWu#Mu#9Km΍r=2?җMg)6Q ͡eRotVR\5|%0N=NY:nyLXo u)̮.gJav[0Pjy N\Ƣ4fί4fӄP]zcƥwF"#Jv^mlՙnGJhvSBJ .mb.oLpdv4OKEbWFE2Fq-#Rƃ*Рbm_ ) FQ'7[&LH%ܕ(DlU9͗"f.JjѹL#24F0" fYd H,`YՍ:/>X "Y䓛EJfY" f>ò~>Eai0/R#XIjDJ46M"2,a)DM" yq3A憔孍g|P͌O 8&ffd܍ЯXYVKΫzezz\lʥU\R霕J:-u )u *ɒ3ʣ6^bG%3Vڒ]JlRC*w ђWxʗ\ gqɳs%6[f00S!HܦCggu#?vF$6,+)/w15"@6F ,y&Dduv&ؕR>EwImb4HmnJ ~gv) w]R1>l"7]) _uDx]ev_en/~iL.}ԹC'6ie;u z.: fYw*Kn>,ԭS^m-C\r{.:;kʸUF'F)< V\e4{@l ^i1 Nn߳FtaX R(+J( HnN@%7SҤe(Бܵ$77͈֬r(0/3Om(zroѕlDy`O6\#]gHn(ƛѕmFmF9''Bz(C7L|.]< 4;N8M)e(R]hwzL-)oS`|+k'Qze*e׹)|YI7L麇;KTJ= oY63P}m<$67,B)_Q3DxoUl\ema6v-2[1TWKuj,RA ߂pH;_*]h5ǂ25jOPL Tþ-|԰^ְ S~25/aHa?S~b1Tb`ˏ>e?a *yE6 o]7DKSm7' D^r󘨏>腇JlcZ]f\:pޚ[Ц1ᣉa14T)04QL( 操N&h㓹[ⓩDOUCC$uIyde>MH8cHa[RamF09 # 7?`WF>asF #Rb񊲧aWR_ ƺR,ةaEZ"RV"%<"uRHC^H.6l,G"kHÍ:B}hiMJl% 4RkQ< _+Sۂ| қdrd]dL=s%DNv}x?(yVV@NvܨM,yB ^֙oγa3-Nў)̥TT~L=۱Ye JH>Ybj>Η̮*yAWT9Qs lIΏe&3tIĐܕJp] ۠jAb%-?6HR#S[#x̲?&j$:OOR`/͹pkQYN)ΧЃ[Lo)g1  Mn醹]B]G[ZmD׳*eϦ+Ź;}hfcPe*Զ.76(+?Is>2uJqmThb t e)Ƒ,[qn :C{,t-L,b nɹ}YQ }B)%fg'|6j3LaBlB@B|lń)zUƈò(g:F{f扔We8X+<&R'͹_7/,:*Yݐi ?A e2{VƳJrnА$.CY7}nI]uWo-bJs))%p>e8J\10guAp91,t&E%8ffX<#NIp> қ;Eܣy\zs*l<ڗucAIo83087_՛$ҶpT!=?(E`veb"T1mZ6iYTłP12ݧx]C~jϓ"5L5!?с5v Ԙ "Zc~" jO 4kзI5k*5'<Jnh2H)E0%7O)DBXT6k ) cBJn>&'X ƕ e6OPu`2Bzdqav)ffs57xƖ?-?(japՖ<"M*]_ti (y\tlٍJk>=SWiL8?fe,Ȣg?X>y24f%B*xpg4 !}O$Fg4(!Kl %6is?y6P"$$d"E|?H0Ix̡0 OƑ?GzexE'HidUϑ)ȸ{df%"91Wu1ϥ5f*l~uYLIdH2G:Co2K%31p#QJi{HIdسऐxsïۼ,R{KB(Ii~/bQ+KIJOe8o) N_RsnIcYf2MRb,\ z]'O?vF|Z1e&|VX]jї:0~}P;R۴~hӆܕ3-6Bi%8Ol)(?ȥjrڕ<5Ŧ .KT0v2XrR~9Y]>%igKO)M:s_9ujW}vG<N:sWI~`K1R4vPoYuYxt&$|+yf64Z=>BZ)]!_Iu]MzVinb\J%L߲_!f%,,2̲ͧەmy)X8)XIYR65֬ !XxՎ>P"ŨM5KiӰ)-tE͐Y7w;:(FDŽ2I,_ &/[7Eln j㴻6ZfjB mBſ& 7Vޱ]Gce1\8M>xS~);{!|fFynY|եc[L•&s3l6qKpnNTkRAr6箍C8%8+kCL|,^%:& yЃ̦򧑟&uięMѐ^lFu1]׏?MүB*q<̣U1'󶧲} 4CCs[4YT9j{Eoo jҶpEV12wt,<*] Xܖ5e(հ0հalհ@I XP[ 5V~b~j؏(S~jۢjOS~bymak K ;]<& ehjב>eցfxR5|UF}P]R@U`1/6(FeFtnDy,ѧj@rO X(d!BnTQ1’)oOF͘LOjR*yd&rɤ_&E#*&$LE6%KJ4.h&_DS6 $fH89$B>DwadU7!>^툖id+HYdP"xcQ2{P2KbW|>w|Β:aRQSsl^.}0ӗ̓O"Šf%ɗFw=W)gǪS2HJ́ruJzz1IhC!|*y*) ϕ`nd>'8\/Rӥ=L^|4=vKt.A2H7:OntS\TJ(y]'s߱ }VcH7: UuMЗ!]zjXRR΢*qm=eSBn$DO(@nOB8A.+s&ѧmxy0: PA ѧ /WpJ(A%uI}z>,6Vz`b2ݵDw6|3Blú.B<*# $Ee Vٜ|ERt6=0l1`usSE(ITᵤS~27`+Ome3pOK5 =J~~tf9{HRZc=S}PZ:x*z?XZ[4֒]C=A50AhdG#5I_0IceWXf9|4ˏĢۥKOߢ$LS$MK`iӷ ,]#K,W Rx29 CJw=R6ʄ@ O>!)2,a6i0Uj,хJn e 0pK?fYl{C EG2*eQd":Q(`i0Qĸ.Fϑ(X2Q̓$a0SyGPb)GңgemyS0ݥ[zA@ңBjfÛ2=d $AlFIEzlxՎpIau9Xx+8.=^7̟Fa#/aYH,}L+׀jp_PriN.kP(7z(=ǘSvdn0=ҍdžd2PqK1RL-7.SV_y)q;(H7N(J /ez@O0>R4cΥw%gKA@=k˱/Uzcdnu(Y}`L-H2*9X}j{;Xj^=ueu%VJX]2Bۤ.5{(#Y1]R~qk=)J ߳ŐdKf7z%K͌0 ߔ!E;:='YO!ݾLVn2R<ӳ@t$Kut= $J>eF3ʹO)<M\7J`JyTSDwCfHTe11v}XZi&Gnu[LÔ#M]ֹHm1NmVm^T"L^ )z=ݥhH=SZNP8OLӗGV<T2u+B*fw>;Ƃ1!R/5+=9RP=1FtS믏))w_ R ]g?X0s,%vώ_ʎYJˠS(G[~TF} Բ,G)[c/J`)w OU|eO_Ɓ- ,i .fpׅᖓ4qOentu鱍bu_!4`ڥZ~% _[ߩgWCNDzM6bJvYרrq=;(0'`A>TlJƔK+k S'$R|+& )(w!DHD9J!8;*JR"wF;},[}|REL%SRPJ6ʳNG'yB>,eR9?}θ>DŽʖ]YųgdY $%ҍ ("UwX(kWEN.SR'l!`D)ŧz|OeԾuߏ}=G*v}o-CEiC~0VwI&vR`bG(^{#Pz6Zr)wnGWp"g0*}mXÕSD\_=@WAm ږ-7e|cز/G{_[ _:kwoº4S#-5cKaq?e5k܏sY-WO@?&~P.%ںMz@4ׄ$y,?R `L,eXb} -K`]+ܼG)֧%ΝJ>*{17oW.R7K6.6zSuGY1kHn JY\H^,e^KiԃHVץW44t0sм~Xux'NT3îTLṈ+c12Yrz]yv? ]bD*kRB¬ƚhV>t3҃K}-j YM-{3&s>Eie*KdrŃ4T&OBH#OO2dR4$, ׇ׍ӯ}8cxѪKGkE~o0hէCbi !0]HLJҞ0ad"KE7<$$[4I4n~AZ0"\JY}"_=Xg2_6qu WcZg?֡TnZ_*Nt 9P2u[z9N.TוX2î[\2 ~12+=2R*3)zHbCYc3r"+o7R/JR{eRO`eǴn3¬IYHD} ZL!ҨMZ :ЮzMQfv`dW_ux ݵQ4K6Kuw+۹Nf=RP It4$t~*-'/t:E+wS%QV!XD);Ar܊0 Tef-9wCEC類 )$ꈡ Jj\pTS):!Zo{ ʣhiHܐr):j:MՐDLޔ  ^p1T|F]&*B)\.HΩ6G#5k&@HJ>.:M*R_\d*EZU!% WYh KrwPu XIc R8:Qg} A-MlECfvY*:9̮ϛWRL,Ҩ3P!vERhҨ 4F]wDꜞɽ]Ů6F%R8H#R_~M ϑ {մ0}.Zᗊ0 NVԴgUeAc_j_-@i KLuy,_b_3eAO S_m;iZGOԿ[*uxKOiU!Ϙj; ϱ8hYܤ.1դF')#M>uEwʏEi[Hi5tQ#]MX*).ջ]):ȮxY].LR캉ȮF?'HN}vRkP{V!sp]3kWT /ʩ.2Է:Ğd2E{c:uخ{}GZ&+6(NL],˃TVzt$ Q>kP#Dj, .%YGڝP3FzpT*a^RX]ʼ/(RYKnty oTVO%7K5EOu+ͺ]W}*m0{P#,'87>JI@2ϓ@ruZX%: QBmbWRwKtwqGubD5OҫW6qSWujzJ7&$XgJKoZOvem8[JᕒA׷k1)twN2 M*ßTlA=h :M\H>#iIO+8\ubBa꼎e!/ӫl㿳U,vGV.)5lؙbK 9ټRhEmҬAVuY -f}+uS E,E)7nR@yY^⢏v[+:Ԯ{;=#zR gCGS>yD<<TU1T#o7Z?%6|FN' ЀGh٪,0_,`u6WҬϓDuֿ~TXI>Ol}it)ҡ)k}.AKleW?m:K'lsʌO'OTfWʺv\)~/q?潛5P;گuxS4\K.1)]] S6NeFyHҫY^4e/GܾPĒxϢ&Eѕs<>Ow<◎y Rf*ɺf)6cƇ/H%^WL/2z]72Х_g@+$_ף]z{H~[U7 *k>z#GKYI]w67an^b(ts%(ߺFJXᆕjC{ߠvvѕ)?Xgë3z b#L>&xD:Ǟe*~]f׷o0j斢W+{~]mJ.1{|8KZ~h\r:K5 #R=XƆ. ;[$/P]a Rue}^:C(C˝xrV?OQeRO=e]qo.v[.¿wpQ?mӵ|ο9Ͽ6Xe㿻 <Vg[ b_kv,+=  #dɘ.՜2 M*!'#2-a]I3LQ72Fv:qsˡQb-jcjc.Wnl+E\jM8!/[D/-?#D( !A-K5\~ĝUJA3n_Pwe- ㎶+yv 0-ar*&бKq:m"& iRml5F묊FyeGVʬ!_!:08Ř_-kF744~ICAiTRm&ih"YUe|8jYU~4|Fi8W#8nĦ%-c]2vF&&.חjُ&*c$bPVX*vYXacn0]B}]FgB. X늂H6V{)dXWXb#}g9RNwʕeX:X&t%Y'zv٩RNxs@6Qi]IW2vz++7#G cuVï̲̆K^rWYw2벀HMFY0|]>B;"kૡ]0톖-t» *˹(uZ( z}[3r% ouFeY׷qo}5úYiYLdb٧&vX ,ou.ju%Z#Ю/D!]袁v}%Ү~P JpXu , nj7&`H׷2+wB2%j7KnX9!6vf%[!]W!IK& /'_iez5,Cm??2ˀr]t]Out&ħȺ.PBvuV*cWzv|Srpt]bXYMwXVJ|WRD JcKjjL]R+OL`<usvݓ&˿>_FuQBNӺq]ņv}/!]s'R.%, .vi|w_oUf> aJEuq`x.Em/ Cq!]Z#Em~}=؆tj/?]<5}87-`6-6µi <. ±k{Z`2-euՆ2.0Pm6Q|$#TmOi&@ #\1 7y"T0#"T G;AOITPM)?,KS/"JdA*ht}qy$aw3φ)qmtk8;E6O&&\?NjbsX~!Evqi&:]Lb}6] #(:?ڕkhcCt$`,hm=HnhW]efZ%$]@ҽvyav5,+tҮ3/<*R+m߱v%7t&<Eov:MFu=JTlj'TR":-ȿ'ẚQR5T\ ,`^wWʹr V6Qe / (QPWv ^?Nrpeǂ`eaR?z^FF%LJ &KK,6ޚKdƒEJPf# CV5H*uwؤH@@'#JqHi# ca_ ޹i\mE)l ii"$.. =0256uXR5 Rj܌+Pʭ:şf_ǘ{QϵƔGN5/@] * +үB0~]/OVWʌ+9Խu_Iؗ7G vJ|G72K]]IJK㳟( Fv]J=ˍS~ L~}.B9uq凞A<֣TMڥmf(x雙徒4L U˟ZC:Oۡne}+_Qt$(X p`GT$afiW,ch=]۝ZJȆ~]!(Evэ 6q-G}}u %gyu8AFNjd''g~h 6>غ5E} +&Z.xg?[jdyzO$5;.+:LjX_#$h竗f˃%hcک]bW,d^Lj(iF.yKQobbOjvo8Fv cqVYĖHŮ &E&v;nwE}qu; A;2ӴW|Q;4wX n9<eZWf{iҀae vMCw! ]Bef VXR1ڀ0Ros}޸LjVN4hהC%iok1ĒY`Fv0"Lot PKJᎸv;YMP&iڗI..N 4j:VaD*EM6"cZ9&Ie^BLeIebJ[M S:eBv!r3UO}gv]Nr/Pv. j8cKss2|e1d}O'Q;!]42TC³}]eci)ƃZ*ƦM_ Ҵ/'n8to{>"-jkYj[ds;-yR4\y-}RDʹ .X]4<6Jv_2H~Ы {mbCp~m`Wn}.^M+ba۬&t6R:eUhTP>xn9;eُrgI)\<ÛILڸ ' j$<2y<88UlU6|FA`V5`HY;X2x~%v"H)OgVp1))1h pM+:ecDz ΟDV*RR%fW3(7˵麁ow*UrP^vZ0KjWhދc'}>֤xĵE ,stv>di%VgbׅZ]6X1kR%fg mh% 1KV,$Ubv:,^&^-M-?HbB܉hƱmbeyS+^{`=֤d˾WٗHJzJRK5Jed)Y:Q9S]6vث@+sH]Xvy?o)ko\[΄.c.tE$LQw4Z)@4Bv0T/Z_$m׃Xh[nAhOv<خM-/Ү$mM:e2|ndוY:R>6[{>68 1of)I.+^QyeZTSyԥo'lXׂLk_>V,4B-5v@f5^/jkT\] L-4-SySNԲWv%5U)F_E+­▚isE`50Q[6k(]خQcA6K6 qYDKr.NVhxyTKR/ԀM9u.+;c-$j70]Qnt-lO?p)O˧pϲ-6ebm,C¯e­ײSO f]j3XPe0#Ybyā허GD ȇ]ҰKcWh $~i11!(>`ZŤQv ,ap+aVᤊ%a :|e7/\+Yf4+GyPJ?Sdejh`%jx`|%j7umbݛWd3*3ǛٗuwS7JgKNq}#Y;}'ET(+KQdbW.*_:cdc<$jWijfkf`04j_fv%7Tlܔ]]mjv0B-ԦoH>?4mJ>CeikFvM,%ig0k$i_,+W[mIO Ip$Iִ//P+eƔ֐b-jg8 (=|r6ܪ!1M'7WSo.,eN~޿5ΦJqޚNkj%k]Ɠ4 'd|< ^0(,70L?1iߚL܃С[p2 ʞ/|.;F@i9.]S'ȕ̝ :>—]z_*{;K{_9ˍjxE_0ݰV-e*НF8r@HLׁ?؂&rkY_Ts{whw}h_ձLu=2Q9mdW,db5j`x}\5y+D_y1TW_WcD]?+4/^e:r'0kTeoWLxV*w(j0JܲC-+V4E4#:̮4'ٻ]ǝ7쓒OIYKh>I~,Km^yd,Cѵi] AΞRmTyd,Þ6T${?eVxʟ${'%[ᆥ15t@CJKUtPC*4TGJw80Qw%|+0%x_:=TpzK92>`e*ոɍJZOj^u^buxyOHN\TX~HN/!kejf";fv=쓑4TzwjdWNVjV7>#|佀uw`J]猏O+ĝvݮo+(<Ь|Vsخ_VRIKR7i{IautrAIi⅁:Ll-(Yj%dݕpi^j^!4)?IΠ^on}-ޗ7dĐ_)_{JA$Pjw>j |=vXȒf K]װ~>9*ɻ ^k`%yWrYKll)Q;.oWMwW` 49һM-Ζ]bl2" bp$>oڌ-O/uS}F]wDa.Dݷ QVcUKuZ c~:X4VL 5~Q%ަ;F~K;M+# }k%ݕq?e3.!Trw1Х讔p7bYrw8uy2,4$x?&+;-x+ͻnJl ޟ2#\d lN[֑[mbnpn- G5-TuíS-!A=މhD0<'bAՆpʀD ( 08a2.- PXF(:ţJBnWD@j9 gU~~Ǻ %z ^]?xG:2$lVz҇_U )8[ uSjonJo?e)߻*L$ 9 06۹krDy6BA9$|]|c}aޗY'ٻ\ )1qMcWR˺+] 4l3K f,Nq,y:<:.]rxʃH$z0i$z[\,CY]vJP7rsr4 * g^%7d8uQWs1RꖲtRt}ޏY(Rn:tr&;[MA*;ܪqttf{IɹɤPwz m0Y'ձړ| WMfIg N7!|Iqد%6r<ʏB[k@F'g4Ao5^Rl5$NJxgꝱk_]K.Uke{x|Z~H.ZW`h@L-ߊR\*׻L-$$y'{C˦Zk3~u0sXiHyr4\yU&CuCdCtk;RkS_5LT}ߌ:,~[uvߚrs܆H>o#b~3xc(`viг#@˔RPRRD5\(;{?}]w/朮RxKRl,&;B^I޷5kv%;Z癗'je[$wד8wluy Wɦ{oܝu3sSRZHS 0DxY i$;]Exd,?]MgC*`cYe-"Qhh^cE?;̏Acro7o1ԧ̫W[c x?X&'LX"PueWzw 0`aiZ/t_fܝBlr{elll]ll-K*ċGjwدJ`3Jྟ AG5G0vʰ巟HVx&]B$w c"HB]&>orGYGTߦ krjĵLR&Zm۴-épjYsDœYF(H S%"TwiyDlq1po(@ S"@=e68-"@"Ԁ"Xb_GZw=M]Q5P]uو{ѕLJBt̢kBi!.ҝsí|[)V7t*kJ:ޔrinJ9kzcuy]WphX-*io|ug#u:9NWJq%:6lve)K}/ .w+\0sVbwQzjlr_TWԃZsqWu:(]b~ Ht ڱJ%sl]J ir?ºNu$"qggH"p%*tafc{EK]2lXSJ'~4K~,|Z>纜}JNAۮ9v[/QidȮtؕ-˳ +.{€^2K A @h?+Z۷ ,wttmkW:ttefKN24U!P.w(9tvJ؎R/>&n"yJCs?`MYýi!ݲp3-VwB-<[mJ-FA˵maZl""BZUZ ,#U, htĂ,/E$96X"<޸QBNz -b@Y@Nj5 Lf)8ZQ~H߾Z_9@#$};Ay6=ZkJp*')L&vUaϤŴu΍+uD8q}9%q8elbW}Fv-u*fCun -sCtow( MJq:$pL) 3AHcV);ib}-'uzuM:ue,bjhVwyhlJ侴? EL#+r_ZDn>|T`(Ky]ƕ~H0);X1 ))ß.X]^*w5{-sg}64q1!x(iӽ0 ȽZ]Y vDRk(6Z7w'kTP?;@Ѽ\r\em'dG;R ̝0t$)݄.K#/I%u'VlACoהh "_PmJy{SFQ sPY5ҹsPzI.w}Ju ~S6Ԙ[ *_ҹK ҹs&/w󩡽JΆ Uu';k7 r8FRӴ$\9:̤tT(rgrBQJqʟ6KoTn]^lT~W>yՕްR]" )pW)6p,Q\i;`yW_F*H^lb$=Eq糸mYQUN<lƈTnqeMd]}ѥ` 5뾁te:jlaxs׳"wyr)dz}3`{sPۦwR=Bt2Tq`QvePZyIeF0;-M[ug3j!x[]~Vylki+^Ze^I.hn׹TP+BRI45ܷ]Dڋ|V\¼5 R+5|3,:]^bx}pJND]&pl4D` ]6`}X"7puˀ./5}k(e|T2wUx k qQ&^I<eeZ?uuob ז`mOun26aXw8? sxaăjF"AZNh~G0x,#sVD ,.n0Pri[N6Ga,e`ӿߤ;T&6+|u ^ |c _RڐrHQP`e4R떛uZwt'b);-Љ!S.wBH2\ 7Ɲ8TwzTKNHT̺a~$f7 tƓn>׭ (_ mT7%}kHËD: uxCʈ#nN N"_܏e>w忆 Ӑ-EM+ԥ"7KnLܘd9ESLݕM&X~Kw,M*c2/]jP$ĝԐh2ci) ; p+Rjfu]T){kTFu0*i?+S´mxem^&ĝQ ,Iوm*{XR5ܾĝɇJNӺz|OJ~qWܮ,ve75Kw #}{,\K=Kw p u+RMwͯ3&p\LwBYrP{R5K5K:>qJԾ-wtXyZ4lfN*O k^pڵ5|VOfv fS[܅3>-Mfv=άk҈x(&7+1]8i[vt=(i %&7%i[.uby@sWw نw%u=ᥬ OϏMc@=2|Ja7 OkEh,4E)u*cVfo:Z~ R˹U~CQR%2z>.4.g ]@ht// *LtӈtRVǖ_-HC?g].%vg]~³tK1)x4CJw5e|ҝ4Wp)ݗA]o\ ?+vW/Xޭ2k B6pµUe}jLng\[/Nژ;PΊp49AŷzĂ,E$"xG (D06jD l;-#"@Zhh8GW)'>NiiLqprUaSM2Iݗ1שUA.Ԥ㵛{S/zn`ׅwbuCB3M BԾA]^%zLD%w_Y&R/\H.QO]}u<2+UC;/>z!}2|HNSoh? ¯&%;;4̺؝M:ݴb^(+)$v?M+2gHN':̗AxLTѿr9-|rBbS?"Bi|.!xҁ(8_F&b1T$v?e 8Q6K];0Zsd|4#؉ZKn f3lgJՌq3sҺۭpe!1_Jbw[=_J@egOtEwWn]#%q6obwC®3똀1 %.a)mͷ6]S) ~^.t9I/!J؜h}y9OBSᔕ3+A9eTKϬL DŽЭm5)kR)֕3WZoʪ3+l/ZBTw w%VF-u4qHVQtES*U@s2[G;eճ@}<';s`+[V=o*Ԃpߧ`lyV2w?En 5/W-gThd$xe+ j~l[ԝZ SAYȚ`H{c~ba]N{,*tOgP xfTh9.%?tOS:5ZҳJ!({@ DP2V^d{JcHf[B)w?Vxdxǂˏd=Mkx_JRZ2E{N ~p)_0lYp%O [,L۲>]̵PPH=2 sN%<=_S^l6Dz!dkc&]k߱PXڠ`7vu`@ JJv5(~z]SݷnX*rV`K.WbT2+:Į^2;yYbn8v}_BcklWZ"on(zZUdZyMʧ}W2}eu_ghY]=TBwV!#˝8EF%0Bgh vwS L(YZgfhcZA@b8OƕAS%vOR6,9mXZCfV|,AX]bwwyJı`N/>SJ/_lHW,O/NjBWLɗg1d%v{Й}φsp%%4*7=Pb K[KHRʟHl8ycznJ8n@uPŽr]F%vOV*{jFr,TYp4%%TVL-{0Q>|.< vh-c@Π$xyRq{TF;=<.[vWTv]-> ݰW)=Gڠr)gcJ)&# ?[9͍gq׳_fB]=*KG *{,PW6Kb7+.p%Go?Yw}`8^=$usx M'OHn}Ȅ2x. ʄ/>sI~c|˺;n]y\~Rcn2[*{Fy8e)Ҿ]W>-?T+9Kԕۤ.E:TK'*=*UKaUjs@r6.^Ӄ"B#W^~ |Qѫ u}XWFKL~ݫ%秊ΫbߘN%mⶕfQ=x$$OY*.>̮AvJw,pX+*YxҿPW{^;{/+d˕ķK]at>Ϩ4(/dC_`4!uu',FޥY5gfQ9"=ܼ (R h]B+uۄp >I.Maq`RQܽJvnV9R/kt>o84sCh w5@]w-8"!{5C.gI:rU$[p>CNn:w_$ fJrWuSC u6_f䏰c޸VEj珽X|NoqO?.i+׹{*M<3.AixRaZoKi𺘮uwV-.xmٹǂ8~^D|`]yKa:nUk)NJ?.9~w].~g=%D.ŽmZyx1+Ԡgxi \ZI UyT_۲lYC)}[OT{S;uCUӶ TXy0&|>MIRhm=&-rH4X2TAAʗo[{ef\%(٬Ui#҉Y.)ޯ(|jvE_j/^Oow6+]SjaP "o;\(f+?o! b1K77Kg6Kg6ROSy|;M+|Wi;pp|bF0o*svX4\IF?;'^_P;|F 8|=W>>%_,s7o2󴕠<Lٶ#]G_d"TC|o>"~7QE߻A:cyg>($^~\ʯt W1|ͧTJaKﶮb*|JQL6+zXR/R%ϫX*7!6w`%ߚe*{=Է8v&v]n`.!Fu~;@Ÿ2^00FBhpe [{KT)U{(dޥcE:?VK+, j,:җKRXIX T\T~Joo7K0\V"V5v,XfD53IgcJS~cK<|WKV¢4v+ߵxʇݥ].%Ϝd/oowG9ެEG lPJ:%cC =/!|f2Mwv^I֨M:T{l~_+H5ӅnF;^)Y=HwxɨhyıKSB]}/^jwm=ॸxK7/-|&%+^+.؍zWBq.^0KA9 .az,)~*xWݳj`%.Ü  6qPRkH RT ܚ~u߅&+ |jHq5/geJ>䯘] %)bɾ,knpy]դ`X'w jq?w)G b^)|j".RJ2tC(|H;rY2im;V0Ԑ LYW~]p灙#ks_۲Ru{շ]յcձ]f嶱?խSұЭWkQM/h і/^(_pc .Zrg ta  (-0tA}]iiXuKoB+|ʮe,~p0J&׎:v C;ߗJSe@*Gm9C*X~yX] ؤ~um^YU!rCRLnf?^X)JmfWl37Zn*yz\&+ oiTx[ǹ2G~?# FatXxHxyE4ؕrSIq"Ł?f7sͺۃލ>CKӸ+q]i]iKS9n + -:gT$iK5i!xKov:S("`+=vO0khDyIDu TC(-tsisƓK Ӟe T*c<'5ƓY x)tNO R xC~tiJ]D9Пh|W7ٌ.=u7}JIJYڔqT x\T]x>/ x|YgG]=CA~HIJ^ /&u&!~7[|O\)K=?*IE!TLJE_şr_w8ԯF]'{ƕh> *+ yV6(6-bB]ޡXT5ck֨+KF{=ŭchZ6+dAVPy~<n}3ƠSwXJ9/6-us`|Ɛ(|*HӁ.E?<م-iFePB)+}K`=+{X+&Xc=SExIi7ӱ@@5>KYX tKx%~_搂򔱏wǫ y kzWS]cs-렗}z%~OCj(wxYk[iSwW5从XƁ.X4H*MKO]zhx/K$}l/z]ʺ@sRcb̖) jW.oZ!wݥ}X\_忄$sᜤ}U4Ҿ/Dw !{J>0ajIV)b}g}_=pT}+{WG\]JջSͪ ^4ٲyW{kݪW_)w~L= isL@kS7)G0e0q\,=rԠX0 u7E{Pkߏi܆t%Ww?"h9jӊ6XV*1R jLLlNW.pb*7 {R/8fVWԺY昴@y/V8=#s7~w)Fd+M+~9.9nvtx?pfvFvŒuö] MJ1nb?${Mu9u}^ j]Y,<9`d)JN25.o,tJe'%}w h1X}M%եUxgEUI~ʬ&*OPƷ >SY)Y soOeĥ{`Qww1v=W ( t]]ro _, uw^p  6Kzҍ(?[ʓʂx[wWxR0<cQ)s){p~O-Ԡ.:TJuud_שG J-oPg$c * }@K|:`TRy\IQ6`XƤSqVu?Ҿfʵ:ӊ5+;T({Uǟ4RjձR}oaW $B+L+gR!{R~ {[nfJ \mZRď+${h3-KՅWp\~|ZEAð2rSO+/XW> \@/.}Y`*U`ʠ3kPA[j=Sڏf2=bN %9RA?7Qw6=&jat]K෮=&Vr42CE6rgDΟBMۓ\7Xwfvn0+yY CR hWBzSsIO?G^XVr]i] Jb)C,?g&@+|&FnfJldɦ<=,El$vNVyΧl]/2\z+03q)^xY~z?Ub#z3b$ ~ʼ9,Lh{`V)UB*0Օ۳όتw7tn8V[~g( AMBܘP>X@s6+6|.\ӹҏm4gӹǛR3])_>06z7%Op{Rsۋ(5e9BRm5+d/8Fǧl+F*{ |_\m&)揊i]i]i]innI.HKq]וܸWQpgVw#\E 2<o>15 O>ڤ]?IF)՜mc~J)ET 2#|^8djNWd9]?~X㣏QjRy.WOWZwʟyw&ՒG[ ޷noeux|S 4ŷ/RwxC9۴c3#Pyܭ=|Ѽ|gE 8]+>/rUwber?Y|W$ywbJX+zJO=@ K0 |H2g({uҼO0H M/7]AwE;{sAV zF g,xW0}_JBO,1 Vg$ lR hK跃њv8a(RϚmn\s-jܿTcj{PH =5g i +wBaݧHF^cYwYe}]cإؕI2vKyͧ[A>s𰾢TM+?oJl~`e6zS7vuqV3_"p|huO ~KzwQ5S8K 6-%v(iCI]'M*UbVnAʡ3a񪴘ӷ$tE0uSt`SxgC⼻T9蒈(EA 2YbD׺ػ{q)QɈ ,Rͻ F?gϖQn{)yR*ŻMo(ɊuukDw )SCKwH(=ZM+T֥F+ػ_DָrEwO21 \1R;ոr[Ο2KRk#i]ȼ.ڼ۽li埵4;6zwSښT/ރRwWse"q+9ԕ3 yJ?+]Uzn+M' U<_Sw"_Z1ɤz X`ƿQzGwWcEǿf./T6-62TyJ\Jdi݀KE6VGH tW ,8`O {vW5.)y?~ 3<v׻ov P HzOY*OzO) AY}'C tdnƆծ]>0yT p|E {#vW?]Ie9L!{^D;TdKҜϔ]-ard%ˏd.J_>xW{ux宎XVV)R5K7<!(ya629̍꒐R*a{?5+ʓ&ui&u$0ow׀`NWצtΦt7Kv6owךª)]RJf{1JfR )7nft7ßV mFבV3.6YߝDznԎ7eޔg_vuz?U QC mJCwwr?uQ)ut>b.ue`Tz?: e? ߗ2?W K]Wؕ<_{b7%V~?$}?cz{IP,{Lҍ*٤}O! fuEMշ$=}YYSwWry(bJBl .]5Wowb%17+R=;%ҽy{eW0I fW0^2|ݦ%,ܦPн_6+?XKű1}x]u ӮyY.*ccccL ci?4Z<vWUfJ5r%xAm]9p$ݻ.ua-{Z 4ݳ~"(@<\Pe+{LT Qw?p]~:]^#xݽ]] ۥK:>v (%R} o`,RAs. zan½kxW; ʳ4JTjx$~,X^ʥ/ߗQmUӇ`^e{lT*{jw: +dX5<\/+ 'zW@}%RgP8a6O١twWY4VVSg 7`"(r8]j'o=(Q +;H` \z/aH_6pp )]-ǰ`~]Vkug? ln[X닑8jPߚmy߆-y֜!ĀԌ +f|[eU3-/w[S-kO Kk¿7SSg6qYS [11=]? $uU՗c:U!~3)޷΋[=в*@OJ\odܒaD'ѻlj|i h27*C+'½,&J1R8upn\WWTb캁=.?.}=tTX(ਾށz,XكMA2J9/X(,mBe+{JQ +;v#]=5Ěp[jWl0#ɷ79@/⽧]N)w /s~cep?9B${_FRy/]x6/:rxۻ/ߤ{w)~|{m٢xhx Te*KS+//%X6؍ 0s wA{`{Q/3Em7O@ )yABE|OoP l"7_|iiءJt~F]v=|M}Ye:Qc|7~^|څI|`KG){\zWp&)߷ jC/7/|M\nJ~JcYe_;=  Pw7 nHw?o}ˁ4+ ]M .ψ+t>ϥ|ERrIiUw“=&q̲x)w_;*?$__Oc66LHw }c fKs ۀp;PJ}1n--ldw[=yZ_קޯӷ"۲# JR({œMR] YF)/)%Ui+aPwVwlúNJTsw;5J*{JQ-ĮAve,+t?roBոc[2k+չɠtt~Y ~]CXtW^mUKTJNr^7ޅ^lAP}:xhWx]&C7\F Qjd)|jS oUw;Y 9ɼr-P ϻ ޗ>;5=uc*5SuR⫄+׌.܈.<>Ҭ9g(QO%<t(Q1\ OeN)Qfn4FWTwВLJ` >͌2s]w>|cZ~(+@{,^)^% \zޥbZWi]i]t@^7+׭{+*@*!d fJQXR/v H]ѹ$-)Vfs⿻ЊA7\e~n-݂h)gɺnZZqϡj9|se¢5MY܂E߼׻Ǵh>TIԖ~6XxZu߷N?-Z+w]Q+5@~w"ÞJ v1+x ;V)!v鶾[|p)\t|IyRRM%{R2T)b +9.EMKM| VpVV>=*t%ehWi-,}`Ҵ쳄} ѲDZѢ`EuB ncJC (ʚu_ Rj6yH=^>˔giz W/3GW$ -obW&vEF7+ {3лó<4Q,3nnb$" ~$Lb]]ہ.Fw%[oty3-V1EF/g%)7K57)г9*' j7ߵjT 0ߙ;)SKw.y3KFৱY)z ,1k+:5 j~j2xY +'(t~F%TM*NޛTAUԕ۠ncsa%)YmNiޛ7cncM{cRӛ/献:tƔ=!<ccJ@_:6gz(gU!< mFWkGFQ (1UXƕװ Ѿ'C11PK]Y괳 ۫XZq]чyfn\WӤ5X:ݨߵ(.y/^o*y VjbW\Q6qߞٻvL@&:8IIk tIF4+~PJG`I1w&&(mHW PkD^J}^Oh"|)ckb~ AYj}g 43h4WhfJdd C,Q @*JZU:Zگ:MK0壅_ZFeh-}pE9$A1U0C!}W?̑ҠAάr3w 3MJݸR 廆EE@{Tt7.Bn4KnlW~s_w &X7l0k'nxD x.*.) 5䶲0|afa|" P<1ʑQh߇w-_)zY% l7+1s abd??ţB.)#7Km/؍&]_VJ6\6 s_; ]n`l`[o^Wq`kNq]q1]HH$5ou%r7+T߰lTW\I]G!_#.qܔ֖poHW$]uꁍѨF,x. s(Qֆ^~ < 1t6m>< Ãx>\Xvc5<^,z6wi6pă":*~C'cb)F*-]-7kaHYk}oM/ ޥ7Tv[nzl]қ=p{k^z!m;$jiP P-hf["~Lf RP^3>kl8㠖.cC?Xݵ粨wh,;4 œcGơ56x>TE~OРҢEb#qZ h" ԡ/}0B0."4._>?>>?||_?~z/~y/|OO~o^Ϗ%?===~O_9oPK!raxl/worksheets/sheet6.xmlM0,I]6VCj{wVAm'Q+eOؚy("{p^ZS,I)#l#MWџ?o>Q7 W@E}]yn{@`|E1/z'v:^]7%XЉPkm; &Lr3MkpnVH%1B)Ѣxu|sbf Yoې M|겑`l;qV!+VK2痄?;kc੩h hp|3(U#ىx~q@i;]pq ^`Q8#5BSL/X+Ba?dtZLh!L>/zB i uxӽMO<;3Uo=~ʀLZ˸qCPK!!_Qxl/worksheets/sheet7.xmln0@@A@*j*kw7@l= fdǚ)QTpO+q #gESPkuFO $UꏃJsSao4;cM:PΜ.{A,yJ9ywFإpb)xDj֐vF);<'B zg^G Z3P\M'^OB6}06ܑ]-DM RV%TO'QMWA˦ m X$cSHy[#U!9\,f⼔:n,,yFqq'n@-gbZoPK!jRxl/worksheets/sheet8.xmln0@4MhU*UUڻ `w7@l= fdǚ)QTpO+q #gESPkuFO $UꏃJsSao4;cM:PΜ.{A,yJ9ywFإpb)xDj֐vF);<'B zg^G Z3P\M'^OB6}06ܑ]-DM RV%TO'QMWA˦ m X$cSHy[#U!9\,fQU,t=ga3=0%tk6*=n9Ӛoh~PK!uvFYdocProps/core.xml (|N0EHC}b;/+IC]Q ;˞cن!9sgdu -j",.E/:\:$/k):E*5W ?onհP, &-Q=^ɭ;o wsY. 2A)Yy?kńބq9#3F_mSO\D2ɒ%.G3 KgȾPK! 'xl/printerSettings/printerSettings1.binSKJ@}I!!x dI&1M C:t OA%b jg6)P[J½58E0! ?ZX|`F+)0%ޖ}JЌM2Nnj,B2Rh lZ(9kqтD"I[xpR յ ֍Gǟ{zFC w\+PM;$S\P#Ʉ#LƄxVtyw}ƻ9LN'gbM>?_ںfl{T?6,",a+[s YVlotwiz_aGWPK! > L+=xl/calcChain.xmll,ǑXqv2DB?? (; 1<#٧:ϟߖ|_~?o?߾?O?÷?~n_~O/???ǟ_eoOouݟ}o~{d/s{_,zGʞ#W\r%7׌j=5{^sky=o-垷{r[y=o-{s{y=={>rG=#|䞏{>rg=3|{>sg=_+|垯{rW=_+|0|{sw=߹;䞟{~rO=?' %'7|JO)W>˧d-S|=(K~/wƥp\Jǥx\ǥ\Jȥ\ȥ\Jɥ\ɥ\Jʥ\ʥ\J˥\˥\J̥\̥\Jͥ\ͥ\JΥ\Υ\Jϥ\ϥ]JХ]Х]Jѥ]ѥ ]Jҥ(]ҥ0]Jӥ8]ӥ@]JԥH]ԥP]JեX]ե`]~KٺKKK KK)K9KI˃^PYkYkYkYkYA]ڵ]ڵ]ڵ]ڵ]чU'V(QthGя!-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-kײv-k[Y[Y[Y[Y[Y[Y[Y[Y[Y[Y[YڭڭڭW ڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڭڽڽڽڽڽڽڽڽڽڽڽڽڽڽڽڽڽe^e^e\+~uϮݵ?W^kY{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{Y{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{GY{=ڣ=ڣ= [ I۝  [ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڣ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=ڳ=מeY֞e`b=d]f}h\֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY֞eY^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^eU^k*k/Ճz^^=x垾݃{ګګګګګګګګګګڻڻڻڻڻ/ʖwY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{wY{eڲx/ZRwjRmY{wY{wY{wY{wY{wYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOYOY}` H,` ,`B. ,dB> -dBN I-`#倕b->p[| n (.>p\| rL.>p]| v‹(/>p^| z/>p_| ~ (0>p`| "L0>pa| B(1>pb| /b1>pc| ? (2>pd| &OL2>pe| .yY j|||YE*dJ*ɪd sx9x5b}sM. s8&C5aGFX9d]5+ʮE]v0xq䢮s&uC5PW9 ^!\xПC6XH\shf3s&u퐥M. 9|i=uC6+ִE]M.JG8 gSuqAΘB䢮ПC6sf> 9jB䢮ПC6+jq@6q^m:MƁ!Y+0|6q܅km^h]6q|ka9k]WV0E]簯M.s&u6b}M. y<8<8<8<8<1|0|0>o8<8<5qqqya0|0>o8<8<8Gqqqyaah+0>o8<8<8'(Gqqyaaa}>z<8<8aa)~˨o[~;o^{L~~ekG8RCoƑ|0qdL\{ a>8G>K8G>&.a5G>8G>c8G>8Ga>” {k|o#}0aą5G3q|y0Na>|;{ zo#8Gq||~a>|;w7|'.5Gz7|W{0FQε\8Zces-12k\|gs ('({1F&q|0a9~ -q|0a}oo\gqfu>׺o77o{?ϻwarYƵk]u׺|o\{I&<ޔ7ϻ'E]݋byM.xS8xޣ%\x,\hN.x޽`oayw/=8b=vr۟5|+yx=o.wokab[b?Ǟm=o9x޽o.x=onGX;UO~::SƵzqy8ПwyqƵnā <П_b/BwBfl}>׺.ʛϫ <;0o. <7s uƁ$ 5y߭z0Nכu]7<;ls @w?@w\y)\(qn䛋u7nq?;o.ƁN.a?;o.95Ey{ߕ~>Nh]'7N'xwlj࿹< yon uv}y onljOD~'x~??:&:&y[ox^כ)v}Q5~uqǁZ{s;yoo.Ɓn7u?m]s[W;'<џ 7|G޳ܮy<\|G޳\梮ПlwgZ+gi+gx+;x;<|{?!b}={f?b}=#{6?YGo\a{fӛq?YQ 7@3\梮П96gѽ <x|{ޛ=587g+g++g:+gIN. <o. <ٙo. < oy }sQWy(}sQWyF\|Cx? <Пoo{П! yϮ~sygf_s~wy3߸c{qy0s1gO.x=m]yϊs[W+x3'<_ |W|E+x?_<_џ W|W+x+<_ |W|E+x?_<_џ |W2 H/ g~sِ8x>gs(r1ll.x x>~qYl6@<@b>`8x>!csy8x>8ϧE.<|G<_<_<^<_<_<_<_<_<_<_П/||A?xA?7u?u}7q]M\9p?ܔ7n ? ~)~^7qk0M.yݕMƁ7Пם:~y]o <n>>П7+~07+M.>g>'<n07x4b}M.x^uyqz8 j8 Z8X*rJ'1 )z򿠂 rILZ_Z _{ 굞k=uM580Aֳn[mNbᆳ.Lk/h`YēBvuM_G0OT)@ʷ4z$vՖЪjP|jЩĿRq{QF 5HPB 5O> 4ӮPqiЦiiPv/ 4Ү>|+ }-Di]e-,iEվ4Ѯv4ѮP1ߍEj_ 'hWY !|hСv *}-Dh]e-,h]kW!@*k?dϠ> Ⳬ ֳ 3 < 3 ; 3 : 3X 9 3 8 z3 7 j3 6 Z3X 5 J3 4 :3 3 *3 2   3ˠ/3dˠ.;B\oپ2H, Ʋ 2`*3dh`); 2(`(,k',k&l_ 1dВJv2dP݁!|dБF\dPDcGN<cЎ:cP8|cЍ6vts\cPY ,ȳ΂: ,͂6 ,Ĥ2 ,˂. ,Ȳʂ* ,ɂ& ,HȂ" ,ǂ v,ȱƂ f,ł V,HĂ F, 6,Ȱ‚ &, km_`e@[dpe@SDd@KWpd@a CWc@w;pc(gus_)}\g8lD{<1wucM:^ So32 {{{5~追w/4ƽ{a3NDٻw/ %pByػR*`tK.7` ^MR ghQ "r#oo-i r-e 2-a x-] ײh-Y2mXo2ʊ ד(M++(۳i-Ft{y[zpm%'~|=ٷw > .2{;J\Zwvʚ(ck5UCQJvSSbKȾԷt G |w~뷧\ b,L#?PK-!wK)H_ [Content_Types].xmlPK-!U0#L _rels/.relsPK-!*SX  xl/_rels/workbook.xml.relsPK-!?Tx xl/workbook.xmlPK-!7MB xl/worksheets/sheet4.xmlPK-! x wxl/worksheets/sheet1.xmlPK-!}1Xw Lxl/styles.xmlPK-!X/20Pxxl/sharedStrings.xmlPK-!5sGxl/worksheets/sheet3.xmlPK-!C5xl/worksheets/sheet2.xmlPK-!;m2KB#9xl/worksheets/_rels/sheet3.xml.relsPK-!/xj^V:xl/worksheets/sheet12.xmlPK-!<xl/theme/theme1.xmlPK-!bNعTCxl/worksheets/sheet10.xmlPK-!Zl>| Exl/worksheets/sheet5.xmlPK-!raRxl/worksheets/sheet6.xmlPK-!!_Qoxl/worksheets/sheet7.xmlPK-!<ֺW[xl/worksheets/sheet11.xmlPK-!Y SLxl/worksheets/sheet9.xmlPK-!jR:xl/worksheets/sheet8.xmlPK-!uvFY'docProps/core.xmlPK-! 'xl/printerSettings/printerSettings1.binPK-!8docProps/app.xmlPK-! > L+=xl/calcChain.xmlPKiopenxlsx/inst/WORDLIST0000644000176200001440000000222514155600363014305 0ustar liggesusersappreci args artMS aut Barbone baseXML bibliometrix bochum borderFunctions Braglia BugReports cbaf chartsheet CHRONOS CMD CNVPanelizer CommentClass config cran cre cron ctb ctype DAPAR ddPCRclust de deps dev dir emdi env eval fedup fontSizeLookupTables fs garbuszus Garbuszus gcc GHshiny github gmail grDevices hashFiles helperFunctions https hypeR hypeR’ HyperlinkClass io isoreader jan jmbarbone knitr LIBS LinkingTo linux loadWorkbook LTS Luca macOS maEndToEnd MatrixQCvis MicroSEC nanotatoR onUnload openXL openxlsx openxlsxCoerce ORCID os packagemanager pandoc philipp Philipp pkgs PloGO rbiom rcmdcheck Rcpp RcppExports Rds readWorkbook Revdeps RHUB rmarkdown roxygen RoxygenNote Rscript rspm RSPM rstudiopm ruhr sangeranalyseR saveRDS schauberger Schauberger sessioninfo SEtools sigFeature stplanr stringi struct structToolbox Sturm StyleClass sudo sysreq sysreqs TarSeqQC testthat tinytex TPP tz ubuntu ui uncoverappLib upstartr VignetteBuilder WorkbookClass writeData writeDataTable writexlsx xenial XLConnect xlsx YAML ycphs zzz Δ

`]S|6pMYk_'ES|6X|"IkZ;|!<5k ܧIѢլe,kʚwה YY}ʚwE+Y YXdFրEvmdaBs5g_"O)(yjֲ5e0uMYkOE5 ܧլe'EȮ)k>_"O͚OY`jֲ{Xdה5?)rMYkg/df ܧIѢլe,kʚ'E_"Oɚq#4`],_CXd_nȟ?o6ȋV _EvMn,⫡|"A7Y/d5 Ek}7a_t5)k z YYXd_ d?`]cȋ>a73MA7"/Z"]dFh"6 ڳ_"Ok]dXEY OEEvM-XXdE"O-("Z~rEEvMYEByk֠k(d?cG2M?0nȮ)4"/XEBy^}#4`d-.,k"6Z"!<5k0kZoȮ)k0jAXXd״`6Y/df`} ! "/ZXXdTP7Y/dNȾV ""l,+ Y Z]/dXd,լ|3MY[d`_ - |5e fEBykAa6jA""½|yՂ""IQ"!+4 @Sh vȟ?E5gA{mdOa7p})4cAShӹA)WAE ,Yx"OY6EOA70MYn[Y ? Yd)kӹA)+k)MY`zn,r}6ԟn,Cs)iS#c,2^3_6{5gnQ08 >e v}C3>׏4^䲇Y? NS ~F6EShs7X( E `dvcuA7p| 2> f/25 "="^n,~mX"Vw*"ןn`te,r}TWh EBA"GA70MP E}vgaSE6ߋ Ze|5pyZ2>o^"`dvctgWYS E3Shs7aïQ E `dvcUP9 >em 2> f sgw_ LSA`dn1VAYd(cߋ >e "XmSE_E޵ve,r}tgРE.{ E3O{m|/r)"=>/(k)Y䲇"cjZ9 6o2> NMSh)ryA88\i*(lFa70Xn,2Ty6X( LSAa6py0{ͳA""VYs mdOxȠY]kX(870MYMȻvgaSd`dv/ӽ6wEnEZ/h EI)4#\co(tg c룠o(tgw_/h EnE6wEE,2h"ןn`qk^dkE޵VЌEn`YC LS֠E.{ E3Z70BM"=c{ 6E"O_ySQ)2MlE}^ LSAaS4e7E)r|*)X()Ȼ4c?8 Z,2hE?x*Ƞս,"g4,rn`YdA"Gi"gw_/h En` "= 1VAa6p|*|Qpl }tZ/h EI)4xhd08 >t 2ȠUAEBYS '9 ZY]kZ"Gl`Ȼvgl0egcU˯A70Mڼ)X(Y vg¦hd08 >te,r}lLSAaS, LSAyfE^ٵ> ?gY9>țCfXdה5)k|=>޻kʚwMe5eͻk Q \Sh \ShaX߿ ܧz7ش^Ь^nB`zhlp 5t{|SBMk m E^vw\Z6G"/ ~B#d"XMk_85`fE^5 ܧ`zֲM,k@70M B5)qStɽ曢Ak*(tE>By")4mZײ5i* B0;ȮUրE޴E>Ev,kE>By۽,5?7شp65`fE^5,+43|,} 64"t`7_- E>EvMYn0G"//(t/r`fE^oܧhZACٵ׀Ev~rgYoY}5`7g-|Bn[롅XXdtE>By{A{+43|,Рk0!|욾 LShlȮ)4?EvMG"//(t`Z6o~X`5}C,߲EByZ""V*"!=k)ٯUhA""_~"!=k ;Vh fYA7ٯUAnȮ E|""l0G"//(t`Zt!| "`7_+h"^k^SL쾺׀E޴p6"WpE}=p6"$t`7_-By"`Zt,,k*(lE|!|Z,kE>By[AEv_k"oZZ E>EvM)"!=kתY 7E""C 05, ~ :",,kSd`7_/h| , ת!|ZXd*E>By[րEv_XMkZ""ț|욲"!0^gE>EvMX nE>EvMM"!)ٯUAMQ""¦X nE>EvMM"!{A]ߠk{ΐE^oܧ|Si=l68EvMYn6n߳況5ֳ'Ȯ)kC(4 6'Ȯ)4 Y}ʚwMY˞):EvMY5em ߗX߳")k>lZZ N`]S|S䚲6g"/BY3dXdV,C'Lݧhڽ'ȮսEvMY7Eg"/ϚS֠E>XXU֠zA5 Y}*lZZ):Ȯ)k>g^YM5 6e-dO`]Byzh'Ȯ)!3|,>ֳmN`]S Sk0g"/t`Z6nΐE^?a> 64"^d{߳ދתl'5} zAދ|`6yBٵ Xdת3|,{ XdXMY 7E""Р,y}FW.}*<, EvMlE|ދ|`6"oZ E>Ȯ^n0g"/B7ٯս6wE>Ȯ ț4""l0g"/+(ЀE޴E>Ev ,ku,߳"`Z6, ,k"o^ދ|{]ӽE>Cy{Aa6ٯUAsE>EvMMț4<756E3|,¹~ :oB5f`7_+h""VZemfϐE^VP`Wțֳ""`6ߋ|E^58EٯUA |/ ,k`6y"¦hfϐE^^P8E"*|n'5NMShl'Ȯ)4 E|^ g`O`]ӽ"!^}țֲ'ٵ*(Ȯս6g"/" m B5e f`7_/h8{OtӔ dO`]Sh)y"`O`]76E3|,lEkUй, ,k*(t`7_/h E>EvMn0g"/+(ЀE޴E>ȮUAEvE>Cy{֠*, ,kt`7_/h ȧi yXދ욾0,5 EkuͳA""l, )"^dTPxtfϐE^^PxXdV0 YXdת"V,߲,"oZ~XXXdlf YYM~ :o""{ ț4""B7Y3d~ :wE>EvMn, 'Ȯ f Yn,_ dO`]Z6g"/i^ YXdה5πE|= S`O`]Sր>Y3d0ٯս6oB56E"o^pS, ,k*(lf YM~ :oB56E"o^pS, ,k*(lf Y _]۳v,М>sBs`zh),kʚwהqSt,l>eͻe Xdה5 \SnpE^5}ʚֳux)4C˞)EvM5tW"//?S>egMY˺,kʚ?S䚲6W"/i-k!|c\_*k"VYY+dg ܧhzֲ5e \SM߳況5tzֲM,kʚ)kW^Yn,_MC}*?ai٦5gה +dg7ES|6شEEv,k߾Z"_"МEvMe+dXdUAE޴|{]Sh0,߳XdVgE'y{ ț4 EEvM`fE^^P"*<,,k*(t`7_/h8| B Y Xd*k"VY+dk","`/`]Sh0,߳"`Zt B5k)yȮ)ߋ|E^^P ~ :!|{]SAaSdBA"_"Рv`/`]ӽ`fE^v XMkY Y Xdת"V"_!=k)ٯUhA"_"X |5, "*) Y XdT!5 ~kȮ)4, "*) Y XdTP87y󵂆,,kȮUf Yo}ui=kl,,k fEBy{`6ٯUA d/`]ӽț4 EEvM`fE^^P EkUy6Y XdTP E|ȗi f`7_- EEvM3|,{ XdUh"oZZ"_"Vٵ,߳~BgEEvMYX "_"{ fEBy{Aa6ٯUAl,k*("o^p60ƸN[LSh LSMQ"_"Рzn,,k@7Y+d} Ev_,򦵬,,kUP`]{mfE^5" m!|욲X v`/`]ӽ`~/y{A{Zt!|{]SA{7_/h EȮ)ߋ|E^^P{Zt,EvMMy󵂆,,kȮUf Yo}ui=kX Xdlf YYM~ :oB5k)y"`/`]SAaS4W"//(lEkUySȮ0zAMȮ, Oת3oȮUAEvBY+de XdUAE޴v,,kg"oZ)Ȯ)k"_!)~ ӐEEvM'LE|"_")"_!)~ :oB56E"o^pS,,k*(lfY _]۳v,МEvBsyzhlp욲5em!=k>OYni=k٦5egה!=k)re5e7E)klpHc{ּOYMѦem<7E)4 6uXdTP ,*k"oZZ""VYٵ"!=k~n>e7Eֳ7Ȯ)k~n6nE^5 ܧeXdה5 \Ss;dg7ES|6ش6~rMS|S䚲6 4*`gΤB56!)r , ,k5`],y}F+(Sh#|,*(ț v`o`]Sh0,߳XdVgEEvM_"o^p65"!0תs7YXdTP"o^p65"!WրEv :w"/ 6EWրE޴V8hT""`6Y;dg 6E"*<, ,k`S, g`o`]SAaS4w"//("*) YXdTP E|X4XC 7Ȯ^n0w"/׀Ev_,򦵬, ,kUP`]"!=k)ٯUhA""X |5, "*) YXdTP87y"`o`]SA`fE^^P87ٯUAMQ""¹ț4do`]ЀEv6w"/+({ XMY OEEvMl0w"/"*<, ,k`6y7Ȯ0, ~ :!| ț4 EEvMSZ- EEvMl0w"/׀Ev_,򦵬, ,kuZ} fYYXdVͳA""l, g`o`]ӽ"!0תl7Ȯ0zAXXdTP fYXdVg8X3|욲`fE^5`Wi^ YXdk,kUЙECy{֠*, ,kt`7_/h EEvMtECy{Aתs7YXdTP"o^а| `fE^^P"* B56E"oVАEEvBٵ"!"5`7g-|BM"!=k)ٯUAMQ""{ 6E"o^pS, ,k*(lfYM~ :oB56E"o^pS, ,k*(lfY'LEkUЙ7YXdת"V,߲,"oZB5e 3`7_-|XXdה5xtfE^^PxXdVi"""o^ S`o`]SA әECy{A S`Zt~4do`]SA"`7_/hxn, ,k*(lf Y _]۳, S)4g7Ȯ)k \SM߳況5ֳm`]S|6pMY߳")k>lZZ `]S|S䚲6O"/Ϛw)k)ڴl6xEvMYn6n,ϚSּlZZ"?Ze|țCބB7aVY'dcX׳Ȯ)4?7pMY7EO"/Ϛӽֳm`]S|6pMY E^5OY`zֲsXdה5߳S|Si=klO\)4Z"?"Vٵ*"?!+4)E~By{h~*(ț v``]Sh0,߳XdVgE~EvMt`7_/h8 `fE^^P EkUй,,k*("o^а `fE^VP`W :O"/ 6E" m!ZkSyڽȏ1/)klȮ)4zXXdTP,߿0ת(d`]SAa6y"``]SAa6n,,k "?!^}țֲȮս,k5Y'dg f`Z6,,kt`7_/hxn,,k`S4O"//(ת(d`]SAX nE~EvMsE~By{AaS,_! OgE^5+4`]f Yl`qk"oZAȮ)4 f YYSd`Zt B5k0zAXXdTP f YXdVgE~EvMX "?"l0 x_ށEvMl0O"/׀Ev_,򦵂,,kuZ} f YYXdVͳA"?"l, g``]ӽ"?!0תlȮ0zAXXdTP f YXdVgE~EvMX ȏըEv ,gE~LZtfE^vw" B5X v``]ӽ`fE^^P"* B5ț4"?"B7Y'd~ :wE~EvMn,k YXd*4`], ,^yzBXXdlf YYM~ :oB5k)y"``]SAaS4O"//(lEkUySȮ)y"``]SAaS4O"//(a ,,k*( ?gCSd)4g7`]SּO"/ϚSּlZZ)욲況k >!=k)re,kʚo\S߳}ʚo6g- >"y7pMY7EE^5 ܧy7شEZe Xd*k}=,k 3|By[AEv_XMYfȮ)k~n6n>!=k>OYsMY6E`]S|6pMY >!=k)re`]S|S䚲6E^5?7p曢MYfȮ)k~n6n>!-kWh"V,'d~BA"'_}?SZk"oZ~XXdTP"oZ8cA7}*<,Xdה5"op6,kנ,'dCa6ٯUAn`]SAa6yXȮ0,'dXdUh"oZZ"1Ӫ"V"By{֠*yS`]S֠zAn,4"`7_- E욾)YO"/`6ٯս6oB,k*("o^pS,XdTP f^y}"+k0ZBcem,r XdVN 5 Lӏ,rW+ Ih]Xd)kA" " m,r7R=4`W5_6 j"By{h Ekk 5e "By{֠*ks7YȮ;ȿ^"`?"Р,'d6E"* B,k*(t`7_+h"EvBٵ"By[AEv_k"oZZ85`f?!=k EkUy6YȮ^s`7_/h856E3 YXdVgE "o^XȮ)YO"//(תA"Ev ,k"By[րEv_XMkZ"EvMYMț5ew t!1Ƹ1)k LShA"EvM"o^XȮ0,'dCXdV B,k*(lE|`]SA`fߐE^U ~7hڞ7d漁o6/Ȯ)k \Ss7dgg)k 6g-"y7pMY߳")k>lZZ ^`]S|S䚲6o"/Ϛw)k)ڴl6xEvMYn6nސE^5 ܧy7شE~Ev,kE~X~F-k+4g]Sh#},"UAE޴^l6xEvMY "!=k~>ֳm^`]S|6pMxn,}ʚֳ"5em ސE^5?7p曢MYfXdה5?7pMY7Eo"/BY7d~BA""V5E~EvMA7yXXdה53,{ f`Zks7a욲`fߐE^5 Ek, ,kl,_ "!"ByZBٵ*(Ȯս6o"/" m, ,kt`7_/h E~EvMtZ- E~EvMhfߐE^~l,_{m, ,k`6y"`_`]SAa6Y7d6E"*<, ,kUPUրE޴V/Ȯ)4VYY7dXd)k0,Cs`ZemlZZ "5em !=k ܧhzֲ ,kʚwהqS YY}ʚwMkY Y/ȮUրEv6ߐE^5`WY3Bo"/Oݧ|Sil5 \SM7dgg)k~ni=k٦ ,kʚ)k7dg7ES|6ش ,kʚo\S߳S|Si=kl5e \SM7de XdUրE޴EcXdUf߳XdVͳA"Ev5`]Shs7a;U5țka7"MSh ț"EvMP3 Y_ Eku d"l, _`]SAa6Yo"/+(ЀE޴EZXd^Yo"/t`Z6wE욲ț45k0,3~YYn,Y 1"lLӽ6oB ,k f`7_Z) ,k`6Yo"/M~ :!ٵ*(ȮUh}!5OMQ"'o(Ȯս,"x/4e i<,ދBދzX/5 f~<1V֠zM_`]S`f!=k)ٯ`>7Y/Ȯ)y󵂆,Xd*4`],7dXdս,"MSXC _`]Syf!pn,_{m>EY/Ȯ0zAX/Ȯ)Yo"//(lEkUyS ,k*(lE|x$56E3 Yo}țֲ_`]Zk3 YYM~B7E!5e f`7_/h) ,k`S4ߐE^^PxXdVOC ,k*("o^p6 ,k*(3 YXdVgE ț4d"VZemf?|py}F>6o"EvMYX "Ev"oZ-"EvMA7Yo"/} 6E3 Y3E"?,p \`]S֠,7dg ~6wE~<74d"VZemf!"5`7g-5O,7dg x`Zt>7Y/Ȯ^s`7_/hxn,XdTP3 Yn,_ d")2ț47Y/Ȯpn, E 3 Ys`Zt>7Y/Ȯpn, E 3g"/^P7hڞ?Cy{hO9i=l6Xdה5)k!=k>OYni=k٦O`]S|6pMY!=k~n>egMY˺"\u$^AWy}7ke_`EBkx6,y7p7Eֳ "y7pMYo!=k~6p`ZB ,k5`],7de XdUրE޴OW>EvMMѦвXdTP"7EߐE^^P?OYe7E_`]Sl6N!=k~S>eֳM"Mkx6,}ʚmZZv6욲sה߲,*k"oZZ"Ev,kE,A7*l_`]S֠{7_/hx6 EvM{ ^o^ȾVA^/Ȯp6y"tӔ,Xd E|=ka7 ,kz l0ߐE^ XMkY Y/ȮUAEv"Cy{֠k B ,kt`7_/h EtE,B7תs7ߋ5ț4"MSh E|=X/Ȯi`f!}MȾV{m>,Xdת"V,7de͸c,k m7dk"oZ{ "EvMYn,롅Xi "oZxS,XdTP,7d5)1?,XdTP"o^аc\ LSA"EvMMțe-d"VZemf!1}i=kX/Ȯ)4¹_`]Sh0EYo"//( Ek9d""o^p ,XdTP,7d"Zt) Y/Ȯ E|M_`]SAhf!"ByZB ,kUP`]6ߐE^58k|S_`]Sl, "EvM{ nf 7E"Zt) Y/ȮpS, oE )Yo"//(LE*|6Y/Ȯp6y󵂆,Xd*4`],7dԸc7E^׳ E{]XM롅g`"lC _`]Sh ftx/c0wE{]cދzAnE `f! E* B ,k*(t`7_+h"EvBٵ"Cy[AEv_5`7g-"EvMM"Cy{ր7ת/LC ,kk07ys`"`f!07ת d"X  E SE,Xd_SEZXd*E,*(țZ"EvMY Z-<5gE,Xd_6 B ,k*( E|X/Ȯ07Yo"//( E*<7Y/Ȯ07ys`"`d?d,/VPݵ-k'_,ЌEB3yzh٠>f4emԟn߳fg)k vg-)ʚuZ-:Goyf7E)4;Z-Q]4f`zhMQ}TWh @ShvSk=lPտlk=Gotfgu]ke,gA5gAﵑE?xʚȠU֜E޵8\oSd vvg8 >t)?xbitߵ8\$tgMsS 'V֠A7]Y OB7p|t6?xbe itߵ8\$tgM7E?2૬9k-k\ EM`dvctgWYS EڻȠ)dESA,rnE=kvS>em\g/25{vg}FϚ cl`)MMSS E=k6EBA"0לEȻֺA"GA70 ,2hE?ce ShMQ"GsBShs7a?gl`tE.{ ȿ>="OYA"GA7"OYn0a7pgA7py_ۿ<³p60MY"= ^A"pu mdOMg,2"ןnE=48mA"GA7"gw9\Ydvg}F/( E7Eח4"="5"k{n,r} +"ヨE,2hȠUFv1pz E޵8\g/2l,r}< LS`CEBo"Gok"VȻ NEi "="^ͯР,rn` Ydi c룠'Ȼﵰ8\4"= E :wEn` Y ȟ?EBs"ןn` p60MSEР8zAn,r}i*( fYqݵ=kG"/Yd)4 6 `]Sּg#dgͻ5ֳuXdה5?ދ\556 >Wz7p `zAnp욲;BMe7EȮ)4Mё>o\Sh~SBG"/kWh"V,Cn`q5`7>8xBE>EvMMѦв,k*7EG"// ܧ`zֲXdה5)ks7YXdƛNW} \SA,OMѦ `]SA d㉫"VYyZBٵʚ"!m>e "!=48k,,k*( E|}X44em!|Bn,Y "^ ^C ""'"! ")4)ڴE>Ev*4`7 `]ShhfE^VP`ݧР,Cn,ﵙE>By{hp6*(d`]^n`BA""Рv`/rXC Ȯ n0G"/ kA""V{ Xd*E>By[OB70M0=By{h"^ycȮpS,롅s``]ShfE^^PMhzֲ),k*(t`7_-")4"oZ E>EvMn0G"/k E^A""Vٵ mfE^5`Wy^a S``]Shp6Y#dg "Zt) YXd״46 B5g`7_ka755", E*#ds`}B?,pSd``]Shs7YxZ5`7=!|Z="VYY#d5`ݧA7Y#dXd_``]Sl0G"/ E|SȮ1ț?XXdTP", Xd_ d`]SAa ,k YXd*4`], EnȻg-)Ȯc,|B_, stE>By{h Ez nȮ n,{-""B7Y#dȾVA""B7y󵂆,,kȮUfYo}׀E޴|B"!=kp6ת d`]^"`7_/h"``]SAan0G"//("Zti^()S֠,߳Xd_ d`]c46,,kȮUh3|,k"oZYXdה5πE|=l,,k fYȾV{m,,k*( E|XXdTP, s`} : B5we ȿk!o,,k ~a:g"/^Phڞ3dtzh5eͻk"!=k~6p`zֲ"5emg"/ϚOYe"Mkx68>Z5Sli=koN`]Sּg3dgS>eͻ, Ev,kE>Cy[i=ka757EC'Ȯ ST Y5lZZvSt욲gהqnp,M5?lZZ678Ȯ)k Sהlp,M5?lZZv68EvMY"ה YoY}5`7e-dO`]ZemfϐE^5 ܧA7Y3dg`}6wE>EvMțﵰ| gE>Cy{AkUl5ț4<| `fϐE^^P8wl0 Yo{Wi{O`],k,߳ދk|S'5e ), EvMA7"on,iBț|{] gg^k \b|욲`fϐE^5"Zem>, ,kZ6g"/i Yދ욲s`7_-)"w E>EvMM"!}cgߵ|{]Sh "{g ȾV_s7an, ,ktE>Cy{֠k, ,k,k"!-k" ,'4dO`]S֠zha7"^dה53|,Xd_6OC5"o^а| `fϐE^^P"Zt!|{]SAa E|ދ| 7E3|, XMkY YXdת"V{mfϐE^5)"Z6, EvMY"x/ ""gE>Cy{Al,UA)r""oE|XXdTPM"!0EתoB5"oVАE>EvBٵ"!"țֳXXdLzhXXd f YȾV{m>, EvM LShs7YXdt`7_ka7553|,Xd_ dO`]SAZACٵ Xd*k3|,k"oZZ E>EvMA7Y3dg ~S,UA)r""Sd`7_/h8E53|,oE*E>EvMzA{Ox/k*( f YȾV!|ZXd*E>Cy[րEv_XMk{-dO`]S`n,롅g`O`]Sl0g"//( Ek dO`]SAl, ""`fϐE^^PkUyn'Ȯ07ys`O`]SAan0W"/^Phڞ+d,țCȮ)k \Sƹ߳g)k 6g-\"5emW"/Ϛ ܧ`zֲnp욲7E)k YYn>eo6g-;\"y7pMYoE^5?OYni-k!|Ze Xd*k3|,țֳv`/`]Sh~Si=lp SdT Y5"oZZvSt욲gהqnp,M5?lZZ67EvMY"הlp,}ʚmZZv6EvMYkxSt,țֲȮUրEv6W"/Ϛ}tEBy{hp6*ks7Y XdTP8^ Ȯp6Y+dȾV!| X  EEvMn0W"//( E* B"V"Vk Y Xd56 Yo"yz³ȗi E|=X4XC Ȯ "_{5^d_`!|{]SA{7_/h EEvMn0W"//(t`} :wEEv ,k"_!-k" ,򦵽Ȯ)k07yn,,k fa;pSdܱBEBy{h071o~X$kx^d),EvMtx/A EEvM{ "_!}A7תs7Y Xdת"V,߲,"oZk!|욲XC Ȯ)k f Yn,^!| s`7_/h8E53|,Xd_sEEvM)2ț4| SEBy[AEv_,򦵬,,kUP`]6W"/ E mMQ"_"Mț4)558, g`} : B5~S, "_"of Y)2ȾVSȮ0Ey󵂆,,kȮUf Yo}׀E޴7E"_"`< \,>L E|=ka70Ƹeͻe7E7Ȯ)k~6pMY߳s)k~6ش"Mkx6Cy{ּOYMY7Ȯ)k \Sƛ;dgSּlZZ""VYٵ"!-k",XXdmZ-;"5t)Cy{Al>eͧțֳ"5em"!=k~S>eֳ n`]S5em<!=k>7p7Eֳ n`]S|n6!-k",򦵬, ,k5`],߳)r`fE^ E B5"on, ,k*( fYn,UAA""B7yg`o`]SA, g`} :wEEv ,k"!-k+4g]Sh#|,Р{}Bo""Xמ7Ȯ)4"oZ EEv7pMYO"/Ϛw)k>7شl욲5em)zBy{l>eͻ,,k5`],߲,*k"oZZ E~EvMMѦв,k*O]SAǛ'dS|i=kM,kʚ \S)߳7ESli=k5eo\SƳ߳Sd)k~Si=k5e)kM߲,*k"oZZ"?"VYٵ"?!=k")k f YXd_ d`]SAl,{-"?"`fE^^P"Zt>,,k*(t`7_/hx65"?!p6תs7YXdת"V,߲,Sh#,Рk|SȮp6y'4d`]Sh E|=tO^{ j dx/k*(tx/ 5^'|/B7תs7YXdת"V,߲,"oZk!욲s`7_-)5e 3,MȾV{m,,k*(zAùȮpS4O"//( E*|SȮ07y7E"?"`fE^VP+k"VY'dSd+k"oZ+h"?"A7yn,,ktE~By{AknȮ E|a75"?! E* B5ț4"?"B7Y'dXdUh"oZZ"?"Vٵk3,MȾV7E!욲7E"o^7E"?"SE~By{A7E"Zt",,k*(X NE~EvM, Sd`} :(d`]SAa ,k YXd*4`], ,ji=koE~EvMyfE^5"kUyȮiX  E~EvM"?!07ת d`]SAan, "?"`fE^^PkUynȮUAEvBY'de XdUAE޴~X?"MțcWXds7YXdה5"otE~BycXd_ d`]SAzAn,,k*(tE~By{AkUй,,kUP`]f YoY}UP`7E~EvMYn,롅XXdה5, s`}<7YXdTPzAùȮ07Y'd"Zt,,k*( E|XXdTP,˿Ե?ڿg Y} YMe7E/Ȯ)k \Sƹ߳g)k 6g-"5emo"/Ϛ ܧ`zֲn욲sהl,y7psMY/Ȯ)k \Sƹ߳g)k 6e-d_`]ZemfߐE^5`WYyzn, ,k C/Ȯ>EvMoސE^^P?OY)e7E/Ȯ)k~6pMYo"/ϚOYeSXdה5)rMYo"/ϚOݧMѦegXdה5"7Eo"/i-k!Ze Xd*k3,oܧA7Y7dg`}6wE~EvMțﵰ gE~Cy{AkUl/Ȯ E|XXdTP3,Xd_ d_`]Z6o"/im,k_OB7}|S/Ȯp6y7E")4"oZ E~EvM{ "!1n,^A""B7yXXdTP3,B7תs7YXdת"V,߲,"oZk!욲s`7_-)5e 3,MȾV{m, ,k*(zAù/ȮpS4o"//( E*|S/Ȯ07y7E""`fߐE^VP`W :o"/ "Z6!=48k|6a__`]|*(tE~Cy{֠k, ,kt`7_ao: `fߐE^^P"Zt! X v`_`]SA, ,*4`7e-d_`]ZE~Cy{֠k B5e nE|M/ȮiyfߐE^^PMȾV! )ySd`_`]SA7E3,Xd_ο) YXdTP"ZACٵ Xd*k3,k"oZZ"`_`]Sh0EY7dg "Zt", ,kk07ys`_`]SAan0o"//( E*<7YXdTPzAù/Ȯ07Y7d"Zt, ,kUP`]f YoY}UP`7E~EvMYț E~EvMYE~Cy{AXd_6u /Ȯ)k f YYn,Un/Ȯ1n,A E~EvMn0o"//(t`} :wE~Ev ,k"!-k" ,򦵽/Ȯ)k E|=욲`fߐE^^P"Z, ,k*( E|XXdTP, s`} : B5"o^pn, ,k*( f˿Ե?ڿgC)țCn>"y7pMYE^5)r`zֲXdה5)rMY'dgSli=kY7욲sהl YYn>eֳ >"y7pMYE^5?OYni-k!ٵȮUf߲,*k"oZZ EBвXdTP"sO"// ܧyzֲXdה5?SO"/ϚOYeSȮ)k~S䚲6 >!=k>Ev7Eֳ >"5em),țֲ`]Zemf?!=k")k fCȾVYA"EvMțﵰ53 Yn,UAA"EvMn, "EvMn0ȟE^^P8kUй,Xdת"V,'de XdUAE޴B,k7E!5g`7_ZxS,XdTP8n,XdtE,ȾV{m!55"o^а5"By{AkUй,Xdת"V,'de XdUAE޴B,kt`7_-),k f 7E"Zyn`]SAX  E 7E3 YȾVoB,k*( E|M`]SAan0ȟE^VP`Wi-k!1'a ,j,'dg nE m>,Xd156,rz֠k B^8 Xd_A"!48OO(tEh? E BYȾVS8ZF LSA,r?`g5Bٵ XMk d jZ}Ztf0ZW֠>6wE+ E m?,p\+-\"o›"cUP"OYA" n,UhM +k LӗhfW c58 dgPP"Z6,rfC3,k";+ĵ׀Evț־<~X19fB)"׵`5070MA"9t`}B?,pRP8\q'07YzzÂB70X>gE.tc E| `]S`n0ȟE^v2XY`!ٵ Xd*E,*(țZ"EvMYțv`?"`f?! Eks7YȮ E|a70Ƹ44em>,Xdt`7_Z E3 Y E* B,kUP`]f߲,"oZk!5e țv`?"A7YO"//(t`} B,k*(t`7_/h E gE,MȾV!5nE|XȮ07Y?Cyw'Qwmڟ!=4"O9i=ln'Ȯ)k \Sƹ!=k>7p`zֲ_ ,kʚ \Sng"/Ϛ ܧMѦeO`]Sl6 YYn>eֳ 5eͻkSv6v$~abTV/J 3nq[_;Mf*$|qnBy{|o>eͻ,ٵȮUfy,ȇֳv`Ȯ)4Z-,`]SA}n : V"o/ ܧuB  Xdה5) YY)5Z_k`욲'E)k`,oܧIѡe{,kʚ \SƓ߲,*k"ZZ"/`]ZemfW"oϚOݧA7YC~6wE^"XZ,k ȇ 1€n,Tyo Xdה5"po,5= fy,'"* ByZXd*E^!-k" ,򡵵 Xdה58)½4{`_-<)y {E^o,p7yS֠|zn`qu't!EvMA7X,kZk fy,5"* ByZXd*E^!-k" ,򡵵 Xdה5"Z E^"`fW"o/(j'E!EvMn,  XdTP8)Y s`Zt>) Y,k*( E>|I XdTP, Yo}ȇֲ Xdת"VkmfW"o*y Xdה5{_/h7y4em!EvMA7"Ȯ n0+d? EkUй,5ދEvMn0+dXdUh"ZZ"/`]ZE^!=k Ek By욲ދ|zAn,558)ߋ"o/(XdV!EvM"x/ ^,k*(hfW"o/(LEkU7E!EvM)2𵂆,ٵ Xd*k3By[AEv_5`g-M XdLgy,XdV!EvMk ^ N XdTP"E^P"ת d5"^pn,53By{Aan,_sE^"Vٵ mfW"o|hm,5e "Z87y욲sE^!07ٯZOBy 'E"^X,k*(, Y"`Zt>)zcEvMYn0+dg ~6wE^"VȮUh3By[րEv_XCkOh"/`]S֠|zha7y욲`fW"o/(t`Z,5ȇ4"/`]SA, Yn,_ dȮ07s`Ȯ07Y'd,k??8,k?3kO"oYdXa8|h=5eͻk87xBy{}ʚwCYȮ)k~R䚲6v'dgSzֲn욲sה YYn>eֳ="y7pMYO"oϚ ܧy78E~Ev,kE~By[րEv_e XCY Ȯ)4Z-<"5t|=pn,,k f Y~<7YXdTP|zAùȮ07Y'dNEkU(d`]SAX c\k)4 d`]ЀEvBY'd5`Wȇ Ȯ)k E>|=욲`fE^P"Zks7YXdTP"^а `fE^P"* B5ȇ4"?"`db@NZ}$>8 pZ)4cOխBh NNuUFWhvRBnpj=hnP ͺh CNV[^4f'ECAݪ+4;)MY78Z47[o~f4fsSE'Eu-lnBSEݠnտЬZ -c_L\gAn0Q 'Vh/SVYszn,ri {vg도n,2TinPxbite,2i\xbe itZ_ka7pH"OYQ 'V֠A7xY#8 >em:)Z2 E_eYSkYX LF,2hEn`to,p:\i*( Fa7"G^s| 2nߋ >e ^䲇YoYn,ym ½u{)k Fa70Xk E 2y}p^s md룰O\Ys*ȧZ"׭A7pn,ri FI1*(tg6 2nsTP"n=Y=B7Y䲇cNE6 2nsTP"vg LSAan0C""gW9|j-k\"Ӫ"VY(<)2Ƹt|Ru+"кOn^ GB7"OYA"׭A7"pn,rS]C u{"O cV LSA8|zn,rizB,8 YSkYXtӪ"Vkmd룰c\446wE[A70MA7pY=tӴhdcoE : 2n'Eݧ vg도n`B>em\n`46 2nտѵ֠8 >6e,,2h5gAF> O'9 Z5gO=o,p{wn)2MoFI19 >td,r ȦiY vg LSAOYA"׭A70M{E[ot5"ONP" YZ70XYn,2d,,2h֜EBY(W֜EPgO4cV070MYȧvg LShd 1VAao,2擢E[Ii*(9|zAÓ"gpRd {E.{70Xn,2Tйd,r |L"="G; >em>8 Z="V,r}vk9 ZYSkOh"׭A7pn,ritE.{ 1VA8 >d,r i*(tgO_/h EC70Mn0e1*(tg cV LSAao, u{`fEsn˵Aά]!=4ܧМE>Z67EvMYn6 u+|nBO7>޷ԻTPZ/hvRt욲5em<)$k~RqU5eͻe5ewsC[;ɚw Xd u XdהͯмZ -d/`]ЀEvEByۗ|h=ka75sCe{ XdTPs7Y Xd \Sh YkSA}nph5e)kzc\`=Z} m\![^Yk m"_o,p5"{A70>Y'EW"oXCkA"_"Vٵ"_!=k>Ev`fEOIѡf'EȮ ȧ EGߋ|zha753|,'ESh7Y+dXdV_s7Y Xd@70M d/`]ЀEvBY+dXdZȮ)kpR,롅{`/`]S֠, 'E"Zk d/`]SA擢EEvMXZxR,EXC OEEvM,OI"_!-4kZ=3|,`n`qe XCkk-d/`]S֠|zha75e "_! EknȮ ȧ4| {EBy{Aao,_'E!| 'E^  EEvMn0W"o+(ЀE>EEv ,kf YY"`Z6OC5e "^X Xd״`|IȮ07Y+d"*<7Y XdTP{O_+h"_"VZemfEVP`Wk XCY S,,k 3|,XdVSȮiX NEEvM, 꼁 f YI~ڼ7Yߋ\n, "׭7E"O"_! ,kEBy{h070,/EEvMYȇ EEvMY"_!07ٯZ!| s`_/h875"_! EkUй,,k*(|zAÓ"`//r{/k,,k5`],XCY "_"`o^롅X XdtEBy{Aj d/`]cX v`/`]SA, XdVA"_"B7X XdTP,Եk?өYCy{h7pBszhY7EvMYk87Cy{}ʚwCY75eO\Snp=k>7p'Eֳuދ욲sהwZ67EvM/L]Shc7ठ[/Lg-7Ȯ)k7pMYy;d=^dUW,Cn`ܱBn^k}ッ, ,k C7ȮZ5t!7pSCY75e)kߋ=k>7p{CY 75SCe EvMk*87Cy{A7p{CkY Yx"V|h=_Ev ,kUЙECy[AS֠,CٯUn5"n, ,k*( "{q "!=4?)rBn0w"o "z n7Ȯ1ȇ=!|Z,kECy[AEv_5`g-<)"РzhXXd, {`Z(do`]c{`_/hxR, EvMn0ߋ EkUv""I"!=k7"|R7ٵz EvBY;de ދ*(ȇ֞АEEvMYȇv`o`]S֠, XdVkm!| X v`o`]SA, XdVA""Iȇ4| 'E3|, XCkY YXdת"VkmfE5ܧ`fE*kI } XXdה5,߳Sd`Zem, ,kz ` ,Oh87"I"!pR,_'Eo,p8op욲`fE5`W|hm, ,kZtfE5" mMQ"" XXdtECy{Aתs7YXdTP"^а| `fE^P"* B5"VАEEvBٵ"!"ȇֳ EEvM`fE5ת do`]Z)2ȇ4"| SECy{Aa EkU!|{]SA)(do`]ShpRE>|}'E""{ECyZ}3|," ݵ!|Z5`]Sh""`fE^P*k do`]SAan,k-| sECy{Aan,_ do`]SA|zAn, ,k*(tw"o+qǕ5`],Cn`qe XCk ߋ|욲Sd`_-"|욲`fE^P"Zks7YXdTP"^а| `fE^P"* B5ȇ4""B7YW"oYP~XOvfCn>١вȮ)k \Sƽ+dgOܧy78ln5eO\Sn YY5?):EvMYkxRzc>t{ּOYexk azh=EvMk* W"o+(|h-k!2 EvMA7YW"oϚOݧ|nph=k,kրEv"By{|o>eͧȇֳ ^"5em"By{|n>eֳM_"5em"ot`Z6N_!=4 ܧZAC,kUPUh"Z-c_"7Bn0ȯEVP`ݧР,+d{`z fCn,_ -XȮ7YW"ot`Zem,xizB|G70XA7} B,k E>|-k!ٵ Xd*k3 Yo'XCY OE~Bn0ȯ78^y}&UPk*W"o/(t`Z6wE~욲{`__kI/`]Z"By{AXdV!5)4>}ǯo~寿O>K_7}}/z_~_~ݗ_-?PK!Cxl/worksheets/sheet2.xmlMs0`ȗ=; 3N;$ iQu <]iYoyfYUhlH:-s4&.q 67?Ym~[|2{kc1CQ]ӔS˪zKVeë+*GJ+iV>s>$5:j먤RٔtRRI̓F}UU%x˾6-j#cۍ^W7 Ϗo;bpQ1Ar77ǗBtPK!;m2KB#xl/worksheets/_rels/sheet3.xml.rels0ECx{օ CS7"Ubۗ{ep6<f,Ժch{-A8 -Iy0Ьjm_/N,}W:=RY}H9EbAwk}m PK!/xj^Vxl/worksheets/sheet12.xmlj0=$Mcl%KhR޵+iTI}m`ApB£|@̓;~ųguڳE2ƌܔnp< 0h UN"e$t?Uӂ&HTp 609!۴ {өucCbvxUbL皘~xЊpVbUB[ )z[n|85ZXy aFE/}.)Gz>'MbeF95طX,Nr-Xn⽷ҤٞyItd,GGͨQ_G(E3B .uYwU6OMf3o6aYk {= ס P !6|r.νP38;YnuN2MOu0߬F=[PwoŤm Vy+nF:_*BEJ㮀k5-p= !~P9ge>S J!@K&NV-.+LD9ª'4=B)a d 'V>6[XvkCQ}ksv\?Q[5.+ٗ%@=V>ly /i؇`ҢlRV^F yYNׅ,}Jƞgr^.ņ)JF2fJDiDd7FyQX_U}lQ,@H+ͪkpV(;mt:PάA ~->H j0b2‰E[A;M}7E)h>?5_1ʽCO ]s"D0&LԱ_ s +inз:xzM|Wy QQ⬼$:Q ilk5Erqlˢő)zvANVF3bz_e@?,,~yOw5iz-g=bZꚸoPK!Zl>| xl/worksheets/sheet5.xmlYGlXI%r;; V9x=-7kno:\~7>=/y'uOo~7/O_?qo}ӥc ?xO~ʕ}rݫOW>ͫoWN^yݫ/I߽yK%?QۻIu^}~?V񗷿?+|﷿?/Q#Uݱ+_n4ٕcM?c>o~r}8\ӷoo;?wsrrw?o_ ???^+ϧrå}zx_tDXMo>>-kz}woG?ٵ˗x_|'7Nouo/o>~~ݳ/QW+՜TEVt]wK|?5>qбc٩ʱ;>trWq׵|0)Ly1)_Lj7S)m)o)lʟ)n_򗦼w"$Bcwa;z^zcw;z`cwa;{bH9 88G  ",\$ H88GH#q]H88GH#qp$Žё8:GGH'G牣ё8:GGH#qt8:O#qt$ё8:Gg]:'6#qr$Nɑ89'GH#qr$Nɑ89'GH#qr$NybqXőXybq$Gbq$Gbq$Gbxq#8#8#8#8#8#8#8gGHE"VdH6ّ8D!)8gGH'"t$"W8gG,sx yD"6#qv$Ύő8w^#q'6牋#qq;ő8牋#qq8G<8$^9 Gq'5)Mdʋ)MxWS=1mLmS~'alwϔ7LCSȔ?6OLS3nLsS"KSN Dtv `GAQz;((>  )@ Bd!QHAR#qp$" 88gH "5DHA$RD"HA$RD"H88燃39 H "ё8D4>D "#qts&Q$ Hy"@. \*KBP. ]$yI]dz$}I%!LB@0 xI&!(LB`0 bD CD"TUȪD"xWsLD"ȜU$rk o7H6HLU 1 AbjC~!!2#R%r $Ab"HLBxtAb$I٘k:&d )hJ GV$b7DHHd"wHE"Q$} IAFZ2ҊWD6.)DH5DHN";DvP$nm8V[Nn}8V["NA $HLB 1 "my"y"y"E$")D "#D^!)D "HQD"^"Oey"h/DfD>y"̲8O,DvPaEȞ\n2Q,OD<O "=E"Vjv;YD^u"\t:j֎ XX۱ڱ0t;9H$r3Dg7;"3Cőhǂ'h yv,I$zAb$&|^pz Gy尸ɔS>G97ݵx3i`;Qv)`|li_Ϣ3|ecJ!} wB0tI꒰n]Jf$y@/ ^`/nt?:l&!?$Iy7}`H#$R$DHdp))D "Hċ9DÃH5DH58G 9qS$ 4HLQgp1C"?d$_ AbD cxm$p4&fnAb"HLGM`w S$64pC32;)|E"HdU"k^[;DvP$ Mx 1#O$I$RЂV)5KIkB^$HSOI$p4b?D"थ!,|xH!-yZ@;8\AOAb$sy" 0JHg(AbLМ'SD\qXDޮ"yWv)E$*:O,<<y"[)DC~)!D D4>яYOd^ɳ#qv$A"W['Z'/Gnp::cu"(cav Ў%vy27`v,l\$qȪD"D$rHD"E{g$vx y+MRRIN\VSWL*)޵cD6 y" Z5;3:$:OZ'v1։ʝlZ'©wٸNxxb'NdU< OdD<W):'1HLf 1 AbjC$у#$p4 G[QQ`(g8jʋ)T-QE͔咽C㍹mz{)`yx!G9އ'Qi-g,ϣ"_F9ĥ.KIdRԥtm>.A^*K{_JtE/y@0 `Q) ~}pڏas8WHd* "HLyPH+88y}@E"J$2mrd煃̐rHd"H$r!>IKYy$&A00MÌĔ) s8ZhxPc $&!HLЄDS32DS2Q$ +D"KFypB$ ‘8D&"HD"  %!3PȨ3<0L1;N$b'DC$ROXU 1 Ab$INAb'{Μ5HLm 1]$3HdK 1 DP'+"D HdD= Fybq6*ybqX)$2)D>(yb GIv,h|'R'2'2>Ui$0H謭2wYDu"&YDfZ'qyv,9Hk֎c$r?)5Վγ['"=֙mhvF?A&UȞ<y&qW։0MDV=;]mmob.m%1g*$v,c$v<*e$v;XINVsjZIQg%/ХV;?Jb$v<*9H$v<+{%1 ^INU3<]$RO$p]3C'ROd쵇'r}'+Nd?:q:1n ։ N)6:Ud/~pH̽h ONDUCs8qE G yHlWo GopQ(ɖ)/|6.Q)M;S~7V3MS(ǫD)xxbz"vE"{.yW"WL"H  Ǥ%! )hQH!HL,DF 4HL? 1 "w$1HLB 1%Ab$DLESQOAbr4 199$E~y"4Gbq$mPn(G OLhx" 8O,A{'RpsqX\)~P'(OLYeXjBA; ڱPЎOp:I։Y$2sJ$bfcNdUڱP:ov,v,XڱAbGѲڱpca+ٸgg.Aμ+ 4;#qq$."HH ߉DޮHdg'bwnE;6.OdD y"Ny"]HA$r5;։['.nDzu։['nDzuu"?D "Hd))D "HdՑcU$qsxWU8`w':M:Njvivfpu" *Jbw̭{{Jbw$ vWOV2+3 b%VA<*9v[ lV .ЊaR+gJa¶Rإ+ݽBȌ| vWӗ{0wOYUam^0TI"Sp1ia np][!L{ 1Ţs7Lɤ[!Z!]nHAnH!0Y!δ̝\r 1Ţq<61/oF́!Hߌ+DRr@ )&|~[,zZcQG9DzfMfLms?wLwM=S~?ʱ yPs>rLz(zxO,<7/LKSN> Rj8EU`R8%!KA^e{)  R_+@zSz1![~9 JR,^>HLe6 1Ǣ!HLmI)5$".S,UX4)uUD!)D$)D "HD" 1E}Ab'I $)D ".)DA& )5G-HLd%1}Un*+)JbӚF/EF9_p95 "ɧHd)S$m A6ȱRRD"{ 1G9uBGLBBpI,5jy0)$^2 Mnjv51HLw$T2HLItz $&A$vE"I:(E"Q$R&HA$I$ H8D^HD"d)&3D "iH YH )DC$RD"J$Rp$N"0D "mDE$RpX'-"QH"WD "'2JD"E$ WDXxX@HA$*U_D"HA&ڰH4E͂DőY;$T;^ Dccau"NdA"עsu":։NhȀӭg""fY$rE"ې'R'DH\D"zD@D DQ<X:q'y,牋ZyU8l\J3Jݎe ڶJb++^+KJbwNU[j$vOWV/VO_'VӧQfK^1ZA_*Uvr؝2fv)vw+9٭ĻU W .}\!*)J S]= N^wH!?sky~פ8OLj+Y!򐂖dq܁.+Bޮ0eOh)5ڻwVϰ}LԜn/Şץ{ ax 2A"ʰ̹]\e eSw`CcDfFU] 4a9m t3l0 1!t3&䜇FCLѸv~S$ƵI>GA$+C~O'P c|u' (Pccѵ|4Ncw`䣼1W5ڙ1J2:5FyMcec!hcJc4e)hJau |jRiQr=YF(Oc4Fyo4FY[cJcJcJiRiRi>J2Z^1k7h>ZlGs=mJi2mb>-єj81RZ{M[^֣)5xK2Q)9um3(֣)V ֶgb ֣"zw֣)?m=JGy֣k2:l۞9]G9֍Q#(miK31k$q4F6Ma[-G 7er|JQ*Gَ3-vϴ=bLetis=ǭ1 ڮG]\T\ع~sj63ms=sVez抍Qc]l(BTQ8{0'|w FyZ>n߸Q~ۺӳb4m0-MYd @c9cD;~mh25=ŨmAhw 4v71lAh7]JXЎ?TNJp3`30lQ7[v 2S K12EB3Twc7Frܳc lOEwgê翬*%eZˤ*r7=߭{=7g'ߜ]3|R)I{sfA3|ea]Zz4fUz][Rcx0mQưm=JG4(iQ*m=Cж؝ܶ<ʊэQ޵]x 1ʞ=Cj1Rz 1,s31ZocxwX1J2-QFэQFэQmgemm`5QiRi(((۱x>6sܚ2p{cxl|3-vt s C)m5mglzQ1<6Oam>PlGXFW;6F6F6F4Fö'A;{»ڃcx\:GyG9Qr FzW*`܂ы/D/ќ7 @􂯇kw/71j=wFU!*y'Q{ns{Sޙ9:=۹=y'y'5(ygt)w]yAt;/5)ƺc}b`DqCbI m7F'.:6؅w{㸂]㸂 ]A/vnㄻ!}w'ӽ5q8e<e@,MqsccC1:6O;E>i,Sc|4эQ.1J1c7nQe;Q[c5>׍Q^qC((kkRiRi8 )QƳepel"Fӯ ѤѤ"FS@,F/e;mri>JG(v[2ѹG϶hc (" ܱbFM=֣ VQxcQ>(e@lOٙ #K\ еW@)3'~&TE'(ߙTw\J#'3dWoT;ٌMIu5Me3JՌ6Lg;3(3:tQ61VdrYIZ6^xFt\e:NJ܏8Ō 64:(Q3:f](zYǁY uV(6ø54a(-w!t=]bUqq{Z." υn,zo@C 9ŅМ9Ph"KEp ̷`YU54T˷ qLuQ9ε.(6^GT7XfTC>Lh#aWqtK k;'ym=7Uq*ys79ys6qh>}X^G^ۇkKa>#a,۸fߑ43߶1=oZ✄KI%) > J:6. ՗4_-u= ?LcJ]NŸԕl".* m\Fi1d#ILRi(8MP 8'84k8pC ۸b::$!6Nm 8ٙQdEƩ&MC(۸zbF8iR14z(ftP9m\Cs0z(ۘQ*fJ3:ft6^ި8NJ3:4ft(qft? 89UWb8oFٌؚѡDF}dTJ(UW*m͌rF#ΣcvzQcFQfmzߨ8s*ytcFiOQnc#36ft53J3g3蘃ftfFm\QS18fbFQ*f(\3JŌN8kU'Ō263mx;<*8<::)fO*S36q?Ƶz!+8)f5ø~ iGiB{?JL4(-SGx?J3Q*ޏR/6.Wڌ6Iq_O^y=f]0T(3JsL6+bF9N|gm\M|gm\mhQ*f3*۸zbFi͚Q*f֬w&Fw&*~hQ*fbFDFUq\sM{ڌ"69󨎫Nm~TǩelfBU찍bFmUq({34,(qٖmrlDFe6fϩNm贍 {f8ƁDƫ0btal{Q+۸&۸<۪8^UZʣ 1t^Ӟ.Fo[ZBt*(-B[;\|Ӳ e\d<.< \t<ʟsʟ.6_3Bs|!ǘoX\tVR&_qɋTquVu tgƒ eo:9^یqDw֩şWboq ͸Nt3uVusbS]%*:7 +r(_}p8XUp^އ s\S}9wNIktS&t_1%s\--W!8'ᒄknIxJ»$O‡|L§$|Nc|I$o$H]L¯$nIMTpRl8ǩclj@>6Om$L%B+m,GopgQdViFq 8f<99Iq)HrbD(bF;a=tl9通Q*`(ۘQ*p@ 8EmF]Q*fqp6qRѦ^o38|ftb݌ǰJ3:D3ݛQ*G7 z%6#ft9waoF9N3gAq Dq)t(S9ՆJdt<Oŋ<T(gnjR1;mFs8YI9NmQƦԦS(7fJ<5CiF4L>t3:DF9NwN8Nd`Ffwy>t\Н5T(3JŌ23ōyTqk9!9wNqjcFs"CǩṂrSlfEY)GaޏrGx?޼q]tttߙNޏ7Gl'L79ՆJ|gs\s;Ɍm6t(͌79QcFQ_ySqJ:.96F(3JOٌߙs\L8)Σl(3:4ft:ǁ9ǥLR/tɽ )8՛'O'QTu\PQu\mx:yA/q*Qu\~~z Fqj6~QJ_ sO &0u u! OwIxIZ|IDf['G~&W w$Aq5RXAl1+1]49;Ԧ-i(Gr(P 8u6f9ƓCl@wأqqJ;6;.h3:ft(fQfm(3JŌR1T(N oFtJ3:ft(P xCiFYt(P:/P ︢yt8p49ؐ)CU.;NWw+jf(=f4;^9(qytS3Jҳ7Q*^;Wxgo^xSU*>^ǧcvzE~8\jKK܋FqjӋ\m,_zR_j\ [o-7$J:8V$쑄ջ$O‡tSOI/i- |K$HcL-~0lI-T{\ eH?q9vJ#9[҆ri*X:bsi0Gr9zk4b6y 'Ix 6?;7㤘qtju)?.eǥ;5t|(ۘQ12j3(3ؚ6CiFbF9elfm(3JŌR1TD8R1ft?^KA>wG7[dTqOݨ=N4M3:8<:ƌݖ>2SSȨ>vژQFmFDF0;.8bFW*ΣT(xۛQF`Fy=Qᙓ{3ʻ`Fefm(Q(rCԪ>N({sNWjFub=Q֌n!_=NJdTqeV7qnjztCrC{8N8D-8)f==NJ̣rSȨSq?:.=NQ΁(=bQ:fZ{873JŌRQvrKGߙNΣ:(ft7CiF754M3:iFҌJ|g:cF7#ft(P(c3N8\KqS}8mU܌UqMy^(cZygQ1(}rQG<ޜG(3J%t6 Q!=N(b{8zfm{ʤ;r(zđQ\FU'%2*$)ʆ{|WfrWe +޹bts(x%K}xm3>[. Ou=Zފ3]h\Y[. 1 }I-'G ?S_I?i 9ȁ89R}<\::f:TAN $6TL%̣ds+5TL&dQN*Ɠb@A$T鞚Q&9S9)Q̨Ό239ؙQf9mR1tB(3JՌR1͌z( 23͌R1T(3JŌ2j3JŌRiF7W6R8)FqjcF\S1.(Ȩmt ZW(ۘQcFDF "{35Tt+ 'yt:Ն(Q*ΣTG1ׇG  pANm(g'QGW(iFKیn8nANWjFy(c3T"?NT92Ȩ0r8)Q9!rnjbFDF Z/91?ǘGUx;:"+~Trx~t~uR~TǩM\q?Q\79ȥ3|7A^{>d3 W-ū8iF8P 9]O3 6h_O}^i bԛrrR{FWf?wAs 9)sF 6p#?N=wߛ }viݚW؇BtyUi! e!¨^^s(79t(C9tB^gu `d!'{QދR^{Q*ދ7Ex/J{Q+[oB~ZjpH±擄s.iknIxJ»4$|H]}L->Wϩŗ7k 3ZD[@ZL¯$NcIB(B.>/ 9iaЧ[9 pM䴐+b(4m,b.[cwT\Qchrh6b:0 䊀}4kFq=+j<*BNeΌbd!;356ٛbFQ*fݙQ1T(3J%2";3q(3JŌҌ5T(ft膅Q3:-䢗bFQ*f&e3JŌR#::j"&2#ʹ]0R3JŌ4BYḄGf# 9& 9\,>z3X5T贐S"䤘Q#0y,41BNm"ݎKSȨ,ų- `* ڀftZk_({3vҌnX)&::_uu_e!W< So(6 fwr({kF]0,5Ta6æ<:3 9̨>ftjJM*BBOy(-W3ʂȨNĵ^@OGX\% 9)f3Ur:$ 9D"p=S%_oO::Ǯڌ}N/J3:_ #(F~Z΂btE +Ƶ]^ɺbtfpmFQqs3ykF1kV^fNy3:idڌZ.cvѡ܊ߊQ}+F~+FWz+Fߊa$VNyn>>m>5[9 s|9wk>Yk>_3f#^{Q*^ܼeE{u~ZȽSu]yuSaz/\ hEٛý,mZ)6{Q*CQB~X?aC-/ $\pM­$K@هcS>Z{ _H-~IYE.DrR #ۘFƑ)do&8,|4lc0L*MU!WlRÉGtRi<lj[+6cDQVQfm(Ό\6(#;bF9el]dTr 2* 063J%OYȩ73Jsٌr3JŌ"WBh4* 9ٛi!6fm(3JFq3a!WlBoFqfbFQܹbFi<* 9QU!'ŌN 9\.o׸64M膅CNr'K"tyE eV rQ{p5&tn76 ݨA.v9M԰ nBٛ ՄM'A5uNRxQ~5QrD z ZoELJ!CQ Q\?}F?GP^frs!NrM[:^2? {E~]rQSoqXB<oE-2?\ZoE ANy+z2?oE[oE竃L{!$Z@8✄KI%. SW1 9  *LZ e1 J՟$AANaĬ< WoT#IDRH>F& NrX2sI`R1T&WIgpbѐ\C{T~Dŀ2JňR1ANҌzvfErk*fQ*fbF+6fJLrSoQ9Ն.ήbFvfJLrCUrr"9)fkF鑚  9́αGr)k9I1lcFQ*fJdTEȁ^9ȥژQfJdTXy3R3JŌBQr!9֫9{O9Նnc\fbFQ*qfԌ!2zy=DFf(Ȩ4fQ(r;r8rbF,ANDF 6fw[EȩM\uuAN&GUb3޲\1Guu3{*9̛R[~7Aަ36fe3J7،Nbr:͟(=y3q;)3 yz7rjӌn!hӌJ3:3JWy(ٌ73J%2z9(&9݅ft[dlF9;Qc,9Iq弙 +A\ANJdTEz ;w=9EP.oAUy6$α6t- K/UT хşrmFo!/5/}ΰ^rWnXJ5>`*Zt.kѹe Tk9rN"*@.F:UEt|J'XWltNaNks?r\ esu+8<tDЀc`.B6Ǖ"pKe 5mFlSD[gT<=o'Þ-:w:9H9;ucN:M8Q't;$*~Mb^1o:~igWՆtg;t9N[ЍHq iy! $pnnI%) IXio- o{IICqs\ }+TqT_@Ny8H}9f$b(ΚJ1\R1TL&1tZ&]orym'1TLtC{4t@(3J׌"6}"qJft:ǩ73q(3qtSfQs2.ϝ f>\73JŌ"9fqnjҷ5t(]`3JŌ7Q*f9R1T(3JŌRqbFy=f弙Q*fy_GFU{xs\ 4::E`FQzf>gFQ*qs\6fJ~ ȩUqjz9!'8)19Nm"r =5ls"Q_9NJdTq7'Ō5T(3:AdTq/ F'%G6q?*8fF(gnj덌 ȉ12*8ݟ$8s\ 99ǥSyTՆm܏ f:IqUqdFbFyf.p|s>QfF.p3Q{ƶQ{ft897ÙlF77:fbFQ*Iǁ^9)j3JՌn9NQfFQcFQ*ѳeΣy(^veΣPs.YsR29+K=^9rKal,p!:$\.9b߅׼֤L{C(8ׅ!*?`"G1^/X0g_ix ;9ƫ0 p{e|qAlX?xUƹԆ3r•kC9>l\PBrruQ\X13Z.07?] v̅2w pn8v6t Nֵ*ޞ9&=f?).N l0[9*.w¤^de:zi߅1xcL%k qa9V\C'tFqw/LgTmx|UybΨ$oT N^̩b>>܋pi{1+'7ƩMo8c_>1g}I8&ᔄs.I6 OIxII/IZ|oޏկ՟ԕ|@|R]Fo H:;Ci>8t5׼#90'"0'NeQ}8P38fs"ȨΨNDFwfKS̞kdbĥХu>qJ9t)gQfFQ:zΣ!+㤘QfFQ*f>ۨdnFbFOf'.{3(Ȩ*S^y2/89'K>q͌N8 RO\bFCQcFݧ*Sf\gT6ΣҸOf426fwΌR8 @*q3*8]OdT>qM>qMƩz̎|p=0NQg8fN{ziȨ>sĵ^gTW:f-GdI[˪\ 3^|AG7U|B537;jFŌA611T"Y/b7|'#PqXa.1'%^w|t=ы(Ō2ƌR3JٌnFw*IiFGkPѝ 8)NqbFOb(F3J%~3p\^>qTaȨ*]r?9](UqẠ0NsN1Σtw5~'#ZΣtoOS?(Ko1ss1z8}}E{H-x^K{jl8qjo5_ Q']d S:8rl]XYr>Fr1y`>IVw\L#G!q>>&S>'KkI-5Yt3[gF*nyd?䨍$c$C9᚝y3ydim4;lkl;p)xTqR`åL8)f1f?F?b(fFQ*f3'NfNEPqzh9}Nb:Nb?Zspqp1*ft)ƌ҃5T(VN;P3~(fFcF?\#PdTpQ3:tf+5hdTuifQOŌҝubF?za3(SkSp DuI/#N1TpXm'Ō덌jK='8)~eL|Uq)2*8&8\Y{[p9<!0Nc3tM(f3J%2:4Wq{ӌRKځ:=7ҘG6dnj8P%8@p'%2*8%"SkfYLuiԑыj_"S?Qu)t(޼/fbFQ_7khMpR(?3JŌR1AfQUG\ØGՖ?ȨvT?cFCQ*fsmFqW*8SE?ځ:ܧS?}2⭱WtlA9<.(rFV.$(Bra8c[Q9.,ǖ˅8渰*M5*ߩsh?F/;7ῖ?Yx|cN- pIM][IxNo6 >u! ) % _[@V{~$g&t3"Ńa/h*H*&6T$CIT҇61b2?gNJ9bΝԚ **FJ3QcSp)Q>W }f،5CiFҌŌbvtBqOU?\bFAcCpzh9}"w~b8rA853JŌgר<:ኙpRGӌzft(ΣtgGf:rΣT+pRG?9FF i"N#Oy3GpR(=rQ*fT{N(#<uBqebFjQ*f(QΎר2Q*^j3J3~׫~8-N啚QΨ if,7å>>a^1,R(.Dw -.xXG'xȅذ\KxQ_z #GF2Z4S#p3^KKsX[kxbqt\,PI5\Bg3$EpM ΐ6^4 om-boW?|Jt{X)h{öwk8;7ΐ5)_[IxN$I߻ !4Z@fR$|M}|K$H$.5Hid8q(Pȡ4Ci&b(iW6#Js9s(MP͡4c 5t.EsZEȴbD5bd B1TZ:I)s&foZ:i9oJ(gNڼNqSCpxN-O'[NTAٚS(3:bF5f#8QftZ,^ʎ5f*NQNkZQ΁iFA.=Q 2#]"k8)fG?cFY?@NekΣTGwO;QfFQ.;R1T(3:t=fp7Nnҥp1t8]nFi ~T:3J{όbvd ّ5fGpXYKa=7CiFe 4#ƌJ( $k8'%QY)y+gF9j3+5T([sdTpYad d 'ŌȌN#_Z:Y*Nc3#.{JÝ%k8]O|k3k3+5T([yTpR3~(5TIyTp0W3UQfp1fJfRp~7JS(9t8cFZf5mm-])-Guq[3:]5#eI5)T:\ x5Z1zuY Нm\s\ts&[tVd Q! ބ?AJ7alX.*GsAT o}æ Q6+Ox ^ I׬, ^h[c xgx,yx0^ Lx(4e5c$WXz+ 4GeGSBwgI4;Npi6w4s4C1$ĀN'bDb'%2*'8p"8eN'8I1ΚT6qd3'W?H>b!CW P͌rf(3Jyg3J?ьҝ4G(3Ju*fbFαQ*ft:g'>W ڌ3~([3\ȨpgH8)ftfcF鿙Q*Q QpR(1tN(3J֌R1 MH,vrNpS5T"rSkfw+GlFQڌR1 jh@󨊄 Nŕ #'8)f<*'8fFhQ>g6w\~f٣~JW)\#cS<^0>&ᘄS O¥J- $<'u$m%}IOI$|M·$|O$LC+"Hivp3\Fr1vpi,GLs9nmQi4gap[9 c̗A;8ֈ5TѡDF#ΘOiԏ3"cͱӵ;9NבG3G'OZ1{>:}Ҧu\;A(c(-d3JY(yyhFħьҼsJ3oc3:?ٚTp;?ikNŌR1njR1\355`==QZfJ3: iѡQf֡c=QfF g3ʘGfs?ΣΑsd=;rޜGf=;2ƌ4(c(Q*ΣT"33J{یR^调bFc([3udDF/f6T(GŌfҌ{.2z54}Q>1^([3XkdjFq7^(>z54IGQ*1^cQ'k|QẠW31T(w3|}ujQԌ~wO S3:vM.FckbtZO聕O< l!:vyn:"toTN'J:[+BR\"@S=pԧ"tt:GP>oz1ʢtn(M[ɒ[9ɢs8gVd ͱmvn8jr4UT߽0 Q\D];e"ݚG# Fz۝,gJ4! ùe ǽ($iA a ^ r{3 BpTe gc^{g?8YΊ8vwn yW`=nY!1r#14rZqln9-=_-oZ‡d s K >%[7ſ-Is^QII%}>$c>%s$k%{~$gd cTs1%Fp=Eܱ$rخMPˡ4;pA9+J9fs4Ct>D<T5c1ftXfs%%VьΙê5x~Q*#ьR1l-2*K8NLΞ4d>iQZfJdTp=h0裳(i(k9Q4?N%>׏h1~ӝ֦T|UkFDFf+g<?䩘i w%YNpz닌#5ZpR"'3(3JŌ5T贄z bF%TpXSddF*oFQڮfֳyt#3-R*f'3ʕ3(3JЌNK8lFq=g3JՌ3:-po.eZ¥=Dpj͌rf\GFe DFe W ͌r~SqY[ScT%Ӽ-bx?HBrsrl^<5ʔ V0[HQ!$Rh_tA_Q/Uu@mmoF^$'x5A\Ni8L"pÈ-ǩE*toYLFz"h{?^=zr(WxMb?h- W#o/__pjM[@꼴kT$ܓr$I$K$|Ht9 _5 ߒ= ?3 2q;Fp) iZc~ɱbFpA!b|`>D2"fc84\u',H( M3J1ద2bF9Ιä61n(3:4bF9ȨjC>V[qcL1Nc3#24 GUslFE s}se3:iF wlFیilG9ftJ3:ZN=6Qε(m3Jڌ[iF(GЌrdT)2Ԛ kFZ/W)&2*#8P%#8ڌJ(-fOdbFQ*!Ni֜Gy=fsytpk3T6Eq-fZp qneQnFTkO* ކ5-:wLut͗XʧBs[d5o}${*.΅ʱvAJQ[LU ^a!9}BraM ȱuv8H. lKJ#,\8Fܸpw% xZ 1 9 _5 ߒ= ?3  01*Ǩ4q(Z9bȡܺ5$$D51T%ȥ4בL)lrD”[Pi>w ڌb:Ĩ8@o7IjoU!p1(^9U\Q1T"*N3~USkQ)98R1T(GՖF-w);o)xMJ3:`ftMnFħ4ofp4T1j3: ӌFFft(Σ\ft[3:ft(ToR(F-7DFOѓe?dFO"Q4nF˛poM=Q|joj 9^oR([3T(1ty(]^3ʹv,7d6t(yB43VkcU!p6ߐɵ7t73뉌8cF963J%2z61T"g33qWhFz1tE(Ȩ ;3(3Jyż8x1lybFQ*1jo41-e)ƌN7fFӌv ҌW :fF6;ft8̛N,7 bF&Q΁"W3J/׌rlGnj6Σ4'U\++5-çʣM3JZw׵T)l(wxT &Rbs(M.2P`pVU 6\^gE΅B9N(.Aąq\0F j8FVfexK㨙-/[0ji-q*]ha;wGr8Nxw=ƑB8ʱ GX 1#w3WZ\\Y4pຼa{j֕g=8J m{bjߋA=Yk}o[ o G?5ZSGSv7vg.7z(Txߥ O<ߏgޮw?149+.e-/"n956:vWo`:S7?e-$pI}\[IxN$Iһ$O‡$|liS>'Kο%{~$g.vߤ4#[kwOV FrZk*GLc9ȥtL9)lN7]So|Po1ţ4T:_Ȩ0jmΙȨԚoDoR(c(35#MWUoR(fFQ*G3ʘG93JӌR1T(3JOvf1ΣhF1o:4;1M1]('>UyTGtOzmb" 1r~cFߊ*M<*7fFُ7P~bFٚgxPѡ4nFGL3:$c>%s$koIIEz0T"9fٚy3@2DV}0TF͘rM1撣6TL&IlrԆbQov'~ӌQfFyfekfbFQ*fFFcIy~SkQUoJ>BQ[12*7]jkt=QGe1x]1ò6zF-7)fwM11j2S?ff]kFwPyToKTUo3+Hd _FFe9 12K* p=2Ҍ܌ՌrlYď$֨[ >*7P%7P4f?aƯ%13J[Ќc3:f +w14$(ML3JŌRiF>9M1ы?Z3:֌J3:3:ߺ*Xm3TV?3JŌR1PdV?(c(MftMQrfƧbF8R1T(3JՌR1T(3Jy3jF9fe?fs`FIҌɰE]27ƶą0Yб'r%QT|“&mT]ʸsw 3K}"F)t9|՜yuFUTQvq!9"18ܸx=P8PVjM0' U[뎻K cxG$*M9I1l͌R1T"r(=[3JŌRqpbFQ*fcFDFyN1Q9)&2bTȨC~Ȩ43J'ӌR1T(Q*fh<*8[dTppMdFh70EfEFOQ99J'Ōҙ5T7?Ẓr+f:8ڌ9i#r*-3ڌoz3Jft:5xQo)\ftb 6alFwj ?+ߤQQ)8-29͛?>ftZcy^?9;mF963 vsJpRGُtem0ytfFQ䷫e Ռz(عFFs'2\QΎQ^OdT?؏FFK3ڌ8ۨ-Fv…pacs'VF6W{D1n0uqIKQ.(8ܦXPI,&w߭w^t`p 1E7`<ϔ{q,h<\h%,GjK:#U8w ,hdn>myyEvҕ y]1iTnP6U4HV /Y[$+i }N@ R .2Mye0tB?9S e9bC#w] ;o2"dFح eȨӌ׌yTi(Кp=*-eKXHیRf?]7ЮGgycթkiv"2DFUڌr}ѱ>f;p7dnFҌCs\ mftGQP%78bsAk-OS4=O+ٌRiF1mFiךQ*fQ*fE>[s@Ō\4iF-܌FifQ?5QoMx4klT(3Q*ftiv([3T(gԌRMӌsa 7/1s~_1:6r.DG l:-@W_O"xWSYM,v~^SLonrC(("ף֏}^, Gu]8X̧qDcb>?8ߖVpTan*4ph1M#jWÆ,GsHi+if8&J` w]/xdߝ9ػqp DZwF[?1N}ۿӅlB (dD!8N-yE}䛇Yr7[/n~^1[L. @ MR8AKuҝjgEv"sߥ.,w)c#i`{ZoFHg*]1Oo)?j&~NA8 ) $\JczAIxN$I$K$|H$|+ӴI{jG~>V'MJQaG~SC~SLC5;sM1b ~+O yI1cL(LjR1Px3JٌR1[dToQyI鼹Μ#ƌbsj͌2nj2'0XMebFQ*fŕ 3*7)fQ*filQy)y#ZdT[?Ȩ~N1fߐw&ŌNbQ2ƌRzya~h礘Q%~sbF~N+lFQ*Y]a3JԌ2&>s`GoR"~Ӛ:rlf~jQ#7 GslF1*]1ÏlF+O$o$HbrVf; |@j@T Gr=Iɥ,61S rt<Ȩ/kG~Sr_KZ5yvqё#iFs?h\_8rpF3RzQ?'U}pOX01Nx=&n rϴrcg).GM9"scgOW̤̳xf|fҀf҄f҈fҌF" T֗f4֌fҌkF&BѬis4YSiF΍_:ohnͤϱf4h3I3I3I3I3I3I3I3h&h&h&Ȩ^ WnjQuR 2R 2*K5=oTRM3yTfe-LI3n֗֌fM3/Y_JQu񲾔4A:~ ix?4yv^hFMڌfҌfҌfzY_:xde}<* 5^TR 2z֦]X$ig\%K[kFs}T/Bd})AFe}]fQY_gA=S?34Ge}ՖRҌLf4֌N WN=ӑ<>LS3ͭ5;#)FHё#)F-F7?pN-LI1@֗SI4œs?i &dϚ2y4G3y4~ޔI?pʤ8|}GN44鐛ѬiF6YӌfM3I1nWؑu# \ڌf/٥ kB 0C)qņ3Z~;Om2h enTN{s\Ai߃-4j1RC-30`.{x9±@!͹fp(T3*U| K4c1gC*sf0罌 I\j R<6\|hߵ ┾ 8lQ(B1*I_Q2(N} }w3^{0vP3aLڥ4]Nn)^ X ])ŧ/)z}5؇!xɯfmS8Pkr`M^\s6Vy6ұu$'^9ߕ]I@xt9ߕ>rkkh:_ˇsyDO)8PpTANB)PA")TAL$,_*R7񱒸=9>y QSNjvh5#0cf3d5MeL)4\c{Ә7CY,oߛ<^34Wo4}%1IM^>x/5yC_"o6zccë˦ݯo& ZjW#86c0pMœ$pv]9v^h4*kL eILjSÜknuΊ0.旔9mq3j Cp3lVs`E Xo C+ ᛘ op1 f䑘x>ˮg8nFq7maݺuK~'๾6;=pmL?mTG_ݲqg(8Rp`^V ]d^Bțw ~T74_T}+gӈe{|yy,7lkru!a{<҂rV\n^)27l/|s4yvLИq!O5uCQ^iFF׌fҌfҌfҌf>5YӌfҌͤͤͤ d{\gAdTR 2*K5Ȩl/4#{Ge{i?Ȩlk"TR 2* 5 Tӌ93zyTjQ^[3ckF3iF3iF3iF3iF3iF3AFe{^^Z33l/ p}d{ak416^yTΎlmF3iFoqIinWRrlF#tps4Ȩl/]9dc3:m/p K 2*K5}ԌoFFe{i? u)F ghi4ye{C74EbsF1/A܎!Lkc%Iע/_[2zŚ\[e5xq>E5zw8⦅я/Ồ0knL`+a=Hn 6#0u7˸5!51Cl]bPYf[*(i5㵹5$90wn Ⱞqcsof)W_S*O{aB9ׅsW$2ߠnFMPܐ02I_'q.Βn^_S/ܿp/IoJ)}_`G N } 6u}Cp?VŐwykMeo|GCzqwSu5FkbȒp&%}3RIܐtrҗVdnH_̳tfxNK#h@'_JI_Wҗ֌NK5h4q2yshZdTҗƆŗj>`J֚yJ Xb3<@C8n]pyτ1WwՌ3lw/6A nwM|ԟ붜A@w]b $NsṳL㻶6u~kM??i|Y75K(8Rp B+  > f 7|Bŏ64pOXS0n_/i|䨋aH Ɨj K5)27/m3^/|kx5 h¤IUN2Ԍ/6yjFskh&gO35Ȩ|j?!2TR 2E&hf4f4f4f4f4f4f4f4_MΛuO/̖24邑Q4yLLLLLLLQ9@FK5Ȩ|Q;8~K ΣjGK58͗jQ_8;Zԙdzk9G3Zԙ26_AFe|Q-L5h[dTm5Gk<6_`G:֚ѠJ:&r3 26_246dTƗjѼrȨ|ilȨ|iȨ/\m_ؚ/\K[yT 3͗ƆؐQ_'GOhVGKGڌԌNK4Yӌ.FGp1:\p3U/ZƗf4hS 7zIWou2`1#)7K9uM kdRL{,(GRT[XnH_[!}i63i83i:xf|fҀf҄f҈fҌF6_LLLLLj͚5GM35hf4dTk;U TMt<2>!ZۙƆ>!5I_JQε/%h~whF3AF%}i8jmgiFjkmgiFͤͤ!TJR 2*K5Ȩ/HRJR ΣT/ T͗jQI_AF&_AF%}]`RҌfM3 27ҨQɗjQI_Δ jdTҗjQI_AFȨ/m ZۙjC)ıhn Q JҨqU/ jdTmP#KI3,I_8^mTzI_LZۙU/ jQI_IیfҌNK[kFf4khԜLw/Mo444)"LLLLLLLLLLL+:{Ct-FQ}lѸmwN&4n>gPT:}>$Loos<1nSN{?ԏf0 ]?>MW7Fr)>^,|1[ej泦|=1(rPfov|ϓk7y5f`7v^iK~0 O C5`zB0ţ^&,0&(g}cg8MtƛMh5C4Fp7ܽ:9!O1o]5'SnPLM!㻮CMw%c-]5eBm+:RMq9nݖ7F߿4R/*_(8Sp_+H+۪)PA|>V'zQ*VoąNAO:_M#& q[IIm vv4NimT)*7/mXؙj̍6_\Ɨj&34"t8Bt$h:f4f4f417|<5f457/FTmpMKIMchdTo!Zؙj<;=fyCFe|}BFe|ak2 jf4mϣ44GhagiFͤͤͤͤͤ#EFe|dTƗjQ_f4ε/<*K5Ȩ/ 2TR 2*K5Ȩ/|e|)iFdTƗFR 2* 52 2TR 2*K5Ȩ/ j+'K[kF]5/%z?Q_yTƗjQ_AFe|FƗft_r2^ƗjA3 f#EFe|2TGe|Mڌf?O4A&4Tӌd|]I3Iϣ443WGްͤͤͤͤͤͤͤM4tvth&ѼI ;{lyIjd|)iF.6q޴3|~d|W2'܌6_ķK1:.FR3e|#؈1.4xfn*g*YX)4:wwØP{ b}Sn_]k!}R"H.Ϊ<f/{"%軂TM9ޚ~RCs._'o#)7G챈X5iBo5L1)*|W5cq9j ̑4Obsḽ54444444f4f4f4f4f44mFӌfM3 2._:oȨ/ rT>!OȨ/dTΗjQ9_Q/%Ȩ|aS/%tT˗F˗jQuR 2GFȨ^KAF|dTΗjQ9_AF|FΗdTΗjpG|dTΗjQ9_AF|uMf4ft:_2*KFF|9_JQ9_AF|dTΗjQ9_rrf48|]KI3[yT]4jG8ґ"rTB/%Ȩ|TjigAF3 ZڙjpTjig`GΗdTΗFΣf4-z3I3ףΤM׌f Q/%hͤͤͤͤͤͤͤͤͤͤͤͤͤMW.kLѠW]GT'9_o-c3M-Ϟ|ar+/yT/ςc+FsL5 kF]< P=؄ h]p= f^`cPe7w7_8:/ 0FT:1 Hf'QW7%}z6[;%}cd74q썞ښMX3cԼ&\󗍜~g`_/]6`͆Ma( jG+qQ>nx5lI0Us`ھMp ?].8 a1˓n!{/!wZwkqg'SwՌRXX5!&ZyM}q%~W2kZAwՌC=7@F[K%w)~WMz5[ R&`_/!8V0(8Ӧ.\+ȏ? ]U bU1/_*)+U|`_<>`?" _ػįԮJRC#9 QSDn_ mդ,,v}5s?pkf444|4Yӄf҈fҌlF3iF3iF3iF3iF3iF3iF3iF3isf4kLQ_J;SjygAF3 TjygAF3 j_JQ5R Σo8qU/U/ {dT◶JR 2*K5Ȩ/ TJR 2*K5Ȩ/HRJR ΣT/ TJR 2*K5Ȩ/ ◒ft_AF%~iȨ/ P#K 2*K5Ȩ/ TJR 2* WN◶֌Na-KI3:/ѬyT◶ꝾTꝾTJBK 2f_AFK5ȨwdT;TٗjQ54)WQ_%~iTӌfM3lLw}=ԇh&h&h&h&h&h&=#gd􌌞3x?z5;gGȨį6/%h'5ohڹf4dT;~Z3I1!~ֲCII~/h>Qиf4K˚ЍfU|61{i 1"sfwi~_DnoZy0c v~rC$SHn_2u>4~qе/?2omƦ<;c/RܘGXST1øsOef0[=gZsb>OiMa 09\˴opHs8m6cEacO"S8dQWOw}]x^6sn %\\7f 70CJ^G._]~pkѿ7!]I@w%Audgݟ-&7Z.TIJºCf!3ٺ?~K_/!8PpTAvBV0ԯ*Q{ >T0GOH>W}+(N;AO ~Q_OCI]\<rC ɡqox<~b},,7/jAIÙIәIIIII#I3r̡ͤͤͤͤdѬiF3YscgOhnmq 2*K#@F~dTjQ_AF~_JQ_AF~dT=TjQ_AF~dTjQ_AF~dTj~)AF~dTjQ_AF~dTjQ_LKI3:/ R4jdTjQ_dTjQ_AF~d،N^_Jѩ~il8jgyT=TjQB/%Ȩz~U/ dT=TjQR ΣRT Q_2*K5Ȩyd􄌞2* #8#d􌌞QLAF~dTjQ_AF~K&54SzkXI<);7󻶖~)iFsk(4g? MC3駡S=[&h܃fv{OrR{RQ5C/xp1wdB6Sk&Mg{\{QZrS9};#wWWAm\7>ߵ0JYrIr%pK56f[p/?W7M #= Lv1õfƀP>oӓ{VԧJ.ǚ&q0B+Ma̿ōu7?=N}d8lA9|0h5n0i-{?|^ʡH3 y﵂ >No)xG{ >8H>V /(N ~RF*1)X0N[[K_[8EU573&EZa9r$ f&3F3f33s_<簿Xӄ5谿TԌ5LZ͙6~ѬiF3iF4YӌfҌfҌfҌfҌfҌf>5SOY>45Ѩ#G#{dt>12GFњ˼3&3F3f333334&4F4f4C3I3I3Iw/PuhFhht4dQ_:O/y= d2*K2*KdT: u4^SR/uQ_ AF%~JR2*KdT:Ȩ/uQ_HRJR2*KdT:Ȩ/uQ_ AF%~JR="!2_HRj:Ȩf~j:Ȩf~j:Ȩf~JR2_ 2_ 2* Rj:Ȩf~j:Ȩf~j:Ȩf~j:Ȩf~j:Ȩf~j:Ȩ/t$~)AF%~JR=#t4dT:Ȩ/uQ_ AF%~J5444Ѵ2)~jw%)⥿ bt]3:%icf"|xJi:ǡ gjW5e4oiwo>y2Lk&O@M{H1猍&q9i u2T~NCy3w!c( jǜ9OP- ~ Яp(SC8n;)ε4h \9B8A̅Zb ~'g{llG@0s5ҭ`Iw3LLI6Vxkwў[=j2Ŀm+ToqxG{ >T0,/7OL Ra}UC8,2Úw,ϓNxcsNx3&E!`_[^AsIIGf3; g&Mg&g&g&~`҈њH4 ϵnK 2zhF ^gNx 86C mI/hXn9,+W%c,~͵rm™ۘK G#æ#0c#}#96J׊DA_ygWhx=5v^*rLoȡy dJy8s"0]]S@qr8pf1"3)bqn 7UVbEd]0Ǫ1^g.LJ9@nc_n/-!/*^Ktqm{$z%wWH^>Z?`&y_,/)8US\ARAr0~B3o(xK; W  >V' >S|`xC'(h:Iv'o8ox .xmWLn4HLLLLLLLLLLL3ڄfͤD^HN3WЌfR/LAF兩 St0uQmMdTCSAFȨ0 2*/ yaJQya AF兩 S2*/LdT^:Ȩ0uQya AF兩 CG^dT^:Ȩ0uQya AF兩 S2*/LdT^:Ȩ0uQya AF兡#/L 2*/LdT^:Ȩ0uQya kmMGCF94uQMSt0uQMSt0uQMS)AF5LdTAF5LdTAF5LdTAF5LdTAF5LdTAF5LdTБt0uQMSt0uQMS=#t4dT^:Ȩ0uQya L/LI3:0u'yawkUm G+3$NR 7)bflJiF_Sk3[A17^WjN Θ:ϐf}H kF-.^G6ޯm</9v1y?ۋ[n-rk6DTs{H+6Xtnctt]kMul6 ,"s<cڋg~/'[DhMeI{5cl3j$=*HgHϾh@7O ރac>7Ә F>7zh赴My 9hhBJ/b5oF1r3[VAއ>I Zø;Hyjw:CHn.fr˿ɷZ'_l@>R+ W?"^I+;gͼ"N֜W@o*K{ QqੂX/c nP𱂡>W#V/|)AO ~Q EGRX3:Njqq$H ȑ\ 4i(3i*3i,3i.3i03i23i43NәIIIII#I31&ELI-w62S= @ji dTJS"2*ELdT:"!RБ""2*ELdT:Ȩ1uQ)b RAFJS"2*ELdT:Ȩ1t)AFJS"2*ELdT:Ȩ1uQ)b RAFJS"2*ELdT:RĔ RAFJS"2*ETĔ4AF5:L֌ѐQ)b:2*ELdT:Ȩ1uQ)bHSJS"2*ELdT:Ȩ1up"2*EL\GJS"2*ELdT:RĔ RAFJS"2*ELdJѐQ)b RAFݙft*b4stxu^Hh)[L{԰Ehf4jICft{ԌnyGŋDxT8ޓ:؋ֈnyWHxurK+.D|rtFt؈ +jBܥUtLJNIUtȏ {JYa:gMW:-3ģ w6C5W9U+hN:~Cٮn/2ddm0_xcn.2# \H}M42!G^41Т q]86r6wᅡr{0yb1'bC||b$}Z&/N^r-cZ&s/y/6u2#.OiSH$G5G<@s{7$^INKd<s?mOY?m Oy9%ogeߔ-I)8QTA $^W5%ׯ`Hb8[:; S𡂸܏Ļ+Po|]O ~Q I}Jb' ;s)Hbw$1uxoWcah|7 S@zWLSR0+a4y&2Fra:OC2223&3F3Odpf;gvLLLLH 1%Ȩ0uQa AFJS2*=LdTz:Ȩ0uQa AF#=L 2JѐQa AFJS2*=LdTz:Ȩ0uQa AF#=L 2*=LdTz:Ȩ0uQa AFJS2*=LdTz:Ȩ0uQa Б2*=LdTz:Ȩ0uQawR6)כ2)iiw]FN7u(9kE7"ygiX oFU952wFT4;w+H_݀6^Sfn2%})39小Frߔ/9ĻHA>Ots53TCͿ7j<9PwS] s5v#Ո;5~P'P%))E yy]<`:|/fty-hA1 kFfF4~q]:UHm[Aj{`rgPᕌ 6nl@7N^C5jrx?gʦs?60SΡzpӹ尦665Mg^^75Cz{joPBM6Y1ces9l,ג~yZ Aj&FrܒD9"2Mm䫬ȱ} 3Y)ѡ)xC[:;j >Vz+*;?(I/ ^ƤXڶ`xqcLxf2PfTfXf\f`fdfhf iÙ/i<|F@_#= dP+؎== d2z@F= d2z@F= d>"#2>"#2>"#2>"#2>"#2>"#2>"="Gd#2zDF="Gd#2zDF="Gd#2zDF="'d􄌞2zBFO =!'d􄌞S3:0Ԍfͤͤͤz =!'d􄌞2zBF'd }BF'd }BF'd }u '\Gp}u '\Gp}BF'd }BF'd􌌞32zFF=#gd􌌞32zFF=#gd=#gd􌌞Th|v.F5cvثvh߃=iTh݆5^P-Fӡ\^薛^sٌncL،nC-Ō^4ft\Aпt4g|1s7n=YrxnCv{ \x9sxncSb9L's5kFsd`͕XN=yn] gb2kٌuj"Ǵyī#&ro9tB@Cǜڿ|_M01Fq3FqPmMzu2}PLi^478n0L(ތx^ sbԟZ5Zc4fSԼqfqZ ^EV CjZ ݷZ s;s~;ܟ1>3|ϘϘ3urjmKjxM5j8%#=Rp3 Ag Pw+j0j؍R[C PO ~Q 5 'y,,l3i3i3i3i 3i"3aPњLLLLLL>i8;gvOab 2*5 aJQa RAFJ S2*5LdTj:Ȩ0uQa RAFJ CGjdTj:Ȩ0uQa RAFJ S2*5LdTj:Ȩ0uQa RAF#5L 2*5LdTj:Ȩ0uQa RAFJ S2*5LdTj:Ȩ0uQaH SJ S2*5LdTj:Ȩ0uѩɤj:TiF2*5L2*5LdTj:Ȩ0t)AFJ S2*5LdTj:J SQa:*5L\GJ S2*5 aJQa RAFJ S2*5LdTj:Ȩ0uQa RiFvgQъyẶmycb4瑤M1kF簭U!lF~5s׌ΎƼAѴ2S0^A~] hJËw#6a:G`8sLR^xbc86^ecjs*{9ny,/` }mrfn}5)s]^xOx5Î˱tQ9fzq`S7qWc3M3?q^55c_ij&,/&{k1_dȫEd !dގfĭyE~sl 6c/l9ll_9pF{6)ӽH̐z0=r܏؋9\@C4O."]k̿Q^YdErxuoƻVɱtWFrxOq-9<̱tuB?LuD?e6OiO)>:H' \*Ppo)xGzO:G >Q! 9 :TS@rؗRY̤a̤i̤q̤y̤̤䊏NCISIcIsIIIIIÙ|vb|J;H0uQaHSJS2*9LdTr:Ȩ0uQa AF%JS2*9LdTr:Ô AF%JS2*9LdTr:Ȩ0uQa AF%JS2*9 aJQa AF%JS2*9LdTr:Ȩ0uQa AF%JCGrdTr:Ȩ0uQa AF%Q ѐ2zBFO =!tȨ0uQa Б2*9LdTr:Ȩ0uQa AF%JS2*9LdTr:Ȩ0t$)AF%JS2*9LdTr:Ȩ0uQa AF%JҶr:hzs3B]rx]Gl1W_q`3:'c؈n[i#Ɔذ<_輁w5c<ڀW~acxP;#9 0&sK w깍p^R`2!M6^W9v +5cPjx`9Lg~5c'fC9p6cj92>6:^Mdkd>F"29[2^\I RP搤frܴ&|3&2go&rD~y0Z*oFr]#9n e>39d5;|ɱ~޼Lnÿ|K]>+rH2]oQ9Mp e2^)jV*=: _hvVd=y_H$յJf\sJK?go- Wwx cIH' tK畂t7xK;:{ >P|+*&.UbIØIӘIIIII#I3ICISIcIsIIIIIÙIәI )=L AF#=L 2*=LdTz:Ȩ0uQa AFJS2*=LdTz:Ȩ0uQaHSJS2*=LdTz:Ȩ0uQa AFJS2*=LdTz:Ȩ0t)AFJS2*=LdTz:Ȩ0uQa AFJS2*= aJQa AFJS2*=LdJѐQa AF4uQm+MdTJCGzdTJSնAF4uQm+MdTJSնAF4uQm+MdTJSնAF4uQm+MdTz:VdTJSնAF4uQm+MdTJSնAF4uQm+MdTJvҶAF;)\ޙ^G9Ha't1::h~h[nez1c΋ftz[3:+Mx$K|0s4東0e0s9Ʀr\IߋyIh.m}e:_M4.(hCfGrx]pӆr\LZ6ɫLXce垼W39v.&FPcb2%˱̟~H^,} e|jnK5^Lk39m$|Ώ9l$F2^"7lR)qc7;k[i?ay >O*]S K[,iwic_"3Z.ǴwOhx]:r$\ [pȱڂ3!C $- <&C#uL5sGI̤?j!sxN/zH'  ;-(xO >R|+IN ~RbfY̤a̤i̤q̤y̤WHa4444444444 2*CLdT:2Ĕ 2AFeS!2*CLdT:Ȩ 1uQb 2AFeS! 1%Ȩ 1uQb 2AFeS!2*CLdT:Ȩ 1uQb 2AFeCGdT:Ȩ 1uQb 2AFeS!2*CLdT:Ȩ 1uQb 2Б!!2*CLdT:Ȩ 1uQb 2AFej:Ȩj:Ȩj:Ȩ 1t4@L 2b  2b  2b  2b  2b  2b  1%Ȩj:Ȩj:Ȩj:Ȩj:ȨI:i: 2NwnlV"^qmasNΝ)bJn27 re"AFe#;L 2*;LdTv:Ȩ0uQa AFeS2*;LdTv:Ȩ0uQaSS2*;LdTv:Ȩ0uQa AFeS2*;LdTv:Ȩ0td)AFeS2*;LdTv:Ȩ0uQa AFeS2*; aJQa 2*;LdTv:Ȩ0uQa AFeS2*;LdTv:Ȩ0td)AFeS2*;LdTv:Ȩ0uQa AFeS2*; aJQa AFeS2*;LdTv:Ȩ0uߜdi;$Mod b:2 ba4G8L+0eMg],m61t&37v\66]q;{ﷹ-沷ڌl,iTq漻6L_~N]d>j"멵=3ecX8^0JraksMW՚Vgoa6|LL}QY1go|uXcgy.sQ<5(a&.9Вf"ʖ l*׃vMeKPc 6itp^Qތߩ͍faF5hI3[AϷln) h/'dN d$q'/RoIޒ*CvxԳkfk$Wi_찃/ Nt༂pU(;Fȥ -(xO:G >Q/|Fw ~Pa0yǶA"e3LPv:d> m ζ<S}@BehȨ0td)AFe3xd 2GFe5:%i.wz{cYT쮡Lp5^#Y&2Ko/FN dMY96zJ4hjRZlPf˳=:|?y6m4TqRa)9, ˳/ svSzIn"w/rHl4-d^C5#yӡ2WMe.wXa z"dѬ 5=l8𭲶Nڧ'oofe κg/8kzzӂ3i^p#$N /:kHxљ$AA|O[A$zio7~v? 1)8Qp JJCDzM=)Dg Po|?) OJ1e؂6< )AL&R0XU z|@<Oӵ!AF%#AL 2*ALe 4Jѐ=2a:2*ALdT:Ȩ1uQ b AF%JCGdT:Ȩ1u>*ALdT:Ȩ1uQ b AF%JS 2*ALdT:Ȩ1t$)AF%JS 2*ALdT:Ȩ1uQ b AF%JS 2*A  bJQ b AF%JS 2*ALdT:Ȩ1uQ b AF%JS 1%Ȩ1uQ b AF%JS 2*ALdT:Ȩ1uQ b AF%#AL 2*ALdT:Ȩ1uQ b AF%JS 0zvmA죵 v8f2ц^.d2$ڴ\Ia4aLJR|+(N ~R I $1%cKb [ѐHIb $1mlIL%1udStJS$*ILdT:Ĕ AF5ELi$2)b AF%JS$2*ILdT:Ȩ$1uQIbHSJS$2*ILJS$2*ILdT:Ȩ$1uQIb AF%JS$$1%Ȩ$1uQIb AF%JS$2*ILdT:Ȩ$1uQIb AF%#IL 2*ILdT:Ȩ$1uQIb AF%JS$2*ILdT:Ȩ$1uQIb Б$$2*ILdT:Ȩ$1uQIb AF%JS$2*ILdT:Ȩ$1t$)AF%JS$2*ILdT:Ȩ$1uQIb F[ӒI a%1uіthvԄh a%%1%h /Fkb4珴cz;O .FK,/FH[iFw#6bB h=|>6u(Y/7>6oImjx  cYল\ 3~WaX4ԔSdɲTW#YP֋7TR릙5^[fea(+L9a"k_xk4(gr۷Ʋ6DQuj`ԷD;\fm.6<~zz"3T6d2SfY(bk\2bZ6fxւl)"Mf a8Pf3Fכ2|Y ͂iY g&+eNuJ;E |xVM;/ȣyՒ[QL' ^S-(xO >U}3_(J7 S<<`2,Kǒ D>\hBT> % ̒4l> H|> H"=2GFsL瞙dt=2GFϽ3_2gv=2GF#{dt=2z@F= d2z@F= d2z@F= d2z@F="Gd#2zDF="Gd#2zDF="Gd#2zDF="'d􄌞2zBFO =!'d􄌞2zBFO =!'d􄌞2zBFO =!'d􄌞32zFF=#gd􌌞32zFF=#gd􌌞32zFF=#gd􌌞32>"#2>"#2>"#2>"#2>"hI%1FhhIu$^IIb0xddIb?qhjhu\Ӌj3Z3m3ږx{5mB˕ f.<35FhN^|Ls1yf`;S]֭3;g𫡬AS3K55zzFIf2H#yMd۽:y3-qY؊x,1Y-R1t}]4Q#N̶ df#Qbhm(7-.eORx{mqY?RzƲ-^wm0Y7f2.2"ֿIb5k:{V- 7Y"Zi=G'/6k PIf+b_[^l=-6i}׬i{'Sya A|?Z JSYwAY`Tjo|/,嫜96m f[Ef ͚Ῑ]=(pLn8Ki/6S^l,sqMӰ݋$]x#Tجe΋͚]\ A]A|Z ޾ɔ֞-݉OTߋjJUMA1WJCpƙG .\)QD{C[:; S|L R'1%cvx1glALA1u t1u -h1uO b AD%#ALdT3t4dT:Ȩ1uQ b AF%37M:Ȩ1uQ b AF%JCGdT:Ȩ1uQ b AF%JS 2*ALdT:Ȩ1uQ b AF%#AL 2*ALdT:Ȩ1uQ b AF%JS 2*ALdT:Ȩ1uQ bHSJS 2*ALdT:Ȩ1uQ b AF%JS 2*ALdT:Ȩ1t$)AF%JS 2*ALdT:Ȩ1uQ b AF%JS 2*A  bJQ b AF%JS 2*ALdT:Ȩ1uQ bwZ;iAL0ڂ΃JF[y.hvB% $) ,FK:.FK/FkubV$]NA^h=U3׳Ky1T 3\r⺘#lgjB5E/0ny$X,֨Y70֣h%7E8,2^Mz#̞E@)I쫎f*k%maҷ勴ƲF/*"_Zͷʶ]²LǵG޳|-Nr[wᬁ`Y3g^wlY~Yo,{ЬA3aZh62A3蠙 |B3W o?9-Gݜm#ޒ/_gzG(ϗvAA++_' R:Po|?)#a؎68#َ:d;b 툩ߝ1mlGLa3;g&HLyO홦=AF刡=Ӕ rAF利S#2*GLdT:slGL}f՞i:2=AFg:ȨLS#LSj4uQ홦2=AFg:ȨLS՞i 3MdT{j4uQ홦2=AFg:rĔ 3MdT{j4uQ홦2=AFg:ȨLS՞i 3MdT{j4uQ홦2*G 홦՞i 3MdT{j4uQ홦2=AFg:ȨLS՞i 3MdT{j4t)AFg:ȨLS՞i 3MdT{j4uQ홦2=AFg:ȨLS՞i 3MdT:3M 2=AFg:ȨLS՞i 3MdT{j4u🜴g:0xM3Hc$_ef qF;5G[i6K.6k(xY׵,;l>a3ߙa3;]^\bhZlmgӒ>5IbѦi$w^䔷$ÈԈ&oI|$oJn8~Ձ JPH+]Ս':$(I /=(H' >SʿQ@^IbJƖ9ц<ْ3H$@ْ:od - ͖:3xf|JyPIb Б$$2*ILdT:Ȩ$1uQIb AF5HL}$@JѐQIb AF%#IL 2*ILdT:Ȩ$1uQIb AF%JS$2*ILdT:Ȩ$1uQIbHSJS$~$v'ja4;h&Tta4J{JS$2*ILdT:Ȩ$1uQIbHSJS$G%QIb}T:x$2*ILdT:Ȩ$1uQIb AF%#IL 2*ILdT:Ȩ$1uQIb AF%JS$2*ILdT:Ȩ$1uQIb Б$$2*ILdT:Ȩ$1uQIb AF%JS$v%1%hKb -I=$) 1\$1NR-N ha43f-8 ߯m㈷VD;I{ -}l@_ja$1Y eas YK(Moocn֕&ޒZ-l0Yc fMjx;O9I<[ ?{x;XO,16l*YLo"o|1Ѿum^lw5m;&?*0X;?vLvY5ʚ5f&kCBd H֛&KWܕ6n;_f=[l*lփ8bߏ Κ]p\pCog gu}UmYn7$~I;In-ony&Em^7ky~}js&4_9(M \( T':&(M /=(H' >SʿQ@^ibJ )O[ $[y-&fkb:Ϡa51mlMLS:H41uQibHSJS&4Sw"{dT΃JS&2*MLdT37ԙ[hvQib:2*MLdT:Ĕ AFJS&2*MLdT:Ȩ41uQib AFJS&41%Ȩ41uQibwOibwZS2&~&0ښ^)2}AFo:ȨMSվi 7MdT#ML 2}AFo:xվi}TQ훦Go:xվi 7MdTj4uQ훦2}AF}Ӕ 7MdTj4uQ훦2}AFo:ȨMSվi 7MdTj4uQ훆41%ȨMSվi 7MdTj4uQ훦2}qҾi 7MdqmMd2&0000 2*M 4Te1Zj9Mԋ:0׶-kFw9 Y+FjxKr<=y6jMhM,:ƳAsb8KJo/1 sy(h'NRӦ&3#q7j.iROiECeM.*a(˯ɜ5{33C Ƅjoq>qU汞mw|O d+Ed=yYD41kmibCPpI9XYXpͦg n ,AxN<=(Sn8klxGXӹG6/Q/|]AܰQ@`;c20fghlSLSglSLa2;eb:`23!3A3a3333S$T:ȨL1td)AFe3xd 2*SLGCFȨL1 )2*SLdT:ȨLLdTO&2'SՓ#SL 2'SՓAFdb z21uQ=:ȨLLdTO&2'SՓAFdb 2F3F3F3F3AFehdTO&W_@e<ȨLLdT:ȨL1uQb 2AFeCGS)B4PLGCF5PLdTtȨ<Ȩj:Ȩj:Ȩj:Ȩ#SL 2bz S@1uQ Sk:Ȩj:Ȩj:Ȩj:ȨCGŔ (2b (2b (2b (2b (2*SLdՓ}WaM1\)AF5PLdT؝6zd)Fhh'=Py1:K~)ޒ:͙C=^)t(VX/k[oW*ۿrP#5N+(U '\NGO o)xG{ >PO| _W)AO MC*a1v hdv.$3Lf2Pf2Tf2Xf2\f2`f2df2hf2lf2pf2tf2xf|J;HT1uQbHSJS*2AF5TLdT:ȨT1uQb RAFJS*2*U bJQb RAFJS*2*ULdT:ȨT1uQb RAF3W2bwr0T1thbwZUkdT:ȨPLa4߃a4a4 *ULWJS*2*ULdT:ȨT1u>*U bJ>*ULJSP1uQ S*G5TLdTCAF5TLdTCAF5TLdTCAF5TLdT:*P1uQ SP1uQ SP1uQ SP1uQ SP1uQ SP1uQ CGdTCAF5TLdTCAF5TLdTCAF5TLdTCAF5TLdTCAF5TLd*Rb6bJQb R*FS}<~ y1Z[OR׳+kFw/4ix4\ /f=ї-B |g۾ƊU˦-5Dj8sӮT_{z\zHS f&ͳ^l0΃]N|/,F{5St>r )W_y(# YN *7^6-X(7*JIn&da/ E>4jl8}Mc`X;%vN =ON65/c"-0H])1? ]T@&SםםSNple?|y hMKoA>o]3[RjɁom{u1Ƣ⭓$MRψ^<#Ϟ)vg~SEA+qmD㴂)xBO o)xG{ >PO| _W)AO $ V =&L< 0@P`pɀɐɠɰЙ > 2*Q bJQb AF%JS(2*QLdT:ȨD1uQb AF%JCGdT:ȨD1uQb AF%JS(2*QLdT:ȨD1uQb -dM;f2(Qbx=G)QLAF%?hb:~(WJS(2*QLdT:ȨD1u>*Q bJ>*QLdT:x(2*QLJSAF%JS(2*QLdT:ȨD1uQbHSJS(2*QLdT:ȨD1uQb AF%JS(2*QLdT:Ŕ AF%JS(2*QLdT:ȨD1uQb AF%JSj؝ŐHy$)AF%Jӵ?-}NZ }z_Cu]X- Q|?O퇕('u0#NS3S~k~:|NDvdLOm.7jfZϱ5zq; l~ⱹmm^dŷڽ;g>U3C'>^?㳋?*eۥfEc:gX}dX0c?<v)b'=S}b5,Hd Tdf&[yʞ5RƲeռMfq{e ) oIIdYL? hRB`!Z{!Zt!)#U]u>xY$Ɲ733[' U$_LZ?;Wтf&;goLU*T1W_VoΉ3\(Rp-(xO >R| 7 SHT{L38fgxddddddddddddddd*&vPb RБ**2*ULdT:ȨT1uQb RAFJS*2*ULdT:ȨT1t)AFJS*2*ULdT:ȨT1uQb RAFJS*2b )vU1%hvLTϓ_c˧O+**2zDFhȨT1uQb RAFJS*T1%x*GQb RAFJS*2*ULdT:ȨT1uQb RAF#UL 2*ULdT:ȨT1uQb RAFJS*2*ULdT:ȨT1uQbHSJS*2*ULdT:ȨT1uQb RAFJS*2*ULdT:ȨT; R>*UL>*U W}AF~:hv'%00y1Z=^3s~~m=mlFwUx뼈-*'߃*ڀb*x֡LguYJlܧnZ=x{STbs+eZYccOWDYŇ{2\xtSB dIۛ# dA{/5xz c ZWmη0͒&>t ZS`|Øo܎oqWccAYb_@د4BlDwE>ZoILktWy>^ZyQWn֨s_7{XySpข(ƙG .\)QDk Pw)Dg Po|?Wa1;c&Cc&'<2!2A2a222223!3A3a333334!4A4dt=2GF#{dt=2GF#{dt=2GF#{dt2z@F= d2z@F= d2z@F= d? ibLW3f20a,~ ~<"Gy/GzįGd#2zDF="Gd#Ghm0 GOx=}GO '>z =!'d􄌞2zBFO =!'d􄌞wןwן32zFF=#gd􌌞32zFF=#gd􌌞32zFF=#gd}DF\F&^+ɬ7$|oi;A 4tDF=#gd􌌞32zFF=#gd􌌞32zFF=#gd􌌞32zFF/xK3ιG/e(2zC/x=ziF%q~})FsK1cf3 is1ceҋ.f4llab@ 7C^iױp|<\Wܮ&sX9En.׿>l(ɹfr,Gm$4 ` EjUw>#4,kkL}kIH̽2 ıIӏMPO| _)Fw ~P_{YNØIӘIIIII#I3ICISIcIsIIIIIÙIәIIIII# 2*IQ1:Ĕ AF%JS$2*ILdT:Ȩ$1uQIb AF%JS$$1%Ȩ$1uQIb AF%JS$2*ILdT:Ȩ$1u,/IN^y^:h\J&Đ،֎h&ރLZ3bM}܌fҌfҌ5oh&Ȩ$1#IL 2*ILdT:8JSQIb8*I ğz:xs$I &IL I$1up$}qT:Ȩ$1uQIb AF%JS$%#IL %JSs$1uQIb AF%JS$2*ILdT:Ȩ$1t$)AF%JS$2*ILdT:Ȩ$1uQIb AF%JS$2*ILdT؝1f4j3I3I3uzzIb8Ĕ4SI$$h)-6J[J;fF%;9bD>6cu4߿ET/sc:fB1[Yb8T\Y%K|Ls1+<b4Sx:DCs|d;CKrY_9c5 *ƙ W nS|;?(I =`,fq̭54444444444444444444 #KL 2*KLdT:Ȩ,1uQYb AFeS%2*KLdT:Ȩ,1uQYbSS%2*KLdT:Ȩ,1uQYb AFexYbwj iFL؝Tb'邛i#K;bttth&Z3f4n3I3[kF3)FǾ#kPYb@ %2*KLdT>%JL JLGTbi*1uQM%5 Tbw2[$M%#KL qT:8Ss,1uQYb s,1td)s,1uQYb^:x%2*KLdT:Ȩ,1uQYb AFeS%,1%Ȩ,1uQYb AFeS%2*KLdT:Ȩ,1uQYb AFe3-1%LKLI3KD7^Tbwu4h\ \~%^\Lxl9ALSWɫќp{cM1t1cql6C\_Vshk9׃0e6嘃k*S_ e:j&Frl"_Q7cj8%ǜċ\jǼ0 Mok,8Tt(ǍFmbFqs8dc9(?nFq<Y>ar.4Nkm5c}$^{0#l"5uaC9;rzr$nɄF3;;ɭ- ī360ۦs_J܌罙-9OP%Gŗr /th(N9^x/r{WrH' \(Rp7)@G >Q/|)AO ~UfGL4:c&c& d&Md&d&d& e&Me&e&e& f&Mf&f&f& g&Mg&g&g& h&Mh&h&ȨSS#2*GLdT:Ȩ1uQ9b rAF利S#2*GLdT:rĔ rAF利S#2*GLdT:Ȩ1u /GN^<Sf4ft:bN&hf4f4-h3N3os&J9-FGR8b4L:6tL鈩ӌTؑ##2*GL#O1%8SZGsk8j&1m=NG `$iiJprqTMSZn:xrs2妩jiSz-7MdTMSZn:xrs2妩ji Zn:Ȩ2妩ji Zn:Ȩ1t4%Ȩ2妩ji Zn:Ȩ2妩ji Zn:Ȩ2妩ji Zn:rN#؝)B4uѴh&h&hft:b[nN1z ]9⽳r++;E|{'u$ ?jBsuSI1ޥ59 Βk)fsXWC+-gᴍ$ǰf8VW#yguHr'W$@9oVx%hܫqL%KkWKſ?b2,l_j2zN5bцqM\"p̅yg) Gf" kkcak39'&)}.r,{S=~w/0hby j3xu+h6Բ7yON'E']4Ů݆@.Fs M%^9j*MQ](MF:/W_[]j_Apੂ8gj\*\ ֞񦂸yK{|#(L:_)FN ~R^GL4)LLJ2;Md&d&72;Me&e&e& f&Mf&f&f& g&H|LLLQibqё&&2*MLdT:Ȩ41uQib AFJS&2*MLdT:Ȩ41t)iFތfguibN*f4f4f4Si|-PnuLl,j)q3)=R:f~4ch)76[rFxuu:4D;:Ftjbwrq1:j1M󉽵41j>_$`TkN${"Q Mi\gDPDqw.*TA35.W[C;CzK{|#(L Rw ~P_&b&1A|N#I33uSZ̤̤̤̤̤̤̤̤̤̤ͤ ͤͤD،fҌfҌfҌfҌfcc3f4f4f4f4d(#JS(2*QLdT:ȨD1uQbHSj>1G%3E1mN3I3W/xv(+ftbz?hv!} tܚѸ;y,Qn֚N]O&v޷}萾h~i'+n1:b42(ޮqtlڹ/T$}D@Ibt̏q4?(v7aG(&1߻\4 (c Q {5\q4n1@>s_Кރ)hiJzT:x(%zb8*QLKoǒІq(d8BlǜiØkBzi 01|3SƩ\㖟D-S6]5{ >Ц>R|+o|?)E{%Ql(avyԌbڃ&rbpk enLXf\> Mfn̤̤̤̤̤̤ͤ ͤͤMڌfe&h&h&h&h&h&h&=lfҌfҌfJd(!AF%JS(2*Q bJQb8*Q Qb8ݙftbw2iF3iF(=hFS6q٧Ŵb4o%WgHf4%-FSJNjMrq+yGT'ޓu]3"Yūw+ٌnyū7Ŵ53:ҌNsf-FbwRCwD1Ĩ,QLI1:.^JjG87 =fhGS8Iy7GQ3E Dݔ(vs{D1%x=*QLDq ӘքsUdy╌ 1┦=Nl-2weסmƱiEe[ݺ zD d K\qbw׭rl*77s9l0q L\C-<Дbǭ.:ǪEgR^g$sܔiz>?* 30cqkkS @LxyF4;)Mi hl>!Bxu㹽H i<ð57$Scg;S6M֛}"M#v''2ؙ ߥ]z:x~?]zwa]z:x~ߥ#=L ߥJSkPa50uQa AFJS2*=LdTz:Ȩ0t)AFJS2*=LdTz:Ȩ0uQa AFJS=#z01m Sj1uQM#vgaJѩݙzI~_^єhuG|FDs] Eέ Oɟjf4x%T= WB~xRCHA2=xOMXp?:91hEz2jxybޙ^ \|]^=xO6#_! c޲ÇGӈWҲ+O36[N׻Lh _/7= ܴe&Fr.m&3 2EMaBnI.l.t(Ǥ(sk5P5P挀[ cAA9JP̧ 㖀 $1$7"t"?M\*c!b4-_s}t~n5M"^D ryOr>SRA1q3o(xK; Sĸ3_(J7 S &09b89ji&2+v444444iLLLLLLLLLLѴh&h&h&h&h&h&h&h&h&h&h&h&h&h&h&Ȩܰ?AFQa2*7Ld}DFu)qTn:Ȩ0upq㨖G8 u6qR>4^}5'$*7$Y3 $;΃cD2{ck5y-3)h\EKצҍeVWs f>JSW#6ctwn(AVxo/NF2o44 dފkS ^$tZYz%ko7y?^Iބkkא9ҳ̔=dfCfJ2Cf&=dN%c23!323!3OR޷Z3)Ӻf SORӺ0u.%Lx%9^I0]s9 h'4W?+84Wx%Q=y*-*G|̳c: (x?S p 7}|O| hBW Uw ~P_ I8<;E#)i" r`4cɌA̤̤̤̤̤̤ͤ ͤͤMPG4f4f4f4f4f44Ohvz̤ͤͤGLLz̤}DFGd}DFGd}DFq=4p=8zqC3֌plF3iF3qЌfMی)h&x,p1:[kFS>h`B~`@JgGg>h:|4cj2cg9f!XaT7h(kɹSdޣb@y8l~hh2so ?_Xxs ysS4=\Y2Sӵ܃. '.OxJ?)OxJ?)OxJ?)OxJ?)O^v>e^v>e^v>e^v>e^v>e2>!O2>!O2>!O2>!gd􌌞32zFF=#gd􌌞32zFF=#gd􌌞32zFF=#gd^ 2zAF/4io\[ ]``'Wk!^Ōnyz1[z1[^] ^hA^hKw#bBN|ŀNXx8a9̧L!r5#(25 ̜%[\]X){kMyLj"77Ǽ5E4JLW8kjǿ$MPt~1XNfLXgf478&ƹi8Νy6[V5HG$s [ I\V\6z Fc)[ݴ?09ѵ1?C-_</s4O. OE.@"5`jl&tb_b?TR1?vQ*ȯh* ƙ^BRp7>R)PA\})BW U'*&bJv4xr$Mdn̤̤̤򶱜*x}GS5of& g&Mg&g&g& h&Mh&h>"#2>"R #2>"hN3I4 2*L 2*LdT*:ȨT0|)iFS64444S{ v*֌֚Ժx~? Pš;*xXٌHN^h^vW+H*O3wLjT&PF3v4y7p4CƚˡoeGαW85cON+ǜ`|<u.L 偩sy`\:x>偩sy`SלkNy`5<0uS:x)LdT:Ȩ<0uQy` AF偩S2* y`JQy` AF偩S2*LdT:Ȩ<0uQy` AF偩CGdT:Ȩ<0uɤͤftzw׭ ɜ_l"k6dS}ScqFK-Ә2{%WR`8&Ùa +R~'90Njgb>57ro \DnÞɩd䰝f2!cӢ}.a8989\d,`'sj̻[Cm9F1[V|;?w`+U*x%g>l@*:x.@Ǽ4ז Tƾչ_n_ ~Tp_CpH'yf:WM ^F3m [ ѦSCCÞ#'g P^[C{"Q@'m5"b1F'Lqά`Zv7pŤ{PPwT44AL>ɤLpftfxf|>|c݄чf4:RA ͭ5 R:hn  5[Lѩ:SS>"L[kFsLLLLLLLHpլ`4+dT* Mѩ:)|d_G=ckxo1O:qˠY^׈72{#qd`,l>sca8(5CUMؔ6a,s5${.l&,b#9ĵGr,m;"xqtxq8u$Ebs4ġM6vNXeA"?N⋈S]'ɤ4xy“4C]4"L ^pj>0udS/85؝p/8偩БO1j1|`Jd[kF3iF3iF3iF3 Ny`kdT:Ȩ<0uQy` Ny`<0uS:x) y`JQy` AF偩S2*LdT:Ȩ<0uQy` AF݉QY)qT:L 2~)AF/mg3:=0N3ߝ6Wbt\U6M6[5y '$$+I)\9bF޺/J[EijtmBSG;6ϱ3xm*Аރa\昌l4k̡ l^=v:|}4!e~_LEF9}@jӏjtyU ׫;;:Ix{\(ΩuO|YrqM:;qu3cqlT]96ۖH-4O97[(Nz@xC,㰦E㝵ikZ9kubrtICa9rNygmhF3>b3xf|fҀf҄f҈fҌF" I3&SҌ֚iikȨ&SiF3iF3iF3AFeiߚ2ikhnͤͤͤͤ# #,0%=EFeiCYhZf4یN }eWg>GWglFCcdXوEM蘑k@S:J﯑O C Ϧs< p2!hMkbt0w^J?VG#9GyGHskO6}fq8k8އIօ^ M;l%5xmj. &qo\K>r+d 4؝z9=Lr޷|&sk=LkaRAsTI3Sy&xo}*ϭ0m /7&4urSkBSOrњД<0uTf4dTkB4f4f4dT2*LdT:Ȩ0uQ9` rAF倡#L 2*LdT:Ȩ0uQ9` rAF倩S2*LdT:Ȩ;q0t)AFSDrsք["/D] I&J$sDIk5q3[>/JkBΘ[7¦s˯Lv&&d͑L :>SvnIHHoQY"5%o~g?fM(#U q5TP((_v|m[Gg':>E- ^A}ۂXfHwMP ?9g' nHtw CXQ\ QQQv}پ5k]w \;edIe2xR`eL*Ѝ(ͨ;:Q\Cn2RF TQ*5kG!0:qJk(c:uJ0hX([l=b=]e4v1ĄlF',` LmM ]2km=+0JCyufȰqQb>CڷݖbG3*Q)待^z1>/7q8pz 4< MLnCťz`xE!/p=rţE`EŢ\XߣP(^XX\(^D9`Hӂ^j]Sf,~X:ʣ Gy(2`SQ12`3r TQ*(ySG9a0J2hX.2b,ahX.2lbve4,`c XFv1Ѱ]e4,`c XFv1Ѱ]e4,`c XF2lbv]b=L ^llacuQ`^xҳlF/,uX=8{ 9XZRdbTܢW 732=xNl}tx ,.Bj}F x5*J33+pMȸДchLZhSRv.,/v>˟*vpOAio]wsM}"~hӮK ?]/-Fϸ\S;׫?ݝ"n\ $c+hcQb<ʀ]}GG9~ML;elmTQ*(a0JebݝZFv1]e4<`c XFv1]e4<`c Ąhx.2b,hx.2b,hx.2\1x2ES(ʀMLxN]]\1Q-v1+(vʼTx\xK9đrpR>:So\s1z(? a]YvY&ucQ| 7LҢSۂUwbSz{Ye{t);;b%5\5âRʖ+qJS77.$e{"R6. eG$p FAFnujcEV07],ƅ"אq"fh"-bb'u~$,[TTE5 FI7QZ G-].Ò z]u$SnX5% #Io^1LQ\ ~XK'{I{~aO\ 82󸰀N6we0分\a/oG;_h,`#<~*A,`#<E ukt:q 9{w? p "\Nk R.eF]O'儰"j;a'w4444p$ )\1LL^:y?'l~Q*( ڌu;eek(a0Je2RF|Ftve0J\c͘ae4,`YF,QZFF ve46*ʀb `eu\vss<ǩs<ǩs<ǩsJNu<<ǩs<ǩ TQ*; ݩkb,h.2b,h.2 )]e4_c XFu1]e4_c XFu1]e4_c bu0JOҮ5 lm2RF pY5CeW+Fjz(^1mOb ?ýOx)wbQ\bTl:SQv*q=Zxr::2jOAL^ڭ6^ȝ ˋ"AInGM5HD[^:BQmVbؿFxpm5#lya0@krE8ŤXEE8`Q@JD(B(B(u"?)~*&" | Cx w2Eܣ+<॰."<_oG p)\ ]ɓȭQ\1T&O҅g9GmTYNeTYNeTYNeTYNeTYNeTYNeTY%<̳<˩̳<˩ TQ*(a0JeT,qb,hx.2b,hx&&<`XFv1]e4<`c XFv1]e4<`c XFv1]e4?Gy/";M!u#;MJk&ka7VNdLI~D.j8"GOJ9L 6^1<+Kv K^o3oG0՚T57v3 bQ-\`.p2Ftote0JfnaupthZFٷ:֨ TQ*(ITQ*F2.pKQ)(?}_Uo( 6(gTQ9(?W0TpOE*t Q9&^əÅ;^1!c!zHYq!q!*_Fzb?RU׀q*)p~Ty\|Xx[t[pZlPh^X}Huoya K1]Ji^"""?pǷh_`^נ[(aF ^ 1S;h"*#IgW og%y3>c;>Уzlb<9@2t*@2t*@2t(a)3dk@2t*@b}F3R(vw:a0Je2RFIgXu?3`c'a; + uX&&` lm2RF Tue0Je2RF TQ*(ae2VM-ahX_A;Ąx+<=(:ovV0A^kOˆ.F9CF|GqQ1|{etQbXVp)bTXga/Y\WgHODB|z3DN.8ebSBS VLzMg;,,ʜ`kA)u1)Ž\ͫ H1{Maox @aIn%`˷`c Fw ^ /JѥZR]ktI 7x!AwxJ5^#9|l V$WӾ."JF;H mAy҉R{;QΝ(žDIw^! ԝ(OuNB3#ʼBbOFz]\ `-B;:Yv[[BW Sʫ0W ?ꛖfp6O_hrfp bW'ow-`oY$Q 6/⿺]S8ra[ैM $Zu<^& 5P^C8bo 3)MŖ.n}*MFT&TFTf 6} 3،NNFc;Wxp|` у:bkz0W mB-FB߽Cq ^1 7e ^]fk;x<@*MXx/Yt^=$c{h#bkvjf{q)Vma)7XTPP^ In)ey(/,^R;xTv@V7Xfpϋt[*pVgUu>[;ZQoZ:adU~5h}psl(4owl/TT}oVq#V\/k`buiYZ?Z}-6p) j?+/)aO0JUF/RFX^1}a#km}epgeF/^([eF/^6Ki8F//Oqqx襁^6s6MqW fm7zٍm@w 6Y8Moznljo+3Ǧ^^y굚Cnnz<ǦW+ރ{>5KSӫR+OM||Ư?<5\#=5bvJѕ\)npeVNb7Њʝ6R2҃K1h\unMj[AxjVo*&KT)6nN9σ){nLY.?TNŒۨ7\77oR|FxoVoX|oX?hnXVпOjO &ʆ[je'?ZxOje@g>&`^|y>% \S<Yۋ/Jntayʹ{xYdݏho~|o~X(1F-clCkY%(J3"F((JQ(u$Ӄf "%2Q":R~dI(Ls電(in7wF*TlVz*^**`EbţGaVnyiiͷy7/;j͵Q^Q^q7zWR=JCHC^}Q襴KiR\k#KMF/^Jއ^xز.vwx+[xzoRSKӛWo ߼؍^JF/m^6 dǡ?6Qc{{:c+go8+~lzu3Wnz/}?lzzN|Ejz镳77 >52JK'}%K͗4O=uϟ#q+5w&vs+}c+5ZCyFVbX1+|MN@*wڰ^:t&+o XͩX)_$o~^GNH!mGj$m%7-XWo_=6AM.lu_=o6觷iW!mmm@i Pg?MۼzHs -mY7M؍m@iR&e8K 6e6z)mRڦ6z)mR襴KiF/^J6z!zz_7zKiKo/67m^ڍ^J6z)mR7ms`_7zoR襴son7z967JI޴]6/ 7Oo6_Qe _Ciגx6_\6_gDi/^,h޽ǴW755rtK'#m w+{n7[)]nje醖Nvo7Y!:يܴʝ6E֬_ܨޤ c)zs ,͂5D3*4.RUR.{5?/MzG;wR~ޠ?M|ӓf3_Iz !4Z6ߨ2j\i$ObӦ'o5UOtOb4+ZCT=r钦jx%ѭ^ bK|5E-4둆@C%!i" W Xݞw~k " " 2p\rEﵱKi2kq{z^S7z# N6z)yz47ɶ F/ ɷ}>xz6zٍ^F/^JF/a|^",6wF/^tq=|eF/%{7zym7z{=ns6M.r4O i/ 4Ong~*|UjNfWΌ{lv~uR+`]9{*+W{wӤc$j:u[lՂF3ѕjt̫ --ӷorY-Sc+|\+|^7b1Y1gux{e!<5WuVYkAZK!YknDi1MT>.z+?3B3XRxmvmv(?;xfmEOƷmvkm1~p4mvnHW7,oFA:1w6; ] m@mF/6z)mR涔-%2Bsw˞,4wQ~nY׵|:NeF/%^կ1n Gc$Oo:S#5Y.lM\YaW陯ȆViV*֨ޤJ|*npR|@xS|-7AO6b5_?XOߛS@NR)=mTF']5]7Ҩ'7ww]'rɭ"Mn?}rGR[Eދ;2mދQC=Nkx5EV/eYi^Q|^|¿1M WO|I4+^|cfk4W|i^ݘL /5ͿZ9~RtRp[1&&\'ȚXjVĨ%RQ"#%uhdR@W$D*X  D"01Js?Ҁi-[$in." NdPRETEVEXz x\7^_,܈4J٫JsP{Ui6nA.=\Jܡݱ1w?J]F/:;D4wF/ӺKiF/^vc#{i2͏Ne7z)Mʕnxzܤ4wyEVF/襴KiF/%OoVg;MsSi^҅i^֍^vܡ~/~.^4-ozN$MABKgyIV~cv?4H㼮ua;ie '0+@+P+liqK!WAWaWWRmnzyI *s' |06jj> 544444VW^6ѠKi&piWmF/k 7v _΂/o>w+[W7Kރ`qlz&A9v動,9_1FW\&W$opY|܊uګ7{#{u}j`9n-s4u7Wjk;͒qK|F1eI|\ m,3J=m@w&A>:༮&g LZN6Af 5LD H3Af d<^fwYp tOdjff͓++;" "mr 7z) 2CHHtyQ^ipn6z)yz:OKuF/}Ӎ^Jt@7zKiF/Ki nR襴KiûKi襴 6^7z)mAOoln2[N@m^&ۤq^y5Hu7s3YpnYA[-ڗ$5M9\g|Ǜ! ΗD+&%I7fq&,ej>R;igs3Цq~*o8?ge*tׅ gpquŴ.Ԋвr1m5du|Кn`Yzk\qqcVoͪu*t*% 75W6!@C*zmҋR=>y0ͯs߁sc*%ҧiJϫ9S|^pFUPoTu'IVfWߥ{RTso>gZR>Oif֛;i{qwܤ}EK7_xMK_=M5HiDG}S#7ibD )WgU?6?#/6btk[%F->"lL i-F(J鈄`FtP ^D60J$D.0JD&8;>"JZD$`77]HI'i͓]M=m7ެq>s/ms'5w?h^Uj`ZMJ6̪-7*5w 6m^~Ki#b4ڍfWkz9i2ms' ˯6\m^Q4^No6znlR襴KiF/^JC/'{IWjyy5Ho|YoDe4ۇ^N,6k빡֧m^QHYoeyEq' m^|Eq8Z47zY>gjڂiįf ΔAJ9|MJ3mW?3o}'}q ރm8N|I{m$~%Q).?KۼEy&46(rX>\Lt9ˊAHSԛݫĵnrnp|5Q+|nhf7_aR5WݸrrͭڍҬʹ獪(MޠޜםJb/f0}S& Ηēh83C{+7_3Cj!\Z3CzQ{Eqxg =T .gm^@N83CͫAz3C!>3f Dr<2Bmjh 2CozSOonz|s7Kl3)雯y|pD[8+/ 6_AXޤm".QaQy+GtL)-]5.nM)W|θf i?s/#EӍ+gM߰rq'QgI#g5 $.ЕiL?"6GYm~*\:i7JC*;7Ylܣ} Tgy.\5>tS~N>WWyu/o#߷EW}L29|RpRפV)-|ݗ[jG ZƳ|5ܑyI{o^G~^|>/7_ @Kŗl>%[ͫtH|Eܧ݌au-C/?5}ګAp^o7K}oΗLJRtRx~Z #ml)g~eVZARp \'i-A)E60׉dP ~WTP ~HF<`Z4`zY(Lk雛]T$'5jFXQ/+7(%M߼$C+ͻ W6`/oߐdYn~ Z6QKiÖ=nŰpErEtEvWzlzEzzEz^K͝{to=ye6z)mRsnCnQ%M Ԗy/|s^gpdEef)`R\C(OYKoE*I-?m0Rˏ\YB(}*~a Ӯڵb?Z_ϛ |$ߦE}aӊg7|H,߮5̼lE z쐭:0欨4{>&wXKWف:0kG`atXkLM:5Sf"XsRӄKbyF7Eb}6֪y*͞RlzQ)mJ1LM/wtb0:g:,X{yY>6s6@徔`]ӔX~ZQM/G%g+A,ϊ> ȿ~w[lÝۥMLmZlïji]FwQKXڄGX-BOe  6 Q/H vӵuxlP8eӵ:po6>]O.қV% Rbf 'ܓ4Q.R o#,VL.\ևQ^#|AI ˓|-hZE_%gp+*̾=;,r_@.?Fs`ЋK,_Z0{_<[|+z MozM+OXZ}A/og[.ou۠A/34 KӠUZ9 %/b cĶ|{ #er#erGlK1}Ķ=UzƠ+3)1wwKv_I11*ʾR̷a zzi+3)(FYC .?憲6,!p:Q.\~zû \vw(^r%\5tyfjjc=)UMnjEdžgplx1s7쿂3qP=cGي\b%c?&n'I._\~Lܟ\~L-OGX^OryC\ /?+Te-baLnZ.VKlo~[ױŴfoN%,m#,w! /r?҂JѽmpUa՘3jbyQuFC,F.DI)jE}5uU˳Upxy ?4 ?^ ؋B,9,MYZnE! [~L>-[5jX^o Պæת75S oX=b6o;okazC,_nVM᠗Xz#Z 7Tp)qx:Xb(m^Kr#NZ^|^^E!g-Xw{Y~˲yKYbbis|f*v/aB+VG 7RC+_;,v/Vӊߵ0ȵPsf)6፤#xBy,֓ڋVDRyvyB(ω Z2yuң ]!kE, ^r#x5p5B$/(RiT!eSuQr դbt05tP.Ny}F`ԡ'xVa^qũ{Oi7E wHk'wLKU⽃'mVHZI "q|b| /T|{ʿ UboT)M7pmCß(UiTiiT[$ݨҲ*qD|x囥@585|YRKh_xH"OB$'vyQ=bV!/ G>Pߢz{QoLx~)EZdzC*#ιdzCQoLpl5J#J?|8?G(k`#PZec,(#w(3?^ J#|c#N(/oyz3iKNe(Tb^8|{z~wk5pKy "F[l/"|{ z9A/hzme#y<r;QæA/g~7o^[<!CDEj^ ;Iu-H"?&;OPeZ t \Dyu9][\0޷e ٽ fE/ϥ#y[ڵފ^8f_8|55fly$jzæRxHϧ oR.LmtGlZG<>: Qf=@~n;)يP^0zvHB y^p8(~Z߾R-JB&# ;|SXQoYBf>ZZ56J G4Vc-k[k1)gVg(upB~:4 w_E^%1atQf^=J*Eg$n*zm aP.z]/zæݦLuMiM^35fjz|ϫ|nzT]Vyj]t1%l^鵿_ Ol(m6L^Uye/J#Zƙ5^Zpȧ}>/z/I#?;ZT0ٕ].mh"PZ-NKoj ڇϨ!E"֔*mvtj{G 7CF~L&iw`'wXKLT^W/X$%DH"?&K/X-Ѿc['V)SCoD/Zr@j׋V}KYh5)h5evUIN1J"?#]>˨ '4p*r6;)H%+YhNΪie"CUU0IϵLDlER^ދ1}5& _"y|x-lH$O YՊ1cokF#dc5a4T F~:eE&YE/?󦗯D|0"*9QNgkRi1M/ݗRZFK&LM]k zi۵D`#^+^Zub LY=YKD~R[ń' ZnݛIE,J%{ƊRɏO\zHH&=VZ?1v.XMO.V-P5H5@kN-9~p &ܬoP9*8AB~avc TK=/Pml \P\ޠR`mP),^Zzwqjc+jFݘ}t4k%ӋSK.N-8/B~XnRaj5>F0 ,>J#eJ!O[P p#xPϵ[`*(v(j4P4CĐR7ej H!G0@i,_T)QʐBrM/?^e0l졬^v8X4bFo"ARȏ_ CӋ"< 4Q4 z?ez[A!yc!Vcۋ[rc!V׷,<3?Py}/zh(z/~֤'6DA/2Py^P֪,qQ yvHC[+Hޛ`}/ oB~:{I!?I!?&JJ#?&z)iOM/Vӊ; RȏɎoz*gĦ^Zfj7߃(TiqK?m1 ,fE~[7ȗ6ŬUm/dCOots?x WVS '}yM* W T.eG~pz〖2pr Sni99 dk26jj}$Qݽy@Kj̋U˝.Vў=+3~gY-Ǽy>σjjYDB hjJ}:\$czB 1c"ⶮɳC-lWӊcT eH?qUKmӘܴp$-wbj̦u8b^sĴ.Ŭ1AZp?҂?ri[ wi3X4||,`[8~`pX,r rCRȗV"V;J$_ɷR(!J!VEGoUĺ&<_̺ ^>*0x g|`."ʠo]يZwkT r˪ C f*zMޗB]赉Q or6 f(p켕ϵdaZ9d^6.5ex*Ul=.^Kbg64 z WsB&փ^^k[kUk%tyF ^vʓ";dDs)c^3&vx˰W"=< \F:ní+䷅Brs(׭ϴR-hM.fM.d Y.jދX+M^T2ڋW.Nu=:&m: SSz S$]r R6(HMoHM퐀J$?/+דE2o1\GpVǑ߭,J&->[#"qiY>A/oyp4 z)zq- lRɗ7E֗WQ[)ziRu([kKl%Z;#kq^^kVM/Jע>}PɷVM]kk>A/9[ pKiZRɗWc2յlA/w_ZM%:J%_sx#_/~([O} zyA/M^~+< 3?Sæײٛ^35\{ zz1Q:<^CַV^^\^U[_)ɏ:lzv߫jˇ׎,YKxa*tJ~V|/K)n-%kkǶ[˂ƒϰC{qkqdS6m1{Akz1k{!]+ XNGOgAp冑D H~,U& THH$?#@(pMLgLS ޅ)sK$/O"iyӁÉ$?J[- }$?{~\Df)H-/_Vͼ)廪j5f}sz _nM~ZՓN&#&v^ղ۫Z{s`Oo/`Z[r`o-2&ܲt4ZD!eE ]+ХlZc xplP4 ƠB0kˇ2e^~#p7g9lzgkA/5> &L^Jc=FwDdGDV#D)|yMz&~xA/[ zi-;1&^*zg~Ǩ]jc=F~j&д&lGiXQA1!M^Ƀ^^kK%&Zc=ሤo A/Ѿ?K'?/5eUDiK:iTo=M:11R4 F M]ށ׭)I(?6Xfv [ PPBm1er&=vv\x1kc+dM.bM/`}^R8R~ߩ\z])<DJA>]jR-Hiũ.nPʗwGo~Evvj\p:<_sf6^RRljTڅUPZl4v8o'K~N,猪d&?LCo`vVjɨkwT]- g|ݕM#ćKy^$2Z~ %g+++X'\b W`uxe,?X oRxN`-|@Z ѡikS.y( XN22 U[_k4qU*wUm}֠j\ɮ\/| K~<{BުVEӅkL c.y!r7M/e+J&Ouܢ׳‹^{(:|%QsKi[z|k!nR3R ozM3nziK}v_z];(mau6Fk+<2բKeJ~ZY-הALjiZ?iK}KSbs%[찣 :%?/.xf\BcnH';ېN~[L/vW:ƒhw!ܮ~22O_,X$UwNH/jM^B:-+VW(8i԰{dU]Miglx &ӹU֨yOUן/R-m@V~n[rqs^ؚREӹ"ʮV~Ln 1aH+;?S&#)߮5Zn)<;DA|Za~Vm<ʲ? i!eP*-t-V (G8Za- D}kA/r6Zy!M^נA/M^F&I+_唧렗z#TN2)_V~W_(i!A/kKMy_lU*,-{_6QRaozZ{l!`2NXq^*OZ6(St(դ\+Tr=MԴ{IOl4K$?&֩PclH$?&~M&8$ɷZNFA;H|rɩ>\WZfƘKuy]z,lT%&u-uij);dArnI7ߠ~f2eV$-L:o .DzM^^6QH~X2GI$/'Mu㥄W3^(ŒXCwLP,yUȃ^{yTo{!ך1Sg`g^;20%g CjMz75ͻѵLM[qVf ̅%< Q"nld LX"|rR27dR2yoڴZx&gmU_UWߤZxJ)Z2 nLMnJMnH-@J-!5!5KCʋ+we_+z}[lšR]0jkbKz9Wj˹kjfMkj@Ӛp黦<&\kSE&ɯ/z_d*s^|zɫ^ϒL^&gH h[jgԷ6+3nx=nB.~ʳԟWܒTο&/U44r/ (K*OhʫYjT"_ 2( '@Y\*?,ʂw.,`GRʏv+e pmAȍ<zu.2(;jx%'?^RʍW3릆MMFcM ?uSyu`E/% -Ǘ"Lԍlᢗ.zYᢗ|VBt릦׶y%(S7^eep_lr<.M^WM^*zRz:JRxIW xeIg.LԨ߆޷xL^V8Zt{%g]IiC/'J8]T@˝g)Sћ먙70XW+(z^zTD Y㨈e\Û27<l`খI+XZfI+2pY_Xl4QH>MivGy[ШRڤsTӽSSS nL- ]EoL-Sy;ط٣SI+O¡Rk]SaE+e!_/ӋV&:^W)knf-Mknm 5e3E+zRHy,J﹁)?ZkʏWV~܀Xs܀R]hBVe{fkʫBZR_^|֢E/;^}_kSBY:E/*V~ )/ӋV^ϢTeԯg ) Tg+6e}fTޛU./hAjÛU nVyZZy>ڬZFR/T9| U|}CH_\AK,fX/9-0+zx]byxCbyސX^c W^.Ebejh?" (cZXX?~XfÒie X+0. },<_5~oXs,>gmc,`~y:l}gm!7Dm1uuq "+W&pyP=ʯ/,:•B╍_Ҵeo,dX=z-{cqKE.zx1 ^|nz=yKuqݽT^nzM^u}Kލ!;g57}KQ]Wx5~Zbzo,z/z¡kP,wKQAe*{]G_ +"ulMoĕwopӾuԢ_X~H~8)™1|P2byU$mW^?ĕW)#z,LUby7=qee׆~*ڽM-> I3i`F`y5Z<|cUhgm\&Xěٗ TeZXY}"WhOkA⍪YTkAjts=f2zcʕ[_2gvː? *98˯vԖQ@'ɦKkrJGjy?4iM0jy\-?[&pMkjKӚR\ kr@yMm䀧B-WfZ~uTiVL̴lkS^FXj5eenB,?r.ziZҴJ9ZjLǚFdyg MT_ yD_jy(YCK뫌g eE/E/K-zE/MCeYz7nzC-P˯>Ӵĺlw܉]^/z]->썡́9Z'.+o? 3Fdyl?2YYYY> 3rjy7<? 3kLwyE|7\-]<W~i.O>^hizB.Ο3uH^LC+Դܴ.l66ւn?oXyWjiVX\+Uĵ!"сծ$Z_󪐽OJq C0o^96L1(/ƿ7$3tφšC0G{Tn*=, ʂ:dzXWڢWKjՆ>VY\0/ &M Kx OpOx(hAG p%|@Yu.^6wTYT.nR*^K̯7.,/\_E,s#w3 WD \M.X쒨/K-z/z6 ^gد+_k$bۄėE/{cXޖxԷlƢ4-zYᢗrQ~iz?x^Xy}lƢE0?\嗦M GF"H~a#DtaDZ􂨸p6I0SXz-+D"DJ0eK~& m}f *~VyrWx i]^&4g0U|e, '1qY! $g!mvy[DwugkAߍq辤bGYr7e.˳6nI0/pxC&?e,G{#I +̟Ҵݰr(2Wm<ШDgA֜Z鷙{d^tSR-jߋTnRPX&["<[n,լ~KD7,<,aݳok~^Z2.얦5tf45}̏ͷ5?q{v}^4z5?7'ͼJQ]k~fT{~ػ|{v+ͼ^=+ͼJmdcR7~k^ᢗ-ga ڌ;Rl+G5^a~5C42qkыf~|?]0i̯ HUj/zyZaRCVX7"Z^6~5nz#$^I3?E@D .%{zMhf~x0?/K)xKhdcf|p k3n>k3&fͰYMM/$`1հ$IZ1i<ʻ35=vfv#uk}˥4y^c練 -I3D6 c̫SqD6*CëI+uȟYVX]c2#UJ̳؆2?rtUZ"|K06P➵H1O")66\Gy"Ƽ5+Y5Y}1}ƷZaՆo|+$&{,،WgDJƕt{Ey2=}Jy;{eYGC$@2R h^Ѽ| R͟Rܲୢo]kGE-oPL[Bw>'zÅ7( GGsxp%|a Wphxʂ7Pvxpe;B2|ehژvۜ4>*0󫅍 7~v2|Wo|^9Dɿʸ,z'bq}ٽͭZR^5(/vYjM'q}y? "'vdoeH5TvRe9[6cy[-[.<.ƗrYᢗ峖_DyuM^ˬ֑J|/E/E/%x/ǿAsPeZRB^-\|/z>r_^&<oZ^|^6~<{`\_^g)}5~ނTФâݛkFKO(SC]K&NZ$yckpSkW7̷؋#,!Fzyy叅EV|_V"dYuA;Z:IeH\\ 4v=ycrqyƅLCrqy߈deGT1Q.Q$׊\&~\ߚRVyk6ca>sn*.<٢?3(}VV|+KoR["~B̀Z3 35}˯fy-5>/2{qAddR)}k^gf`j*Syu53xI/?~'} *T쨛^ߗb̯Z^phWe1f7˯R^Ƣ&U!C4-zYע7׳(Z+K/)H/R=WE/kK {KӚbcg-z)/zʋ^pcfXdcKӢZ*^~! JzeZRe_c~_E/+^~xU^^fOϞg˟~0mbdWFGU)߳*R*חW3¦gOmڍ.E?xk"̫UrpsKOjS,wٳ̛Z^`VA5Xjy7m^M{o\mV #oZmV^$ƁX1fdFp$?3Q8Qٺv^W&ǟE!WY;3 ;̏o3n0/ nR߿g)RVk^z1Wq>Goƹ2 5<0_\µi㯅,M,E"-{З܀~`eZ0 %g){Sy! i^0ķ-<+G <ᤔ>C/teǧpch,\x븱pJ{Zm^- wzAm KGGopGłZ ^,p9U (ˋ^&ZoT㝔LKlT}W(-{йW o`=y-ldͬhh=(p5^p( !װt@Z𲅋^;ؐ^~%2-z7e~Uֻ襮襠?7_^ʗ36qٌ{˫Bʋ^*C/כ2*Y}x/rPl}`E+^EHQeֻWG)޸,5ZkX6~zE^ l!C;|ڊ2RM/SI.B<6+)Si\` RFWy5*1YJ7vkvS˓_RfٶA*LmAұRm^oaXegb˧4\kI,!^{2qsU)5!%_ѷӈ%c?4ֶF{}-j$M76%+<1x$</-J0V^`*W 픑LUeZt-ĨEMkVK~clRc_1>k\+c߾5۬,IE6\{%.R0yc]~T_׬XZJk^, B`kM /jy>ܳZE2Q_U5ZL ֬ZZ¶f4 qy2e5ffE%Llk^3-z)55E/MLjyRkM ɏ5}j:lƢq]~|RZ~5~Λ^ed?r eR˯RLُRtm~CjA˯ g@NjyKZ(<+%F.O)3&˫޳,x ZxYf &[6Lu׌ssŤұ?b\\ -?,쟩ɮ9qě?Sn\yBsX@jiyV`Y!7){Wj?|J^_73\ b̊ZgɛTB>.k))Tv_\_ޥ(ʳ TV>H*gV jZ_.0?$Is, URy8V_o4RʕR^|YRxBij9R?')izL\I)fPzfg)=J)u#Kח؃XmY~+ 3Ge?LOe'{X_=zzXW“E%:h[ta ?P ~,!ZG ?pXo~ఄ8j /Pv8LL??&xU& zsp|)zVqͪ Vo\=hyuRMjdͬZM7>lp=jAit7]757,(s"krrky$_XR]C5c5ӢJkZV2^CiCL^W^¢E/.K j/zyag-z_:/GG)l +/CX/f &'J^-eE/pM/g L=_bd4qB2cbT,g!pKF׮]or&J$cj|skq㍭376ϽE^.P՞y-Rn^ѡ (O$)6QYsLE'`2|qɟ27ޔZxCvcy4kM$'^ ¾nD-i{#vcyRh#jf#>Fmcm◥x: (^n(H#O,z_< @T؟ -{,x٥x68ʿ gϙRQk#6 <_ZɰD(7&*7+<0̺ _?b,E?mʿX*{ L gut|eK~~=KrW淆R7O)דx>iDߢȟLToFߢ Qy6v0 7}<[>hF-H5UyŗdB yrœ1"OSmMӮKzIwW Ӯ55`k: t5`k:@S{U;)ޙ\*E &=Pyu/\L3P y}Of*LX祐}Mf1ʹ~=k-+zE/~kRȫ+\([=Mke d-\Yk)F|m"p(.X/ yuGy-^y)JE%hE!J-z.zʋ_&^땗e iK{K^)ZuG5ʋ^q^),eHRȳgRȫB6ȏWVeZ(,|/E/׮S{r/<+d)E[*IϞR^>kKyы_)kiZҴ襾<L)S2ebyE/_^BC=ڹrX?:J KfPWaǏ8^&ni{*~p/NRxUHvK9x/z)=/ e ,SҴ|C8~M{9XC1T$g )+IOe^0!qPxxX?>X?>=WE/8T*|jxS*EI}| {JxVb=M\{*|d3e?5/?OHFOEeO=dͭ@ؚrs7<.])cAس&r-]geT;^sWc9ٛR]=˰ )t+uyRahJMmH"MHI˹+l/h>7,҇[Sϋ\y5}صZHǷ I@TUj赳J^ʞNƫ1_^/W) ^ ;FYAez3^׽^/E/NכzM$a'Ov@$C/OJ?z^'l<ޮiגh7eHAƻɵVKjNR* `M% kZ%g7#(z6$BYH4\(^)= ŋ?ӥ㏅~ŋE#!bS淒$-`m)w'盢߾6&6}fͨxUh3J/mÿG-ͨtoF鮾6vy3і$_#fKLLğ'}Л*^kgE/;^}WaċyVVa<°,汔skKaǢ΄M:~|ʨ^&>kˎZValZѴ|/+\7xäAx$4qW]կ oz%W A$2QU^;`WJ|/KݾW^#īk4;`L+I<+XŋIid{WS^J'qƦxd/vMM. )\y?[ n-SlPu Rş6p,tx8+z7F0b*P=[V7gyDaw2\W0}jgFkǯZ]9.a / _p) ?KEE/~pzzCnó)8*dH~+%_;+<E-h~ͭt{XVIgS^&rMMKc]mz]lz=9rˢz C;ŏ>~tԝ%Aiz=kR~uTjz_ï E/e Dr|/ovV(:PXm2R9oK9drժ]vyIŅg`tca˚U6hRY93;{+jʾo8٬.L ^.mc"M%Ot))c:L9L供jl ŢMM= _# 7\~SWYRB^gIR{5kf>_yk9^kEӬ{%<;ۇuءJ ׳(ΤWzJ6cbYTo^&d^(Vj5k-\K. kEI} ﵖ\/ -K/Ӌ~|zR//`^JkÀz4q(>n-=z> _Bm-h+[^Uܚb2 :p X‘e@?o`Muƕ)<9Gstsc1mQe&x+CzHiHe6vbfAeHƽ94<ˀm UE)zL ,x151ة(>y[MoP-`]+֥CHNM~{u6X[jT;0>M6dGUnIR#>YN!=KQcDZ3~4Cx,y0{%ׁqTxKDE瓆?~֗p[-\} cqͱ>ϡߛ2Q gٶ:-gٶ y &{,sc6 xhbPjߺtC VJ<zYRBR ?L s ~} ͠e ZMm( jn]p-wˍPY|aJ V}˟^3-z)e<{wԢQ(]K һuJ =iy[v_`G$xA릦3?,g\G[o7`4C{x&d>|gBJt))DJ`-z(x]}ы(4-z9(^^R|M ]YZ4>RpAE"lhYd, +6+uqlYNMdA& Qͅ'Kk  xHH&T&W<|?QZDkUoTE&"^eLVFh2uJhUib,eׂeN. N E^L20Wm/Qm*D+«ɒglfk(p\ob񺡁w) \&Q2Q]Vk m4pUW^4-zٽ^jC/x߇^Շ^;}e E/KT|cZV/" B?M^>^t~}EvhmO:4p(mq5zc0Dp5d˧ȇ.5bϯE_ym$zm" k{qEJD>B& |D~am$ڢHEώ(pUH\z+,^"\&*Pl!{/z/QQ{_e$&?ךONݗ|<氉8p_k\^&[rBq]x]Іޅ0P Z6n߆^UC/evx5u\R5(n;N/O˕ahݣ'GiuAS-~fvPBZ\,7J#zUQj(E)3~=g >3uTx>V 3/0\ Ih 7IZ@%qK]Āo$P_4トCWrU;0Y:ՐYT#zԍ6Y>BR<QH27"*d= ǑLf`3{ ϞKG_&"Y*<@8л܉;%aJ,K5MTvSO+D)+kkVJ̺,=r7&7(v6O6\! ^덡}/p( %f袗k-zx? rll#\="_Xwޟ+1^^yKy o>z)j~C/z¡#/]p}P`E.U⨰DRhJg"\c3>i)%,"P)2 S@(ϻp%{e<Umk)RUOYXVM"ŭV5Zr1uo:x}TEinxW;t \~vJ.uKk,aXbդxj$RI~$CQp/P-VD,xE2~ RN"ʘ>[ ݞ-q#x['ϴI"z8'!@8bh,Pm*dի[Jݘrw K2tXLh#w);E0~«oBx,OL=nR YQĂw?)wt ޥXR ggk,PJu-8PVY-EL=۵qMuz<'- )z- z'\aI%; ԃcDi}@".R ?:6ҡ-T¯ WT» o%!dA@\ ~@kӏ ri5 z C.I^T» OCϢT;ܽ腃I%xQ3Mp;O+/z_^^6C\ ~|K>;C˥u*QC/k)L}M'_D/Ld@jS-E'gP5)/>J-jө@i/R,wjɽ(w2 wbI")Ab$Eo /S bԞ#FM|/F"pƅ2WAj2 GΈrvPbbxN^6QGj׉+> IJ3 /*P񏏴*E!W =rHR fP&\~ZK.Т$ *7!W)פB*w3^B55ye)yY{ïf5M~mpa')7p bx7E/+\K/~A\ ~ieN 8^^67 |M I1K^b)Cbx^)G?L1M-6Q]bP2$KQxzRyxb.z)ҋ zc-x4`f,z_U~堬-/6cyѴ|/;jQ}oEF9Cʐ61w^J^*7)Cm(zeH9dHx{6򢟥E/{c #/z?s)W'bx2zen1m݆ Yg ̦%]kxyqr*BQkD{.7Ÿfń.눫6K2U?h$F7.Z߂Ÿ@ʹVdFﱳ+Ūꌭ\~wS"«AGn~TS2H=iEËԴq<l$sXJfE?UE{^T-F5@[?O9*d8@ƅ&Se⹊ fd\xO4ͤ^rx@Yƅhj-|vg1ȑy!_p {ԲؕS͎Om*!cP tm@%<> P&~ZmA'8, 9`!fXmA'h z4Q[w m; 'pOpGSLv 7ûpG37z0F&?&t_{-XYaT6XX;0'r5( Z=[.SRv`i5ӠkaL^0Ïg`x^,SJ RR&P^+5cZ|Pg@S)Wq-xNRzŸ4z1?&S / u=i5 /V|rU|R/BON=hgʓ$VS%?jv&@-N2&N-2[Zsqje]m.JMEERF%TŨQh3S^R^_e١_ 'gjyVjϢ|=f.jQvM\~V\{ŕzxЂ57Qkr]!;ji؋\uӰkkE}u^ ?x?+BHo|\rZ{#qMEe{,늽J= 1nʽaz~0m,6 ߻C6i^Z=k/:Wzx^6QD]^zR?%SwGqglwRR^;38`3EzXR_$z ^6cˎZ-\E/p^{)һ{^.z1(wxÏA3m>kˋ^p()/ߋAsRkߋZ$z7Wo^^N=*|'\&J{$I=*4]rzS.v"s?Μ+n ?rx6dlS-6ņ?>S3Gcaml 2 n٥uC)W+28<^(R8BT]e v;xYHA}3@:dxEH˦ NPIPR)t{P~o5`ƇgzxU2>^W  ^m-XMTjC+ç"]Ė/C3-kKD t^W3x@)6S/"^&vT*eO T/L xX+*ŋ3@ie鏔 )IoU[]?߱*c[!S+X8e9-;e8cA38j g-|o ڂ~ _еp+h -\A[ K8b ]m 7p @[ц8ʄ8ʤ$~4.%.Eq;|Uj8%%IįfLiX\ kFwT˅Fnv+TJ=(텭n,sw kT!ޥbK ^oƧ$~p|J($~ yR #sfC/w?2WzUh rԇ^F@<.1+[2BE/wAGxcAYlJf0CWJ⏉GS/ )W#  %fq/+f_$g /+/Z_4),1EtSEZ Pʅ\WsExۥ(^ogS~=H|j#@M\&^Q*?e"BMO,FMa[UwW ӊR-Jy6\*ez{G2WqjZ85ZEE-_z9*.<YQ+#F-0(%,bZ~0@e(B)w#Xpj,CīB~)Z TJ] 6Cd)%.E} }jv `<kW^k݅Ug+K+k Gқ(ʞ5% &)W)fL-7ˍ5Kז/EDޢwD6eZWX ~w RoE/wSQL^4^04 exԽڋ뢗Mo=ү>\[6ykx?9Bw2_^[ûB6cUc|R]Ļ{m7%c2DMll"~aJif/zٌ{`}/nߛ!B{2qx(Hd6s&?,%񲰛]|NIdŅϞfˀīԐ.SJeQFؚ,j-hy6^4j4cMsex< ,},2B*4ӚëV^&iSo= oUX(*3K=Ji\^pC7U7gx[Mg[)$ #k3( kEǶѭU.(8m, ڂuL}+h f  Wp Wн'h m#h KnhAx>huG&m>Ï.ʌGO~Vc;|ë`9#$b].Y$hZqQk/3c^J^SWE.Xk[yϢ.z7R?`K=+Wzx~p1"~ zxWZW^R]R_ޖ_-k8\d 6c\iԋ^[NRx]YȌeN^NS֗rx=ȥa.q=y^ 8|wKx[*UBIT foxu#RKe/HjSB7T L^r#99(-H12@P<Jv9UR vS 7sDTˡD,Fy69^-FM&_˕ׇE!01jG^}?d4 m& R M1yy&J5*ei>*5ޔf)W);~/rx ]ν&ԆkxZWRkŅ,,}OM_߿5c- }-0QN9û_F7P ܢe5yeu5ye)¶km!#)UE/i-^kJZzQz_K/^z~^dc-_y-(C !-^}1+^|/e{ 7Rl"?Y^>kXҴx1X;^4-z_Ï_[F^46QQ^!~rxhF]ZE/`ˌ]!{ Ï̘~{jS|>LX^p)W)Ά3BLg]׎ āρ-D211`r;?,{"kë:*nR=.hĬBw%enx #^_BgkjE]/XM*j53Ϝ+YwJ83GOc2Hr_&#|V'R-dYrs>‘@5UWe ^ϡ>[];~W'TPFW*}ojA*Al ~[v1Un ^oȔeq*MALzϜ-@]O`Wۄ C E~D%q#?UA qwhY8Zѷ]ug '%{nm ޳Lt B-9+( pmAtmpGpmv K8p-@ˠmVh N-..9RRdS :|1|)_&9ӯf vyW{JwK1w/dYJ̾7LPԺ,l]|_܂3gz!}Ļ0 +ĻCTQEF ]!%ࡗ Rԯ^z ~0WC/4E/E/_󢗥^KV<.", 2@fE/+ī-k-z-ru^rٝzx!"Eetx+zP?e(; YX5BZYDRsT."bϻPuȤ"SR+#H996!jE(g橇p ħi OON6]--*O?dt0y΄)PӶ[pSsQ&O=Fjm1jAb}I īm1jo*F2sWbpmFYfܞ| SoF( Sylߔ{zxlSJŪ_Vl_-\MQ ֲޥ !ܕI=;ʌUAZs%xmm&v R)]#N=+5E/E/eS+[8E/[iKӢBu1]}eˌmb-v{nzUfx[ ~| f/ӢE?̘~p/z1H=;{ʋ^ZRe3E/[|/o@e{x}ogK=M|E~TzQal_Jm|/z>zSoeE/E/{zx3R7?E/_y (G^̵EĞzJ=JQ5M=LozcU/Hr'u\gvndG^'jײ U(/Ty*L5g[eB^i*h^Ȑrx2>L6kR@mxd9Ypg.E||Ϥ¹lL x&/L2_z 5W?r8_9z,ܲX)]p) Cڢc9vn,<)%Z!b ڂ _L+h v~Sۂ7 W-\Q&zV~EY zm~_yJvx R|dW?-k֞5rΐrE.(|W{~Q+697𢀪|*g^m>+>`:ckXFԴ}!ji[lxQ*Fm.Fue(/F:%UaQ 5 @Zber.J-LC}&_?Bflx=BoiϵOJglx7_?5R1M-+[W>Kڬuw^kJI~M\ٌ5q} +«)kŎZ.6~M\J:kEt-(.z5q~O\=O=qTx$~E/=ֲ-\74f~-Q] L~=j˝»Uҡ׮7 R ?ZZcS>&6cKmu^Q?,53kTBWJ? oK-zAJ{-KTT7搥l fS ?O-ML-M|򢪽|/e3nz36A|^l4` ́ޛy{xw/~'S /1.z H-JYbE[>*^OZxR?&[RRITeb*_"x^̓Oɽ–bSbZ^vKRL538,0^38pђ5T Vnv"@r;2C~GvH% )7X`^h1\ 2 etP&pmh( ڂpu]]bUj05w85I6|E*Aդa՞NO;1Ryy j^~ZbS J &PC `k71[Ar9J1{];CbxK626d6~E/+\BMo^} ^ yM1+@}ی Ry=j& M L|L^t`S / 5ӤąIRw PR Z_(|& RK.HMygx4&F-02bzQ b$w1jb}71㇩CO2Kz"+Tۄ6}%eTxO%OwQyix?ndF<}kO,Yf>ޥNMkY+g֬+Z5WfI3L*6Q]k.*+/ ,fTqךˉz*x}_Vkg5MoW.z=gJ%Njf458>rْYү>\RZ\~5^ځ JR3Gx4+d5R Rԙ4 tWTYT»)6»B|˩_ZR릵bE@!z}Eތ7^걋%JxXj^kǀXfTE/~߀3,mQς[,ǷQimѲµGˎZ;4]Z^.oz/kS /l^&{|/dXSDqyR GYd.ŸB\ҦXLfrșJx.sYT«()[t/A«`%>0*70>ޖ#\t[ÏqCUg U|w-Ÿ6{&I/H^H)XXFf`xv-PIwJxJFǿ =T20fbHۼgj`RU>~)qe`x&5^xV3 v;xV30YuŸVnĤ^ҁf2QI-L,2qC*/xW<,ޣE}3H-JE3-&dIR-uq87?, d%z[Y,tqѱUr'mdo0zjax~fh X8./>wpmAc-p=ڨ-Ѷps tmpG ~<)s|R;裼8̓R'Хpet..d~+\Ȳ,μ8+'iϋë~Zܲ._raz~呭̓~4>6Q%zYaj]sjkzyt9PCU8r]!J-Lg`xZx,ZƔ» i ^*K% ]+TŸx;?j^uA,!^_*BSޤ>/T^J`J(u4_)ŸYNAk0ƋPK.B-[Z4\xZZu^hp)ނN\prEpW2[x,>~a4NS~8"(3Rv kWAj 5 55]{e TŨQKT/F-r.F-`\1ʽbTr1jOQsS UYR~W):RF5}RZxb*5k}»BxՌ /OvZS M^-pZ_?Ō o략ޥ6{+glEj43J-ZhLJ^ngTUj^.E!Gj]kz3k[ׇ^߇^3 Vk-zC=kjZ8k.N-`#3fTxFFw;D}zKWZF»B{ W3D^7^KvZս^JkE Ry,z]^;lW^]ozåͨ7R ?Ȩ˴f3*^ڢ׺rvYX|~,|/_y|/5 gDfF /%7]]Te{Z,|\/%«fxRW)O%xߴ;0b".j=)A%X,w\ \$df@6«Oi2()c f_-Ezu[ T)^̠PPmՠ\^mn<]S /hvH%?=>h8*N-pjw܀bFw#()6;oL{|+ѩ7 jc`˷kW1b)2Y[k^D '6a@R Oϔ«W5)>"dzJ(ea˗wɠnK K*_£/v,A_e6m,\ȳ؟s,&0Ϳ-wƂQt>GLLXX0ch?_L:ꠟq!KGBLp. ?ή!\B~u~O |kCY5 z/ hJnMpMY_B IEn!ud*ˉzYDPE|e ?"C*=GA ;iC02YNx%^j:#]?=r#$\o: dɢυ"n/2x=q{5D.e3Tx.Tt>+>!w QF?_yf?}zU![8}zB.uJZj׌X9[hyqMދjkz "*9Zˣe߇^rx}_-xk"*i5yZ|_; Y-^l%CE/z" {Rk-zn襚9BGD9{E/EzMC/"5JQuzыQpUƋ7\Xҽe^[c<-+/zzU`z9!_"LvxUaɷ |^ #^^0CP>KaoKi,DGCovZYJTM/e"xb_F xM>LH^e-6DnWqkbБEm / Y]ik6쎀P& ~6MCaW*x[XFrk0/# L\hE< Q*'g#q8Tp5!  V* ޣ" \i|88E6QppZ7v|;΅;>Z"T[{;>>$ndFP<ðkA/SCEE4Zד"x=˒f&¡הv)+C9|!JC/)wR_Rz2S!(/WKX8%\g\ޯf|B-fPX>ERkXe]B4~PqJ#:ӢE)g˼( Q VF->-L[xExZ O+#4H =[CBx -X)ɦ^c񢄷Kc#ƳZ#ƳZ!=qvK*B4S /%S /TD6jy=Rhăg>R ĮH%G ŸRk졄W?J '][%ˠ}ѱedٮ֮ϩ7< 9DvQ[ a>Gp<'Gٹ$y+H}D؎PmO~3$AdȯP$>nĥE c?K_/piq].ڂQ8-(#%fZR=KQNcn8K)gpy-|@( pkqvO%0p9yѫg^p0W8 -]ɋ>0  ٽ斛2_ n,nrdtdv>r=EO^C Co$Ǔ'+%2'/ޥ0 U/~/_>=?E/;j[_%>)/2S80 OA-u<9ѫPKMyxE}Z5ԇT>|8ÇRV5!Do"WpX <-,A3qCfCfm|j|o=hrxRW0^;nE?<#})r ToF000ht 2 r e}Fȼ>FmhvET1Bq #JUW^DdC! JEķȼ@νH?~D*:]j K <)竑|~6 gQ|5.D|5.)_ -竑LR0x?ߓMUPxUHM]x8R0x(XȰTvjeG){Eo[hzµ9`}H{  Ɉ>RkJKnzôe3<ŏdDWf}{(^L/ ~q)o2Tv$ǦkzV8oK M/{1ʒۋM/@ťHoؐ=.z)K=.L/Us;4`XrrK+W`vTnT[沈|/Z`nMYӇ/IoW M/cY$76|e˹$`67Lo$K7br# I/'0 /u)ImbM/c%W)3HoӋ$݈6^mۧ]NNN*q.S IOPų$ {&?e"Tظ|x@`mCkD{k & gH_Ӑ5RN^P(x GxVH=FP̽ͅS( ?I﯋}k(ӫ]&%KmmtmA e8p_p˘?py.>^[Iq]cz| Q=.fZ”&ٚ$KOo. x1 TPkhեI $?iMY؆<~d]xܔ<{ @FYH% RJDr$J-7K%E٦s5 M/ÒC^ҋ^wz ޽R, ZC/?JD 6>rsA73l nHl_ v7I9JX^4*kW 3:|Pe* <(!i44j)P9W i\=|ł7ᓙއ>C!\(*3FF!4~łP^d+D)ؙ1hoNhit,h$8Dk1e | ~\[(x) oD9 h>D#5V} Y|[VL{_BwrMIѻ5N{1Q(x?.O)y}^?j >,Ν ^w '֤ZlQj+b^oō^pE.}g4Ix >& ^ԚRr]nRTc=M/5s3xݣtꍨГV#{k𢗥Lo$F7ФP}e܉w/AkE%^n^ʹ| -BJL/\E/f׆Mt/6 &6C`9^^ Ћ^F`6 X7 g{eaw)!MhBQ B/C/:eX]Ԧ]fk }KIѻ7XRZU(xoT:x^&2VRCizput0st*EH:xxS,] _t z/O)+S(|ldtyISJ íH~%r"ڜR.|I<˜\J /,eʟ/;א\( P;ٵFٵF~sָ6ܮ5Vk# kQk} fP,xx]Rx(j{bYע +)+&6gY҉**|wA* ŕ4hY-ʧwBQtnz-qm\S-~kӫwpwPn+o3h Л_P~ ڂOpiq?pกx`o-Rb6hm r2B6u{.82q@/Jظr*->f_Tla*M6m$a7TdCɘCrhxY}(-° d_FY ݹCJpKGI o+\Ζ-\Ϭ݇4^p 8viᏉ_qnDA/jx«m`TQ5 WU00 FZF9(/RfwOpQ~? p{MF%?OMzx(P(.LqU & */FfP?.GW.Rj'FHo5;Jg Iho_WV8ƙonpȍ,A8.V_et\A[ey Gp9x 2$fZiݹ+[+XNKߞef{J-jyhF er9%7ln 傗25Fnզ7Rǡ_HqᗯRZxR7 T6QŅWqYɇAz]4!,n~| Ÿ0.N}1X)w@T?e"y教pe:F4J"5U^}%׀$9S(IzDk0 {0iуi 1e : )}4nL%?OHrx( ,q4 9j@:r%_Z=E?e"fz a4 !9!963 v>x>| E1va4Rە2܇ш(ߟ'9%[{]ѫ  #?Rl?4ԵⳆՔ (uSx3cۄO]iۄ%~E5w>f`p.os0r\Mjx(ypicʿ[;ʒ~y+EkQV3x*2ORokV_R6!C0و'_{paK͠7<+v "ۄ/[Yo7*2K eIB=ƩqEw^E_Z(5ʊ t[CoK R&Riz쨡7BH6>Ņ?A)/7$"mD<)1}0XM`C\bӂ\)(0Z2}-OK J=r!%$Wm/bxYXf@XZS&uJP_806J?~ XL)<"RK I+02«3ҷDO5> )V{TNQj5Bëwho0Wm<0i60s 0*xz>M? }0eJ/B>(O)4aJ oeס3)U9lrS&_~piZ3W|} 4{_iI uxLfT=sVY,5Ԧf|bϯ+5W 'qZuk^ʓ flz--Yw"j|,D;/H pT7ӚR^rOē]Ϣ]+E/+\lƝ^]~KOOᢗꢗZww BûpP&_T.]Ϣ]T׮Ku׋bo,zY5|/2[ ]?Lodl7J fp(-[ߺ{^ܯQhxxI f(iR^Vf4}~f兩vh YBë|HZx(H o_^ד^&Rhx7| Moh7az)I WϿ_*41QlVdxw_Zx*4EXCrgT]~ʕ4C F<] ]jx^]wD^ʇWnH j 7Vxou&` PVb0"1>*уi\[>Ȗ^Mk( u Qlxu[=m]kWq󂿰)Ւ#RhmXyBwwZ;ȡnJXU!w'tgx "-L(L\J R )nke ޥXˠr7=N-ڂ=.,\kt=J~i+I BoE*Bbx77Vhx(PָMܸ m^9uՕ2w}}2OzMmm^mnԀ)هܼh|M|W^xxS6qj7*z$w )h^ ,1+Ҥlĕ?˃9{Eo42 ;XxmlClvJ .:2j6&|5Ro5Vz$?g*Szָ ,5>ri=vppA[9!!S N=pӷRXp-NyMrx}C[W)mIל C ENwR. >=>[K1ra 汈aSrx}<` ԾF9M^_cv\(Sz^q"6QwCjy+5w*ʔ`)#s-45weox BûB^pqrLfxʤvg׈ZRWgmj]>FuQVPJVk ΃)FE q<:4"s;Rϓ"ë6vxVvn Nx˓^_cEW#xba8S.~٭n9oj۫rHRx,&)LZImB)Ieޥ|؈ c~^|6޻/RS( g)NoeAӵ];YmO^sb9zOa-8eN^,\zxm 4xХ(jT R»x/f5B"»Y7_]RY&6f#_a6656MU-~e} <=fRx7¡7Zn!ҡ7b~_T\xWH}Mm}0n;P[Yһ`MoCgɑ^򄷤H)%2*Hׅs7p֐ `KKJSJplM%F q]+O9Kʝ/ գTPRBRPxu4nLy$R*x(Z*x>دhk̕z0ʩa h;R \|FIAU 2}h{BQP9+?<ra4y{F9ϔ ^Cev |W*S3lK,R/-F#0mFyp] ҋh0gGY\ o ҋ R!y+lP VXZb??K B*+L>lZ,Z\M^lIQeD*xjmŚd*v_x &u0QFP,8((_ o\%~? b/#" ir9RBqɸ}O eam$6U7?O z ̲a H"7(RT7WmWJE !q_E?ȉ?j>OBʎSTS&*/R|P{c ׍) dnʣ.RKDϗo[[oz]lĠ9*Mc6رr[Jx/?f)m x:CJx"RĽY)] ݮ] 2Vn<5m *ʐ&J-ϗ)*)ƐJHqN^ʜXN^,clTNKdan 2TXq>T>b9b9~[Rrڂ>8n> q}|.I]:NRx^**VʠFI m~D[$L-*2$_L,wtaxG#)ֽ62/j9-!80f|э kTx0=J>FTIϳ2|B-TЛCgNe%w`^Qe nK PJxY؆A74!73=2ϓ\>Fm-BR«^grE?j:FʹׁKp)s"5# 2xӯz9v=FPH#N{}Vmhh(GNJxQ07N؆Q|5R‹` V#R| ]}6ңW jjמRSlrF#zy(J[g_{aO z^ΜYe *3y|[Y~>1)BVi+wʏ7ޞ^gE5mO[U@kڊ@ִ}m,/B5"̽⾰«<#%L!\{ ޽AsKz-XjKӢE d)C R»eb^J^JЋ^Z/JT~s]U}K2IJx7 (/zٽ7 R4{Kt^x_Ew()֙RDM{-hR»B-.w)1ƍRY)?( fz#|BJxƋ~pmp(iwE/uw )襦,Eo( f3F T~HPC nsIT ^=Ju0עdk(} C)ӯ4t}{V&lTu,b]-mL-eZom*|~,܆M-d ࿸ vkX!?6xu}}mEW79Ɏ>S6܅:!R\dGS6ܠ::{W>^&6ҭPl 91c0\cgP§7ɟ¯D-x 鍄ۦ7clmzUxZ~B';4-4壃wH=]o!>2x=1 '5zYpMx)xG`tGF ^^>QES9h5rMu hWD %A'VOyIkhkE㚴RV_,JƳBI+5iԽZpMZiZVZnf7A{rtT鍼ݦ`[§B^|_'-Eo|_7L} ~mg޸Fϯ%h6 ؽkEezW^k-W6pKliTUăO 6R7%䢞\TU>`Mo~6Xjp,6 ^^ޣ_eˎ2i |5s`o.k0x3?FcԽ6 ،݅8*LBӦx ^FK#w#^DSnf+cntڥ@rG2Q߹>zD*{GQ5/X[Ə^"BmO3iއn67Fw( jNNn  ]6 RS7]M)4Dס42Rg=r{BB~L9ӐV0Ob*sCxx4i{n5VC yOO0x70'-0GoSh -`>S]k\>-dD5%{75ˇ *R|[Y^TKӷmI-\9w 8 smXI{A)]}pAA3hKm4M+[Oqm(?p7p.v@Yx 3VHtϹ"֧'MYK Co=.Mԑ f`xO) D[tT lyO9]!ղ >&ʾF˗Іfmj99:yѧ sep#xF3.7_n1x?=^\~J&'/-KN0E/ezywcK"ӌz+8*0=F&7Z0F`!Cm$lhCQ7<=0UHoߘxQF}h xPU'Z5LJӈ8LCifqfeWQTyT 5za4⽇їH hF#nJ({g }xa F9F F:Z0gs5x;Hn9k攇,L!_w"TkΚS ~9k5Z9ks59k59+7ON笑<~Ѝjc1\ >&A꜋^x4}MI1%E/#4-zxY􆌼{޸Rmnzô&PLok5;W\ʉ}x{yu^:Co#^ne0t^2|_p"os5< ur߾/ix2ʋ^#X|֢{gz#E/` xXEk^Nܷ2 \ >2Tԥz梗Z\]ۙSU5<( c oz{=en$z7X[]܇6QFڡ8]ά0\ʝ@y/ѥ% 6-\$LD7 37>s/xYp=.OS .b 14;tAC| ګ3J~ָ ~h}YՈyTCj4nHkTnoH/Dq7U\?K/ c"ùjV,?7#lPI/ "zܳ7PXRX$+3=qr(;V~7. _m`H6qB#Jev}Ε[B|Lp''|L|eWn។)z*ɧ3ӻܶ -WNӱ~t9Wwo 궁 ڂgp8 ڂ>RxxA[жsqea ] OlL90M ~s=2$DX OYm\-0 4 <@j32RNV xR x!BA\9l=FP )CQDChQB*!4g偃s1x{tچh¹vp^{tT"x !QzO:fm^R7fKY])Ea{Xh\=0r(a4FmhH{QmOV xFkLa_0o[ȁ< >s+~!u{q󀚚Yj)Z߲ۡ"yM7bS5a64ogn| *ӞMY#x OY(}8{ыA*xK}hҹ^7M{2횲YkszR\{\o*xW{W^pE^ ^עp)R[i-EFw+饂襂EGI8k[\\HoE/֖T[ lᢗ2}obk=^T{-zYZz6KrR>7wsX*x5>rY@!FoƗEo\M/=kªۺMo{^^&7J^R$?"q˿dz%MH*ٍf7n6qKQ2x7zE-8WS5LkPP&>ruC_2xᖫWOHnjywtz'&y⽉@0s?Y~0!!6`)UHᛚ80rD۔ :7+FlX.^^IJcaɉ޽J1w@*蝔? & =3]C$_ޕJk\+ ]+wyW^]#}0|{(W3[^8|kOڈ˼aK^ »)x{f@*i֗ flj--KӹUm[s8q eNvyNj xvB>-ywе ˜ .m;9`6p^kU tX_'-$»Ԍ *_JZY E 5ްFq~&敻lʋ^ 2Ҽ{ [^~olC6!]x/޽An7%Eï ^-PRS&[e -^6a4r/E Ѧ>KkYfhhQʰ o5>QN?Ər ~meE@% 1c1rY+1|95\#K j-9im}[?}< >(Oz맏SDx7QYD?qO! { FE/w!Ԍ|bkkE!-BK }Z7Fx˙P-ߦ7n_r|gǩn5cJ o/Kw^[3TWys{SXwzS!{cg-˳ ^V(/zE/;jf,zi2'4)"Mh.eEUxߔrumv7\)M/w f0B80RP@xo4nt))JQ^|nt)j()э n}Hnttэz*yܸƝnn')鿐χHmhMF1$?BdyHI/d#]g8㙄Oٙ$ F*2TUZ2sxܽ 7`So #A~>}JZFJrW7! raz)Rrٟ4Sj>CG\vBs)?'ei;AXYѧTv<,jvo0n𭐇ϳҢ/iЎ#§5cE#§BjhCqMߞ U(^p(;"|+d`SҠzT3>y,ܜTnskr.mB GƍXuZT)Wa[>(eж;V`-hu9/m .}P`kÛ#Ԭuiuֆ)'0H]N` U['QU=0ţZgrkB ~+UNZ-!)ʴ/*}KrI`{@K䳱 =ܾʡL\};S*D 9^7޷P ϢizO@'FRVׄl|Q_Doi ^'uDp$,T4Wr4d}]OmkdiQ!ʂ4r{Ѹ[Fm"4hv ګ eڀRQFm@Y}SGgLYS.y {]4,fz L >*bbGb_>C!U?e(Q|`"*F*FT1JɨK(i[v^9m Fyp e(}H+O^ -}o|V8 zL 'H ?Dc§ ?Q/Wroײފ檣o+p,WjDh;,r5°\8X$p3FnߗA/[x_`so)|^ߏ*plh2mz^qDLo >|D_s.-;|JqAPI)&_Z^ʦ*} >}Go3(R5|ez#~T N/ >A/ΉR>θ3<nڛ] 4bw)n^i,q'\,#ϴZ,\~t(c XvJ#2p< >|Ǝ ZʳQQqfAK{UB-%{ǵ>@_jA)ơ𩎚(nQCN>ͦlϊi|(lJ2WWoH|nOϲ_OWݴFj{ۯƝo 4#>ԗ5UcMl|u\/5Qjk|L\@& ;~=-|o&VY,^?DjƱZߎ*cJ=XY^Fc:VϊjD{&V7ҫ;-,*tkŴ~3^;rKZoVH]A/M^TQCeG{-k/Q߽ )r;0LGBK₰5Q]4.H\ZP`}a GZ҂r|5km|J F \Z /XUc5q]ƨoV،rk#ǔ2a }+,'-ɼF%ʝ }_,:2}+VH!&c2Q.nyOmNZ~,r [.f6΂)B ,f/n2{8}ѿċ=f!moeֿK(ɟ;?g,qO0s3R Pƅg(3,67<=e 0?grHf.D'qi61ʾnw:ֿ]A3*F#,.W1bMA%Y҉w*b4T{1Jÿ67`F;r-FuPL}(}Hc1ʵW2ZSg{.ZRi;ki{Mxo^{m{KQB>֪ƽVz :1vj5{ފתq7תkU)h:6XTkoZ=*of~;vYLo`s،cEzaj{;:A/e݃^7cވ60QlNJ}l&˩A/[x,\7zFZcs^azC5?vZi}[_ZRTZ:U>&.pZF+;M/]J@2۳9{[ ~㜀u^Jχn~Q6\uQ,u#j|ezU~VxRTMo\=~0PNMo ^ꙿ ްOnhb7n+ot5о |?q6rcmr%or/LeB|@AӃ)|-Ÿ▫Vw(z[FvzK 1EmqϪC:*Ɩjy8QsV&DOz+O\q1OZ~Wxׁ_NgX?و\LwEHr Q8|J}lTDK3Nێ2}OQl|p+h@~|+ZR<L])YRn-'pB}U͵Rn5o ٌW+XVhbC=lHֆ6*4K6LQF<嶷50|7<(m!Q[BHob7Bn& ܐmT[z!r lbXn[Z@ٸR68JqڤR7SL)&HcÅhh" Zʄ->96,>4)~o;3nj 3uhY hDB4Tk!= xS1 K_ѸZFQkC"U^1b4W<_hˇŀ?15St o*FCj1}\Kkm 2>bޥ5 :;||U i$tHBtނX$&tE,*\/VCb&߼X{WNUooG*e_/VD[bER=ob5kZ_6 c:}/vtl(R5!mz߲_ΏVoq-4z鍸cEF8}A෎:%΂>/6d 9tVH[-SZR\*uY-)M/_E-tZW{Rsl-ﳨ>R6H8bExol[}^:P|/~TM/W}+kz#eT_Oq饢OHmz&z);/w.΂fz[_h: ifjofMfft6ES?./ @𭏒/F-?xSFtpfa!xԺE]O"۸n9#f#\ ذ7|5s|ʀw{a{$`^O/YWRiQ'RBM0":| |j/@ qZF++W}{A2@_ùg;א\#k`ʣ |ZُB25Q"ްEme 7Rb6 ٷsBj-^)&V f`G?Oyѿ Ɣ|o)ПGwJQX|Kh ~-~| YpEyۉ20G>xc'%Bhs=zCYqQ WjH^F^rQ7:|MxW<\;wP(+UF{1[\u6uT){CJCV^}hz9t ۳QxЋ>;Y;ƒ^*`M/]^68}(,:04O:Աbo^~?Yx-)/rk?ؽ7s?|/͠F/blze-E ZMosZԱzi2dqmzYq:N h2:X9LoÏD𽔸MoheN;ҦVv \]<*.u|f7$x.7~G!1 m!egͭ~OoX7'7)vl Mn&7L"7Ddpǀo'Qf7NHo(Z rB^F ~oI,b9+uS&jqgOZ"KCu=:|,TM*O)DVFбMߗf H[.L2ta};׎5۷F*xV3qm}kh<,}+o{JoeǪc]([t6=& Z^AR-~oS>TO_#?͈wCɷ-5WP nH&7j.t;|?R2\-vC\8-nn)ß龈Z./ )E"6 ,EcJeZi "E~/!JqQV%B#^_>Cg d3gX Jg =g\m816}De\pFHPeh}!8.ph'k#r'F߂uKP(G3JU=-;Eoe@YF~w_hȸf*JTSɁQ&i{zQϷ4fy{=տ@)=Gfo/T#Nal;{EXrCu,T)[*BiǾ1E {黏*[jd?oB?%ߎ>A U^C P菅*GƠ{,|ߎM[x,T}21ֿD6͢2zlX͢BK1鍈l6Zޞ&mzC6&PPmhƖ`GdSO,-h#B6dc-Ɵ5lFٱƿv!p۱{c-v =-εc<^DFr ng侵1־/Ձ[[-} _UkkiԾ?ϟ}|݇,{^߷Yz^z wa^N uz½咦kRt6 ˏbYb >f2Ymzg˳Ҿ32BWmP*L>zKMo4ƒ^ J]`\lz\{*6q+ʻz+4Tҷoer#V"lɍ&7s"}Y*{®4▿[SQR-'hbe=́l [聾 d_⾷6OXx_WOmqcx ZFtՂ5ʈUV 3i c j/P9*VGX%4n 7 wX7R܏f"PDR/,UaSVG=l0}Iy.O!_}RJ^W9WBHho\RV3h^8=&QZ· aK: XP>+_YP·ߥm_>^`-[-k҈/-[UkU me=W ̖fP.'pka9}/yl6"Ml9YN mHئ6bl d=WG۩kug؍.߷"A!\F&rmܪ-l#z[FAjِٰE,>q𯐄+e|JIڬF)rQJ] BYd{Xc`Dg$u }'9+vާUU£ɽR]eO"BUpro\}s>3bgĶ Q.8C?ˑD|O#^,B#V߈h(b4c/LOroc!MS{4_B{ofï& &%խy׃ٴ5`ZxEЈ#]Fr/eL(bPm>|/M|/j{;:eGHoz9es%Ro*q޸* ?)^:Jx R*{M^.[ CH5o|+dRqҚS&n'7!{#d˯%y[W:}КB}! n`p㯆=]‚"՚ u`q9tC -hC<\/ۚo) ,7u?,?,s=H|=Bh{P(qAxoB}khߊ=eB\-z?;}q S*{[@ؘh0ٹF̲@>bih>‡ע:WFt4-zX/8{t8VvXerϷ++co-_ K+6F?¶=b.~L! ޸[üB <0L6Na6,rqehe8xՑƒ|ŀ.UV%RQSL)/zbQ]F8\+Jq>=Oθf^tr !:C63M'!^t ^tFZtICtFq=\8k.>}(#DmB|ȍƵC/Fq1)gQjB%"VfD шC,D#[}DZ񞏄o*D#[m!}򞉇 QS7|d/QS}±D 5jtgL tSOzɯu*Ky^rqPN c=U:-{:Ey)*xoxN(=h{ N Fyo޽N _w:gE ^$d0^*;{K:2uԝ )R5BԂ2C+XHFvJ;{:ʱOa)^OaӸ [Fx#A7>lH?K*eZ>ea/Ne)E^/ B@g:%N-<yOXВc;o~1v-<%WzI=؊RFFhz-dl}SmU{=T:8l'Y`N(O/f[(ZShAu>_-#׆(?.Sn҂rKy}T9O->(0 z˛y/R[}kZVGaBՂVH9/[ j\~mXnrgմu3Wե޷o{*Y|h-x_F-x[ S''}yՅn7̞z#ٷ2B9LwhݏJc+!b4s1JWjr~~, b4¨hȬb4f1Db4p1>%}b4(g^~ʺgЧH/nOñNu*KuƟt_vzq^0ujDd{ ש!]{*j{ cl{JEש#ֳO{^Ƴuƛh?|_R7zj^ЂTyˊ`ˬ&|W /ިro{*[ ޏ~³/v wvInHЇ5tV{]|[ށHnv_-oHYT{ཌྷgTq.E/N1.j{sywr<ЏRL6o<K~޸X7Yg-woo{Mo~ -wFݗAi{Ly4nzC65,zCyZmv#ɷeDj]=GLnɍ'ܸ 8ۢ<}KX3+i{ɍz7P%elt\΄H XFJqqj–̟a#-x?M,m[|x)JPm^.f9sv4Dtcxq2qӷ2;w7]/*5wa>D`jHjՑZrׂű`J j5TrqNSNȝ|NQ낸[ ַ'ٷNnV#[Z񞷊[SGAu" +?l>ƈ}+hMށd)aq:{`Յ"A-MWj*jTg5ֹ {E?]oK$)R^u부 Y[8OI{Z \Z (5Yn(0&^)Bg#򵙍jCK7ТY6.6 h{;?ȥ.tMun^rג@-ׯ-yO{,| ]RyS,R;{)l\=YM)NjS(e 1Jeۄ3B߅g(}:{:ɔ /2|c !rϰa8Og5 y;T0tZB^Ta _U_潝' !˙,F60 Qƞ0,K=4aϱ8 ?u3 xӖ`eR( Vr:FMU*Jn1ei&'" }9lh1Je)Ѹs}s=\9ʍ)KzW)j(v{W,Tdm<cwj۱ƳR*=]kJ5Z?Zj^{ʡl{ZȅeyowfFԻ_L/Lo4+^qx*Ǧ7Ro7{zDgۙͷ5V@[E|̀kb3L/QZ Beb ^lLoh~78gF{9ࠗbG^/qm_kztf }eP:{[ i:Š=r VxK%Noyo埸-|_7g7}4^evַ}<BZR<EK1̦v7 a__o;v^ FM} ~L˶صe܈x7 K_؁ tjJ@n cn7\FǢQ\n~3ܷmg ԇ.E-OZ~ӆ6Z̸{Q[!F-{_-d#7h/S~,uݸFWޡFp}=q *ׁ?}OmB!e[)%lqy y/sTn *-wP;{:_y'7`:F˱*x B|õR?=Ǖuczӽąp[?Nq˾^_ѺBCttM TnZ?þM k} .L#ZEFm4&L(`uQ-(Uֿ~vVZdiJ9f`J웽ׄh{Lqo?wtR4Iިj2ʝf:EvAkz#W6q ӝֿ|L8rf웽uK/ vH= Lo$?|/K7bZ7M/j{eʟR (l2^?L/oM/%7"KFo}oOٙ倒FvKv 4 yx= 7I2N>ꛐR)1 2i^<`zyŀ>˾ҿX9P>LQa&M/ >S`Ce]9`#1E$qxƍK.5ƟzV{Y?[.j弫 ](y6C*6"8s`j#)SN*ܦZj#z xEc{eKl e9 ėظ| lXvWmoc2K[LpgbQҾB\(c[TdNhAb^wFbʓ,ӈ]Lۦ~SǪWʿEˋ}豔IeL*o/ yǦ,{4 Z!q ڻ".iE}wΕSZQ]*Ε*+7|uw" ޠE_H~w0n ^w׆O.n ֿ/1*{uA㯊t@VC ֩gjnp '-ʔ#9.S~-ܣ(70e0 /I9|V.2RLQ[[ !5/=J&]%~O)*.|O)n0\Gj L{+eJt> #~/4=Pˡ4D]ΧB/P] JA9e7{ސ>^GiKqDɥZjp./}忴⾧63˯"Ka|tÚai0,E`?/\^JzW.~wLE~_j[@)!K~GJw"}w@wA*-Pϸzcy3œ"¾6)ݻ{Y@tKQ#Ke4e$J~-TxtS&QrFD2ˏOmO n㍗Q=I2TbD^FC]_F#qЈ^FC5<`F).Q2~}QnCH<#ct^FfwC>_{([oF5aUC+Z5Vߢ~I>.Rf.{LglTZ>S-Z-^Dٍ^7^rz7"zy2n> =2w) ݦU.$zPR{52/罨^~v|ދ'Mooh.T(jʦkԐRxT)W6anzyA/+<{8o`zC{=Fiz#|(M/T(D%FMo@f6!7{;F7^J0^צ7k˥<`z8 Xꠗ|`.8vPҝ_Z(BT1Bq/s?nE_(}lmԻnωTﮎr#[S& >{Ǧdj/=DM{\jC_hnMF3%P3i^d4stX"y QwY<%:.gScz@4]ݳJΰSLSƒM+)z?PWPaDAԜcyEbRݜU|HBKjhLF^{Xs YsQp^R^TJ}{ƕڶI+ٵIz+<Y}+M+")仟R"೼",^Dk@q\*QBJ~OIn E62{)ZFr/(z{)UᏱp%ƿN~Jlrm`ի6/X0/L@[cWn`,or+0-ȕ <Ԇ~+.[k_ƴ5/65/}3 )QDKv1Qz-pkF1Q-p3"oe^({*doĂ>%:R\Dv1qh_DiƋ}6#'t9(GPH뽻\(M8"|K]=*{w m{&gsK }TA3Uﶼ"",+/M[Zy]aOHF.RRQ)ߏ_qmWFc .g3Q_JIl<&P^FCH_FC^^FC^^FtPH~?_H齌F ֱ~eg~,!/_~!Z[wGa!QXHHRBJIQ|?&Ɨe4nN_FSB ϟژiSwOT Iߏ%n _F!k{{(//+T~]HW᫿aZLվ4Lb]2,QQݭMi5gX7MQ-yV]t(zIl}ӿB^F)/["OGQ5nUҿP ni{ʳ8QkrlB55z?[[ѽ8={䁗ч\q Fo*+MoH^ҵKC^YQm+=^6qX(КhMoH㦗!rҿo-<~ߢ˦ z>bhR7<٧뾻tDMoezC<WmzyKYϧ{ }(2}փ^6޾y?L/=g^.twVYĀ3ʘz?Lo],utQ]+{ye=`z== +MkzRwH􆌼%{jҿI]OPpF .%HDh8Y"~+x}Mmܦ6nJ~ZQw=.74)B)r{Y(ScK6{ #p%}-݄i/TR^?/ʑsUJxX{8`XyB`9!_J&)&7%^X#P^NBosJt9 |9.~׽hpWrw|ƎO .v -q=+0gYM矎6qZ(Mqû]+ݥ]ۮ.&VR.qK,~ԆyyntJ=TY y{L+5q' +7<}w Ո^T'˥} |ima,\rKF-<e! ݐ iYH#}!垻}bФҸz!} ~%;o CQ.!)mv _!_)|~OX"Lz!, iı/ۗrSO#ڮ/dJqW :<>CuwEh.Ï5+ujY#f$5k&6҂ђݍcJXb ${~E}a' R(YCq* 0?Z|/YϨ뾻a:="|Oȁ饴ozyH}?%6M/{^*@Y$jzC6YnzyGwp=(F޷o}xpLoĦ7^F3LoܳmzCFHA!i4uѱ_@E}g¾cBTMo$d7X2dQЦD%|^yKHλB.荃^ 2L><|/K?K/H7D⅗9W켛JXvIF.{膨FmXn?dr~ mDH]tqM\%;R(,C{O}gҽ8mc⦻${JqC@^jC-yO"(8տX""+deXZ@{Ƚ@v}Em l+Iҝ+{f-UV],݊FZw݋jHD)¿N1 Iw9-n{r Sh=J|A mvAs/BklbKq(Z$Kq3Cwބ6<=KnS}ݥnPwJK,M2͐ ,90X%|+y =D ߑmY[R:eGo :zuZ?sW9hAXMr]g} L> `(/p0ƒXrc@xN9j,(ZPZ25/}Pߪ)Ѯoեw8 ߗ=(SKץ&nRh$|ߚaVLHWZqɼrH}ed0TTHY>\0$|FƳmp9^&7:GVλ\!Jns3J`STHmL/V047ғ/VM.b˶[Z[Z,|y_3}?z+o$}?B^Z#{aCS%}7#<_,1qBB]~AONui8|N=ð*y74T4b )44Ŕ9[w), iCeA ]n{^UZx4 ,q=b͗7;n)及nJr)VwЋ *φq^P]HY2ʷYDyԟxe}7PבY'~U-,pNlXS}ٌvyfCw;#)ֻM!^{ϟ$0R{[I *ojhjKo7?i&: )^Wv$y/?qZ mjmܵ* Z QXR>}7XERqN/l({)Pz '$yϳ@b/){z+ֻMɚo7QFvoMok/^#F7e^n+!Gپ7C8pג}J|ZHF}od-7@w7#L7⯏-tbByZː%zw)n)?L/z!{L<`zlTeJ=zȆcK1B<'%DRT1M/r_`ScZݐPnmtCB_tUn8hCHm$4_l)-tC@=dK-2 Mᒼ&y~AJӋ} Z~eyO)ТҼ-Zhy?BսI~pŭhKKn(YL^%;2$YzaE{?e"}$kwƁXb#L\ݽs9pot@Ҽw5Z_՗r?EbE-yCvAsy7>`:HA\Nyrʯn~/=o5H/(?%Anۻ{O]T.C8u'?Eջ ۿ󇵠_e;Rmӵp[@mo[xhA }kCx?]?_-8j onA3q}i &ZuϵwMKfm̦+R` V=B`5HǽO7r/xッ0JRqSIJF+R^TM-}{xn\~+{*ħEGJBqQPi$ f7RR/Fx׭`n*{`>/l׶1PYe`Yv5b]yaUVi7JnáJ (;o%%yO \w["J1R6/XLkXwM)3T&HVƭϋ(+{/wU{/ce!V/;KiXSn8=}f m{җ; % ຿,qhqdh/Ѷe44e4ZrRzHD3Qi%:NC"eVw?azߚF"rRєQ}]D_BgM ,׋iʖPJ JRTiRT⇌/>Ԕ-j\wO~^jZHjH~j\&j_U[ZFmznnzMoThzC7*v֏z_wԏm !rP޷0 S*$c[PNVW BjU7${RwKdzyISXz)n.7<,^@5~0N S*M/cH{^:0iz_2+sSwp({,,F˕W7&`\ry~¼WTK\o*{ =~KqTYNf64j3lltege˶}C2Spa=CO}6Rx/4k=-}a.n J _hlKTzwm}{TʗָJ|ayyOϥݖ{Z@)%=-NYf9%>ys8R^LC y#JJG]Q;yA齥0+Zsz},gS <++{JDzҼO=X|j=Ҽ Q"5iiƚ_]kij*,yO^ \%UH5϶VkK\˶pi{TN a36ĵ9-7\} LXrӂ^ \J9)`ku)SbxcY3DP$6ESjx(ƳB轥( ]>BJw\[!F@,} CKV BIӇT-UOڒBLJ~L1&n0PK'Q5-].*Vf写u![ѦBKe{—X*+5K5⥍kDE/[tGaYsjXL*?_(cˑ3l)śJn~_$b*<2)Β/YF22'Iޗ7[o1}1(bJpEқw |nBy%w|(,H6nK~ƚ ^.^Gx)ÇH?O-W~2p˽g\Ŕvue}]LEe)4Ŕbdж͏G^Fc׵; 4V;0yQ^zXzC=}ak7R`ﭐj ]Xo>Ԧd~ʮBD y{|M/wCizdzJfGLofzUv>xɒDyߢ}˿i.{E]%{O)A/+<^zi:dY[4[|e/IfDVc޸ɾ7LJu,e }@wW$6a2 ;^/e(%{O30^${WDUL/RH }lP?V/,ezC?e^>렗c}hzqs ގ`)Wt^`.! /I! Tmb-Ε=k m'JchP_rC_]&_ҽD8RG -+{ǝ6< #ݻ{(.6qr{OCV.crettnE\mj=.ݸP]hٷ߼ăYT&ePo؈^b%?MҽapKݖH FqVƠս7"+2-o1vK*c^~N^P#|9Pﮍ'*!JMKi\rz?oٯRJMR cHWLeKx ޗOóR>v$zϳh:<+q7R^)QFسzOqxH~pJ~,\WbbҒݵQ\_b#Ye%J7Kޗ7]bsyGʬ:bNm }GuXR2\LyO2{,m_-.)7)/pr7-zrch x.GkO۰̭OX0r59њ6TMm⼧:cE=&5TkmX*Ɨ ) T>x|L,FZk)3oZǍ-?P\fS\^C0\$Vn6 =Jmޅ,y?/!_/L 6i%y?THRQ5uw?EޕٽKk+л{? ^VF.qIPw- j_P#86{C )F4nޅ44, )ɤyPu_H ~DB4n^J9yll]= )O+ʻi|| PJKh -XB9ywJ9~ e"/v=)k[Dr/ͻiҼK>x]]ϥ|=z|Rϟ1zw3{.{^ҭ){c>ʺL*O,ݻ_R½bn*{JY5l{%9ߑҽǣQ=EknVǢE6u,Z_½{OTz-CV/Z{O3Xru͋֗$;(y½/S_ؽ7o+^+8eGިFG^}7ӝ^{LyѽBt{9+4_uͥ({Zf7c˳ߏ_.J/[_.Z~?6 ،^pgnzo٦Jr>z fDhA/^zEZ,ezZIoAyޏ#C1{ 3S^vt.A J4ez=b)ˣ |l,vԱp!c7{ L޷p.9y]; ~Lkx#"\콏2rg_ӈP=RRwFay mv^SmcKQƍf> p!f6w361|[F~j˓o?*ջaFWT+L6O+ڻGI>r>&‹q ll_`yJB|RKd+_`kyDWww{gaVw`Q>XRRS[\]|2$zw)/漘w$zD~υ44c_!rWwee:* OېbzV25B!f?=F${O)4rT>~:H֥޷T`EW6<#, 6Ćldy I.${LM-ݥ%{)ܠ[O^pC^pCD_pR+yN(-y?e y?Pn -=2=iqHnb6,a>gʥ"->ʰ= kܳG({gAҧ]q z!]h2#l i .)Cw pPuQʣhh׋(ؼ_{Tʰh"ʝ/Kh hęTFG,| M[>e(4aڎ?ԟbP5oWI!zELJt kАJէO=>m0>ɏ>_׎4s;PH>dO v_G{ DV)߼#NA8jr\eߎ*z㛽j^7U ({>l|"{,uKPt' nr{ F)Mt 3Wyr}Fx RV~D^nHPJ(5M{ZHlzVBˈx 2{_~W6ܐ}/7~!=-TAnz^x ʘ_W>}{L;M/w==`^^z OTdz),Itԏcр>}/zq}Qi6< {ZHޢ~RVYF}lqQA?^祖ҵOez# s}oHz⼟Fe%M?aF iKm -.H~j,\)S&Z7IZS&ZFiyl,ą즕dVUwzl>&¬)p){Z$Mq-=-2_3겋[wUk㊾eƕe+WE~Z)]ϹI~DtnMU )'&gM|rʯTS=X6^[!-hw .\+#GR2ﳌFqBӒR/B|" "B{nw/Vl5 Rl@?`TνyXPT" jK,?sҹ{6ҹBxesi>ҹ/Ybɥt.XmWuztnnT)^NmK%.زO3hWKu괚>թ>s>(/0)'0M%u0KIyM\Z]c5}0j_Ss5/}]3,ԃk_Tc|T]t5x=&*B k~21Z,u .=R\:w:\9 P?_lDσ$bKLԶU*S&t饖r?e*eM~pIݥ ݦ_b/\7n7fA\IZYKݥB6lA+-yPaQ=,%E5ER7$Rt$Wt d5QFDtcD2U{û-TL,̌ihH&P*X>9ghӋgo4OPX<#{,!/e,/B3CO#3qxR_<{}(|FB3xOzQ2iFNamFK)ݦPSeu4u),[I uW!KKUת!ݬF̷i۝FjrTI瞡eҹg7CsO) t Lҹ&+b pYrUer2bmr\!{3 zR`>e^2!fZpҹ/:0^ҹls_8=3T@z)fО~dw˩'{Zg-=Sm^sϳ؇*tc͸o|7F3%ezFx_t1c 8|/Ϡ^}iƏ^*ܞ:|{Kyb ~Z1>|/q^Ǣq{:Ee^yYeϛ^T`w?k2%4oS2VW:wɤtG%2o [mPo jD/t T)=rTCHq2^Jwk5 }PW]H#v 7{f$dJ) {v}: iPW~VֱXs({(ñ`oxae2!ARcl\-/'--,Qޓm[dC_dYERw""{@H?U;{l KS[s8Cn lZZ[umթW[\ߪ./iym9Էb)0? Jy`LmxmʳRAM~[(%ԟ2蝚>`G!c/敖KwK꾴BR4jqMT"uJPwM+}ʴy ֧Xv B!]a\rW^Jw'Rݏ)Rww?e*pp|ڈl_jy\WJSݚrF-FP]亹{Lh ~R/}>2\_\ -%xO )]/)]Z~"TY֗Y*K, +RH2XppVV|>u"{- ۻoYNL|BXZS3OZw[شԺ2/Zw7 D揅N<S* NZsH@tF\dx@zEIE]Zw 5p(S{JQ'eH]0#w}PҺ 2)Zy,NR.")aj;>c(pPVHE5>@_(yw|LOC=>Tw*Kq|?[Ⴚ^e *KRr?e|\ױ^׫D,&>PLwҺ^鿙>x;8C-nJrPL[)EFp!7M/痒_1Q?襬x~,\ʦ7cJ%tL_FԹj@nP܂9Tϡº2aYHCɽB i )?-Q (LV^dž|w>:bJ_[g٭xq&c@n5n{_z nC~{ץ{iOtҺ>tºB {^XbInY!Klb/oZw?_a?GkܚXCթcZ`$S6~ƪS9\%+BeS9.oZNo}o.cA \,xrS[[2 5y\Ts@HM)T3,xiݗfK'{JmiݷR5u_Ba,"n}Ur}+e^#F4A,q >4T 0ZQJ~MKBKJRwCRw?0꾙-TJ,ln9*(\/''K ${AlYe1 urũr_O{e X~Y̚&OY`YNg^i1V>2YliTQ1P6ԛS4ƟV ޏ%[3JֈR"643BO.T3*[ 0P8p n[Dk2|eeEvaФw̥xR7^7ʿH-7&6Eo{QU)EyϹ$7Cxwu!ۇRݥx*(Oww9BjwsL)GH+tmw?+nJ7 X1Ӑ ٣FRg)VU+eRsje)s˭cEww R6q)Ez7lF=R]?<`z#A/*A/2:~ `^ݗk{)]kKo=kWL[qob>&4\x.zٽ JBƦ7?峎|*{bRYX1x^t祖Loh]}оAxtkz#oMẎC _\τeG^ԓzB}^Q?zQAQc2rEؿfDF7$I/vс?ݨmɍ876bF h2qڋ,ۍmn 55Mkľָqݴ=5Draߦw$\Z#}aX;tg1XzwnS$8h{Ann _yUKG,][6|^9w?m}i|nj(*5w?-]}IK*?Jcޣ ~,\I~,!aOeUpw(e-yESݗ,0ݽCyˀ)S}BXJꘐBYD/,LS=nJX>4۱l͠;ސTPS^b#{%Jc\b֫4OO)b*=Ͽwyn {:R:bN{`~V>- [׶Uvm\h ;Ƃ9 m+'p)SN` 2\)piԧ5AM}j_޴BsM1KFһR;vO)*5`Kzw"۬rSyyUr~=͠k^yeb,ebVS[3,ww)fQpc~ ; ma/Z&=D0m$7с&7kt'ɻ[xIcjt#5э]!{᳌.="V˸4RFnn˶/lF0R"y?J" yyӗVYS*, ),TRQ_QJ%]X"+Z3{&,K'YO꣊P_2iKdFOٙ HvKYKQ@č9{o>7!\:K2'dF:yQ۲ɟukw(m|S_\:{h8.o]eHnB'nCv܈FBbT)7](xw)~x (%XTnEU!+Wnwhs)"xOG`)}DTbPwWqo7μ+gVDwWHU{%xJkJvO 4{S9xOSwn+NBYrL~WTj ZhzfBJg~J QR43~\)dzC6i,g7ӱnz_祖+erx^>F~t7#?]{\%R~^ǑIҙOSv5oLBg:P@5<P:󩐍H`#{-<,UBBi<82}sM/_?.A/qKz]l azCj]xC\vC\ty,9\B)yrb>/!/ov?-J"ˉ#[V|w)}P2kD/anXַ[{΄nXdX#{aXV)P.n'6}Qo һBY]7 œ=0~y$VmyK1xPrwd]=jkktA+7iXxHk 7¿|[VV|S[D/%ג,tNһA݈`|tmw4^J~/r?dEԅ"whS^FeQݍ<$wgR.wr5@+[} Voc.oշ.)ac&-@bFٲe÷UͻI~,cQ ӤOm|bK)Q{ϊF,\$5EF/oIͻ쁅-pIx*;zٌ gܔpHۡR_:K'[vR ])Ӊ|=l׍Fj{QYS#{f&O) 5nFd/ͰBvMw^=0%$zX"ݎ9yF຿yIRԡּM<=VҼDŽ$2oJk>L}Xdn#@r yEYsE4KJm6_w?? ec/]l/]yͫeL/C^eK>N7w1r~W ? ߏSb iwڰ y{l(/ױ`ozc0z ˴ߏ>82࠘޸2EgV; #$ro86tPXWeʫwzy+<|/렗4tLs0d^{M/9T4DrL/?L/s(Ȼ1K//?^vC\t%7t72/FR.itO(lo_bK4臨huLsiQ0qaL)m"[?=V.!tPI-ѻMocz ]Tǃzو쵟eLDn4fO2,o99bZk .^\#˸b#iSw[KdҼ2K|Βj=b{cy7TSi=rT(FoQ^H}yӅ4rk/Ѷe42/$QwoƯHnSA ǹ~[ 5Xr5eA^Dиj_ kBhB߽B lX,$ywmX(Ļ˰W`%ywNՓG[Nԋ:u,;թmƮ:u,(S^-Ļ2xr6S>b)0N6z$vD;4%3/Gm72EӃGpTgP׶K'g6!^ۉF=|PPZRi/eRYfIUR{SY:WQBE5o?$)3W.xw)KӋ}3g)B)mɄDMCywmW[xwV< 5u@ҚwF|[N6۽r<7-`e@5`zy:'A/8V'+cR'.)3^Eh2lnztxq ~CQC/[x^CʱD`)%V@y[VZ۠^Kaz{O^*Y .j2lzmx_ﱪLo<Ȥxw0~fx^}o!)QࠗCyKM8zQ/Fyf)mbi)ޏ)D-S&2/qk!ys(.oY͟2!/K>fȆF82^^QVn5.~,\ HA| kچMiMj#~Y PɗT һ{d)OF*m;Ѐ*Irwe5bF^e52sۿF/ k>V]<Kjtc/rwFajA_NC^LCs"Eb!iȑƔʧ1EJ~G[)1`|e(ĻLJe|kL!]څ_4oYcV/$P8,>.UK}SȃnJݥ5P8^F\ze?C8Ex?f;,O۩b6K jkq_RJU˟T̗O7ˇ-X|,Jf>=aDۼ(UiVӬrUK616ҹ´Q9~N\ʾ~5},tͼ[ﳤFx1 *o%uϘR^N#bʿcJ Rh}*[ƛ.\V6n+2*{/ͤg󦀺{ét8Uv'y 3Rqѻ<,{ ,{Xa°(@V>giep 3׺{ZͶ-ɼp=oJy~i=~݁ ݟZI?.թcZ"nBUϡl^:Z[u“r \#^.rSZ.-\g 8-pX5ǂ>?o?/oZS2>5P=@շԇwjO)VXMsMizBB(#U# lA=$>W~i+0 nx7tiK+{ A/uRKY̷B Za7%tTkM}a^B^. O}ˁ.%%{~XniXj1Y*FeX>eye]K+yYeUK*:bC@~\Lۤsv ,2/T*y?&/Q}+[pFnIivѤҿn,%Y7ˈ_.3K>fdF`X=X%SR($dϸ]3q+n%DsmW>>|?dmTd݃"s_~Y -ۃ?Ͻ,Bs +(wVϽ-dGE1KвKVܗd1{+j:>l\6D5\UٽN PWlq}U6XbyXR7q叅ukNpdTMo7Ͷ2OqWx-f^n$sO^7v^Ҧ7JRؽo޸d86\4(Ls4gM/ճ{fc@pa$s_|Te0g{Ún6<-[hK,u^갇P֭fR6en ^׏c@}8.`oPzx˿~^_8ֹ|eˬ)Jn X"dzэl/u}?ܨmIsw 6ڐٰ,=| a0 +9%s?=L+{ޔ,"H=oVdHܬmV?fU~+y0?-~\T[_SEF=#TT 3җO]Z9bC{8x̥_+=/Jicy}5*{ZWץ3c؂5goâcw ^"I~͕|,X]P(<๘F꥔1SAdx]amf^Fdw%s1|}*>Sy)De/J~{/ q}j$y?^In?7x?`3aB=¹E_x [rߢm0Ծܗ,[IJE~ªUv咊rw1N(K)=EQ T0XV2S[8ct'YF/M*k[eET g]=@XLd4Mұh5JÓݣFu~ϑe0_PcDy){"Na+DIO׽0|Һgҽhc'_D#j [4x5./_Zف kTrV˧qmieVmHe.ߏc\_T?cJc HT@|qDlS*6\JgMf66gaϊďe+e_ؗx Ӈx-%[fk^pscE-MyeVx,[ч lf.Y)Ro&˃q)S-4LCX6)݃(qLW> XA/++i tOGqP +{: *{O @J8=j֦aR|K{MTKmF{ч `3Mb.Vhz#rWwuxA/^T,u^q= r?!ŕMdn^t#gȟ .?~K[{xeCQ*sQ^}`"r9Er&aInSDWS^uH)ŦԸ5Býn^29DP(;Byaw<ۡm ܍ | D"?̓O\,թcNmKd\-թSwk ?,[&Ñp)0YGyc#T[-0erSSʠjKMScZS̿M" Ë=&AS!M?k_:\" rJ~A/͏DiozCP7~KA}BV F .ʘwr"r?}-0lغZ|,%U>cAZjLoRbEt3|RƓ-ܷV/n,j;UZMe2Ydj:pRK'fWJHQF*wUJ>W;I~L<T.{$rC/(k>;7q︝h\m'vqy!Vvܯ + nH%>X"ܴϗV>fa%_R{"۸T.U֭lner h)P׭J[>BK|7$gQ=֭x聯 |3T2]FY*(}RǮ %{%z7d^*^cPlzC>eMo~?襢~Wi/Aiou^~KrO [Z]=xQg^T)x6S!KU)|Eӱ`xQc]|LBj9az#z^Y0\>0\9H ()EyA܏%$ea?]$-.^u#bsC~^h OE6uz+$swmR2cŕ LJyy TsO Rao.ةG#Ӹ >'?!?PS#ǸYGva LptAR }/A,zU#I0 n |?_\[¹}0at}p~-P*x-hթ2թ2թS[ٵ0:e"mrmU[@In,02ƂoAyP,08rc2xӚmaFKk@j!]5/)8L5)mz2 *=JZ~k|9)E%sz+#ƖJZ>="6F717~>4<%}FJJKg!K|Y-{*^lzy( L/W ݗ)]KYBR96,ŖBK@ԗXY^1 ,gYs?FژҲl)yL^DҖw(.Ot\!.HRLU1eB t=w b0g/dR^40>3eRqҌ'eϊ~,!$ۋ~RNC )؈c wR886pTkJѱ 8쯇*KEu{F%u{Q>xeo,qgW /h9M,;}Ax$f:>Ug*yTHtUHwbtBDŽ7VHx/>F&c J]2Kˇ[VxW#{: _tOcxF%/9;f\~tGL٥KS)EavQ7<‘=}R+M=/VLއץxjz#ڦHb~7,պ^5޸xa(y{M/Oa0/TxQX7!^oz#FK/,2^ R=?|/m7fmzm||[hz2sYMoJ^>}HR=?NXӦ Jn3m)]z {P !KHK.~GތQ]lC^jFٰۖ,,qQk>G%tw_U=r~%v>,eT#Q l:Qjd7TȗT!{}+ %{!J~i =,zZXە|ҺgSXZVJ]a %ҹiei(hoi}7]ToéB6lM;o:`Omq%r7نUm-R4,[:^i Ŕ.ZwtSd]dUcU418hPyʋ)$w_^YzwOt==~h$P?VЌlOX=WIS?>]`I%ﻮ-?$>5&\c/t_~3!O֥tw3"Fjem$7ۈ>Ԭ,)^<&=ӑr-sc˓?)C4[hl @JfVvb lcjGtTսjM/ #"lzt=-iҹ2quBЗЬ^bVs7]^TPj*-o.7lRPw[XfQ{ MjgԈ6a2q!I"t7ьE5/uIS]nȀB)ՐUʄpmv%t4rn_)kmrPZwgZX~j~-3):R5=AތW52V*m1!,N0R/2! ۩RaZR9< P]NnA`ۖHmJu!9HS2.*8bnڭvw#ۤt+tO8(LߦXJ[#Ev;6Ek"'Ev`(- Ymaۖ#a/J?O.ù^:qO7i-)3(К^Z /v ~LF *S>P)ojSp  O]|Dž%U1ŻD},ҺP]B#{|,.->η{4>&]4/q[2͋&Wg+2 X/ܫ)krB+-:T9ǧP]Dy# Rj/VkE#(ЯLn!#}kh6ьc .<o,%@dm3{Y|]dvcο"$*Gx|1ݍ JZScżKEOƭ\*6.6@rwW m⁽O),Bvݢt="v A{fg˴} R\|*$l7 7*RC7j6{,]NF2uI$wc?C)EW^3b~CA/CMo$S+{:MoH9(4kz &w_Wr(&^ 4^rw"=ql"wO{L]%w)~$w_qE5r,և[?L/av@I~Lt|Aºo-B!ߍ7J ~r/qBü[Myx|ދQl^^^J ,ދQ_XmiC5y}z@՜RcOz=ߒ2)SIBz!zn_DݳfD[{S n{U3vO)`!ו]_y1owVdn*$w{BC,J ,B2d)Z#^6FFGFPj>5s0Bڨ]ˣTU+̃^+^˺9YFH as}?\ToM/=΁=eYM/]ougr=qM/d 62{BEu4*}("^vdq) 6D5Ԧj]=CW6:-{=7K8b^J+y?筒 Qlj2L2w?g5W^z!)nmOm)+.CrDQO,a~YtUE> (>u}9([@q~YT{LYfI7]CnP#۠^,=#Y%_tu?bЩ?TLf]ReISVLww='-.qO! ){zJtUwSDIi5.yrJ~Z +AǕ*Oh~ ~*>8x{(֮G Qx9p唿qlm){>4녔K@e/jdh{/aYF阤qόG&o OTqw)~q ^o*ɓc"qzU gR UdpbZYJ!?Wݏ%/F*f VQC%cៈէ]_ӱa-\VNi\6~rl!öp xy$@72k'^51k%6fI-ּܴo IMԬo ? iM5 m-4)e%q_Ht_BB{1H(IcZ`^-q4}h`C@^b?wdI p7]} 9եHf{ /?t Rn4,T/M/tO< bz#-Q$S\wnRwbz$)ƛ^ 3^x)1J➦̢ˡZpK/Zҋ,K6SG7(Ē7\NJ\<<{^HC^F#bzeprsOh h.CfϢ-X2#*%`R^02 fHK&CM}ٌ/ow{ ~PtB(,q?OLJ>4MP%y-p&~^pF@) @*.Shh }?*\Xì4qBSi˧0~7\HJ yjt^jĦ7Dgf({jq^@82A/+<{Y[q^1 {Llq"pz8/L/I2M^6S cSHOP]!)e(܏%¯۰ކQ"]h#2Yн *nbXY^CǾop_DZ`YE怭^P#Hz9 bėҸX[ ?Ҩo)} ~ Uw)4^RJ5OӂkTUwmǗxr7Yۯ2ۖnLWv T.JGYͻ0*'>2`TN_ j 7a5N!{/dGZx(_y%F;S΋i^L#z1On؂44$쥔BK2M5煔S21r7)q:^Cɻg\3x_v\ݦ)[vdҟ~ owv ^ZN}iMr?e},Q)|;#Yqo-n~1NkHiK->p3%Ǒ/m/ }ix6{7$#'RL(S)QK$)YbΓ%\,t&՞t 5[  ?ksa j{u FXYuk5/Q@P~1`Pc~,k]~p7H!cK;EF`.iO6ۼiۆ/ uy#NdCq;-:Coʧ<&f^PApE拴E~ !9 7 [pF|ޞфgt|ޞQ{J^4h3ޞ!}h܇mh ejwa-DiD?"=SOה)z''ʵI S T#Z-3p{&Ⱦؕ2|xj\ueZoY{]qk2|W)Wf(hY{^-_+[p.pc[,>X6{총|+O -u@~ױzolx͋ʷ}iNO>&ǎRTkcEeXR6nzC5(eztjs\Ӽ ۱bC^-ktވ? (xA/kx?)hY{k6{ڏ|4]Fiw[){cw( dz!\oSvN{4v%y-\ uStVPqToH1;+MV׏׾j{IکRx5ִPkcyѴ Q7P SKkڗ.R#øw`YO;S[kOBĴ3^,/P,Nd~Ѝ)/aʩ5KpQ59+u}3uZkִWi_Fkk;دcʯ:\Y^FC"`){}^v^jheҰzǭiOO:%xU~)JsQ ۋc߮umb5~\{R%lD~:Rm gj&6ߑ-a{sHnjoJVnټ7wzl\¶0mBJ^KؾuJlX¶U12Q5(^0*f[Lohަ;پalZҒ)$oi]Jq=Okp!F [T,MRY+;(GC n*b[H~e!{+c QY|*<#[t򨺒{^^ `q"3&0#ŹDۢ-0 L/9j|IJ~kN|x\o뷷Ĭ"/ÐPA9wUҬ(-Y{_WlR '궞ۊRjמARGiosAS֡EU~+Y(YP)u`I[Pj?JٜFq\wQ5 "(I[s=ڵ"ˍ.lV#R-I.;P l ~Tن^T*ՔR(~T#gRezBxyԽ͊:Y ZRF~T[~L/û*R[ՠDz8Z*vlz o"io5cHmAFRqq6X2T*rՐkzԭ}[FH LoᦗΡnܖ?%iw1}PezCK[<-)%M/#zJҾ9U >8%$-eeݸ&C&jQ%iSej%~va!_ %i{*XxbYXB*}[DT"RLZ%iohq+`C5TL+i SiȷB^5haҸېFp! X?b4b(͈}#5NXJ2(r0)H:mCʣxAJir/y )VEio"Hu^ڇz(r;(4K*T@1^Fl_ޞ`*d*`sGs/Zc XJ=</I{-)65z_^1RڜR\Uu+%i_Ci/H#O%iߞiEҖ8m|K@ٖ T^5h/ZCtqT|/Wh Y9ȗ0Y$ByZF>qকµie D-[7/!iGҎ^?>R^extTnnnmA{WN*TjԵ,Y8L, r.Yj-ca@MRe)P#^cV15 ڹF\ 2\l-go[+JQ5g)CikO})e[rvkXMd0\~nq ټRo幐e–y)6ܲ]=aG\@rj9{MTJM/[ΞR#x \Atnz_R)Ê^@>"ϸ؍Be [Waz*BG WrlfwT).J"[rxj ϸ}[tܱBVl*4#\~5,3Tc{Ur:ljqn )A j .#9 _dFtab3盚=to/hƓ(CgOxR_l%{;^hHUcG1rLиڞU"Z>+:!{j%{A?-d?ϊ}S(kT*ȇizC&6qfnM˝r,ez7oRwnz>Q٪0r;,S[A/8l!{MMo\}K oA/aS[z˃RO7<-i!{:%}W ^-c? bEfO[سr [~D1CEfO].dvn/j_dY耆O[jpe`[EfOȑ=-WسУ+N\~pV h53l|;/⚽`F_tZ(W^-czS-H_"/VdZ(P R.ZƞU:IEfäe)҈OeUdV(!XfM_TiSZji-bϷR[NF~rئ׽W]=5 T׋=.}} "}<@RM~j(W3PH{[P/ΧiDi ĵ=PUnfu3|!a)gw򙝨Euf|.J-T{Sb/ӊWTu=LRBa니j_Z)ۧbq"Zⲧ ]jnGE?]ES8Y2Kbg f5-|,/ը[,  ]wrk  ^yE5ׂP25{joԺ\]C ejS,9j௅m5 -d_ڴ8XGq1qzUaw jP59n^9u\V -2`e'5 -%^5m_\_ o|v\--do76rq. 7bz)o|MoTNoݥ}k˞G.Rqb㲟:O-$li yZ0͢XKL*JS9,D"HC!} шtY7gߎ*nξ|cok`|;}SZ>ek^vJݜb8t<:N>߷-?X)ebM/t|/5tazcDcOkpr127荠kXFrq:Sxԭcñq jp.pP_qӶ [ǞXxTZυka_z]v@S7B)saʳ4e hteQ FI1i]# }4{ZIe_RL ?i\mHk Rj cp{)O:{H\bQf< S H*;mWԕٷ2”뽎Şcݼ{v( D)w0-c?ر؏%nev,$-H#s k}t`AQڂ4jAJѲҋo?-c!cBڌĺ2{Bv,<]o,tX||Vzuxn )Z~,\8|1#;ړ%_.yvN1-a0\ݒRcsN96Wb5L/MK/w+WK_-a [w'Uڱ劫nֻX K2kxO[d lᱽwq4^-aw S?& pQ|,7ZЍHvKa=ܛc aKO O(J{:{PAmQEWFp/\=`4t(@5aO;TyHTWd{ӰgH`5=`jս^*/*@k 4lK؏r-aXf-aO R!+-b(-a{yHJ#_QwZOP֠uÕ-Fnը)p)SRZ5j}V i5I]V5/5߃5@j䏅B-n-a_>%5[;{MTKk_jd =&}A]n,&?(|4ܼr,u [¾cK7j3oWR?'[ž-a>R@ۻ odJ?襞kzy%@gwQk4~}% QL/&/Vr6/Xgad? -a?"uTS 1\RB|x$Z(ZT)_.Jx$F-_?HQ|Oеghb3fXDf@``GlKSۧ-(˟|Td ;RZDe2r|=w}5b3pE'Kщiz %8YlФlI%\dg: 2R`gzV@dFsKnZĶx N.Zl3p"f =#y&j#=GicIQX_;ѨƱipL)Q{e8V܅W=h^o)^`)?%6^iTގrx_y[kiz#ee5LoʔDצ7L'}VL/{oL/mȣ̏}F(A/0!*6A/;^'ZN(kz#A/u 5Zb z?-:R~ W4^.:{d=Z~ Eo˿;SM߆={qa6dmQKSٸ'[2KH5+۠ï=`kR&^{( 'lx<-R{MK- #7ǭ]Xž?|Էa(< e֮81hN#> 3LXF2~Um{TAvLtr?P^R T6=~ՙz}7_|@5t@i8 mΔ)V)"N^?eBP'SwShb3eS~Q V;ky~JEM*/0}{ը׫ECc5Vu-a_:%)Ʊb˛hc5@]W?rY_ϗ| / b4R~QN?hd7Ѣb4Ѹ<[v+Fl1Xvlb-zءOS`~=p-_?}E4[S_ DiXەD)7|=2yM}`K_?OdyEi4҄ҸIٞukz"_W,cO6{ 3+ E)7A-_?fQG1ߣZ#Wy^F8 ًՐX} b_gp|^R7z=]HذRUfw ǂ~jzQ VGV4OQ7QyTT##if5Bu,!y`d?7Q dחjz,]#~ ;S~,u? l) ZXR!Ԣ+_"Z8U~I%E)J1J})^/Oψ!7z@uw`:}"2;h_=ԢT˥%{@\qIdKJ3PС&7[{|J+8iN>}ITlRUTEx=/a4Y1L%5jaTS~a H.2¯~͝<|p!6b3,bMK6\u"Q}gt.MW!{v%vg@;E#) M^r0n 4ѽ6 ð]QcmRǶm?4tOo~2@T_/pUd)q| XRx5q|( wVL//XÃ^ʵ/wQ6<))8z{O>q.R-^_Px`_/|{S}B`5] [C*"A/5[߿Z¨S֮ ~,( ݈o.pmqKQF,)6B?:x Ur lzj@+Yj^OZ\?eBgH-P/;q։ 咭K1 hwZԧbhRQiygP!oFY71JC0in^؆_c4jb+f$vu+\UhV ;mc\O~ R eϡ $LC#/*60%-]n4bb+xE0w_?5`^ ;ocaa}=`at=>);zښOt~y=e(w S:َ2J9յt}Kc*Jߤ ԧVSCת@”ëӆO%^ǡ*?W 7-S6qJ\pM6|$iTC  U|Q SQe%c]OS_[OWQݚ>t,LQm:qnm&Zt߃6Z?dឿu?P[,'k_j],u }Vc[5ca"߃/ 48c5/ںy[;RৣoU{G]BYBL)"_okt5{[R6@XΌ5|[]FrCʺ rQ apBos㎺6dC]n[Rmz 'O/ gnQH5:zL/dzTgM/SnT ~, [B<,lYgAwY>JO}Y+JmZpXe)ĐB~T3 j=EUxr!SO+6Casl0"c ӷ^/caܸk|mW >L# uZhOBQFp\5M'uxBk)8)NMBW^4Y>Ԡ&[\’≞1ϧj_rL̾zXS׌&Q7xȤUaUhzLy|w^)⏏)C<5]4_s2_Zϻx(w^2hDX}5HWih;O^r9!ۼ/0t;k ?r{Ə^n5a)v:鍀fćiz7P^wj=L!v\(zټl 5|MohWEjp^dت6/PazlZ0*[^c_R,uh:NȚ^n[[ADR$[뱀e E/]o"% aX2Z[?O \rO[SwvyY֏mc&-*`#%x 9[F h U,+Vn68.Ru8Z ӷ44ds1tfCFŌfȳFQ},)6ZaOˮEԙ)n[hSvP34A#V-RNxڴϨ.Ґǯí <ˮSN Ӱ<`Lc P0Ƚ"#v.@xZ֏hH"dXQ2S2ӗ]Oh+po="+HyݢTL[i3RjG{0|תLMвSGQ1J-zG?I2B 7ae8N4|?#hz> i:m V:zL+>u=tS7+iN:-[YPGU?j3狶OT-:OZt,1 '0YƩ6p+[m:j5Hv?ߧ[Ss=5/8>m25G]Z> äX]~,1ָ_ mzL>Zo)*5[ ]&5"kXŭZ)TPq(Ӫ<m]O6!ٸI2S wǨalC6lp軮 vj+1Zz]Z|kx gѪB55OmL/):҆Z? /fVqNec[D-U̲:-^f7U>M8)JQ(\!4mg3}E[~b3Du;V6Фn2h_ _l1 O&Efͷ EppjvoE)6 Otg (N~JQb±]'}ϙx&xz< L(!.cɏ<Sp-\_,"3B6߄wf̨Ȍ@lͨ䚫\n=֭w{vip RԄZO[^So5@{vcvkuUKs{ѷh-ںXyMC/`vMC44rMぞ#|&O\4Bkӎ^:zLlÎ^(ǚ^v} \72A/pz:qj.ve|n}*|L< fztjMo,uN1Q K=ŝ˦7L7m^.B;z饤}K+NCezC6̾ٺE[+wS(n[Mv<_+p)J~q;E-'~j, k)?Fx?e/bp>ӱ4ca߱5{ -R#@ 1WE,k HCb42QZvkF ~m11m FX71(k=㔚tЪS&[1sV2\tXoPK({Xp1Z4 $>؝%A^Ej=,ĵj};QJ_ު|i-{R=)lQɢU}E)PTEi\lJYƞ=)fO>mzzʽ oeZ/c")}V,*_J5J5n6@Uw끛/!gO8Te7 U.~ U:ӆ֬7*XR`hX}[OPS ^nzsnխF[xUKjԱ0Wn5iը[&u'jԱpTi[MS[ ۢI:5icK =5//e=5ׂɨX8Q_ Y֭ċ;Z^%Rs *5WuWq&Һ7:[L6Z 2m}D} %jCܷtmjTkzAgNxa].z{£EFM:e5nF7R ]D;E-lAI@R|i^.XeUva0zSD) b: (*>YBtr5YpF }Shƭr<,0#dnN>g>ۡNgA Z[aflf{Tק"*zF\O7z,L&3ì{e:P~裸lɇs"8&UcOzE~=&MZ<ճ)扞):bij&k °eoF8l9Nh=/5m JTrBkzj(w/lZR5q91`@w5j(hոR+T|۝FXV.5۩]7T=ٕC>Ũil6 VaԢ2u֗oM[_iz۪zAYOjP=֦\mez#r|h}k^w^z:EzѢVlz鈾~w8jz ezS*l^7[j{wV"2R~m(>8BtœZjz[?@i2nMH.p#+ lXjn!t ڈE7-Z?O4ݱ֏ۜ &^C.Eyں~,t-Zh})#Riꫭ JZO( ӷXzZǽd =( izj/5 A݇YN[_m=_h$Ir;A4֢cSZ?hXcT)F#uѐ(`-ZO %ە"ZOP-'z )'ZO8"T |Pi41ӮvK Sߟ”á~FJ;A!>”K~oVgpQa(L#=0>BtlaJ § (j2tOתQmJCZN>"✽V kEj"ƴl>~]I%R)xt󴨁HHuJMExlzYkњy֧S֬ T*ROXO`?WwOS/ˠ=\]\Sꗿlaڂi_OgY)_47\7Yοۂ-??^_єFgw5S|G ǃT2p]5bQwQ?x'ԫ@Yrm񀖟lj)O}񣰍5rH-n#fRPhp' .Ϙ?\bҽ}2$.:'54!IܸW[FrqK㓰$7jLX k(*B2HZ|8vзE)_"F"P'ExhɩmTeg~c K=>PMAgQ J#ATa\hH2S\F f&3Sd-4Cė~!WkƢ,:F N&&z&wp_3C= :LY`Fͼ,_fjժ|LdD'b:ʋ ޺EHdΨw~DݾN9}o=᛾_E)cQJXRZ>_( n4](ռFܰ~,J^Fu/JC^*eozlCOS<:eW[*7BoMoDQHmz# %Fk,ӿ+S*7dxF=};pkzLo'7v]pyL/9fzCM?%QW|;n}^ӛbM/Hߋm2^\Y}7٢N.vyd]r]F.qaK]Ԇ"u,ܰ|{>A5eְղn?⩨Q+RiR.JC!(#D#Z[]S-D#B4,B4V(w!r(\3pL%wC&LJdE,hZ)DSV%D:d!g˫-D/!JK,FvIy n ^ e6 n0)-4&p$sI S/a*L#mޘR`߾]~yG ӔSoGR)CϷ7oaʉݷ0߾)4JM)EߦSmJ_ߞm+L5L o*-5Ҟ*#Ʒ@e@lmUJ@ӫHڥH%ATG<)sݿ[TĽoRV]Q#Kղ㺜eTXNXSE,1Y%~2K~2]N ҭ½xe2kO 0؟2V?FlZ#҂5ǂV?a୑?eRmK ȿ`AQ3]c54L5n6uZuKpWʀj\Cmx-6ވE*z:򷙍 kJWSRwBJx5QJz>-rKVYUF ؒҪgK6)6Z0\بjh)ؚY\e,mzP.Jۤ]0%#U R6 >o"? PqS-ddMlEh&*2) \r,.5aɪ%oaye(2/![`EdfۗrZz]pRQ9|o %Wϧe5o3Ӕrck:beWO!q3! M'kSԌk~3=/EH}wdPE'ߧP=N=NfTaMiԺ}ށ T>ˤYid';F2ꙶPP4YiYiYif[Yi${=yO)\ވ][\=>%WT7z)nk*`MN>Lo o7-R5$KWf)*lrPru?'8\=|62^"'umz\-Ru"U9b3Rqfh"󂒪Dq瀛n~S8=(OICbu; O|dBvpAo/(C'q$2f?uIqUhR]"87n|贎|OaɉaN=qy/EfΊf& $WÙi8hIyw]͓Nݮ+:Cw=w6kBg#-O2u7=H? *?UF =#ӫ=#`g!oK)۞rV2t jrOeUw)L=+KY)?L)z* o|l,Y) ~^~l)P2ٚSVD v Uƹ7oz)|ސM/jU4ۆ7 /' t^KBKRޖT`J=Mʆ=Y*?eo# /WRTIՏ)$vC=6qh[AQ=8J~,quPI]=i%_PTݵ7] H W>=(XC ~ UDRRSFDiXi(b4rhMP%ݦAHB4, >F߄hB4$t!-0SM*KFevחWZhd' ѨM*F#'MsYI HS@rX9գz,Fyg*NO!P2cKE)3*[ܢ)տ/][%SwT*%uG2u .-M* _)7-Ai؂4lC)GT7|_S#oژ򬖒QOrSd WؕQ}*ëT.Þ\VFǽ-@{zU2̖Jv U<22#Jo<5!QQrޖULZ2uÍ_2u[}SnLOWkFu[WlKm˫m}9u,0 ?_sS}25MQYc5 Z}ȟ2g)?!F5/>X#bY#-\^~͕L=OfkL#;֜r9V2t%V?e3V weToZ9 +zJ!vL}3XnhXu+z*d7f Cj 8 }~%SwOG{IyU7ziڂ,%Ǎ^JsJƳB7q/z&nmʫ~-BT-$jd1K.^ J?UH{(IJURJEUTݎ*:`H]x!m ȏ!G ˨MT׶7AӎPN$X!I=ځo~Уet0&E͸||M'ʳ Y|R.߾h݃QYՏ%s)u4=*4q61Һ-,ѐ=1|?Mkh5ң7^ ;/RDŽPjʹMLqo+**h5+z*d}e|]!XޓZ=§$)M%Q ZIm 2/.T=ݛH6}-X {/r%Uw)Kaz\+zT)ˈSNJ7mUxنXWGu YYN\N2q,sKk$Ƶm˩jr*K܁n Ԇ~]c,(ŲFx^#5ǂFkOxt 6?9kළӁ5/^-pֱc׸,.C%u~ H!W2u?STx:n}i5 :.S:15RG~wө6Vه|/y  7*zEP̾pCоG:{*jʁS]?@&;+zJzLW?r{$姶 !K/ ^~[vRp|xV*;@$݉),4P#J *@C=TQFtr ΰV =P~ L "* 7,߄%pRv..#kZ`rMde|،Tθ!m::i4ɧNUgU PH7*u?Ra]pR -Z|ghl$ L^yqƸ7us'՜RR;"@h&мEh"4.s,Bll®d! <bkszYܣMK):F}U q!{ZB6-iixxSa-R2$OOԅ=-场 +b%O)6qzSM/?LϊMo>ok*7T;ux>41mzMo47dmQ+zOolR26q6hf |&T6u!)yzLs{=;`o\C-v\Ӽ qglE-/A{:iuÞpK+BA簌py45m+STw꒧ g\%O_hW| -F#Z ,eçlVekG{˞ꟲL~x{ϣQOQY7T?eBPt?kPe(i_(?F%Owm9xc O? ⊣q(} j%SwRԏbj%Sw #i4N7tV edsdꮍ! )%`SQn_6Sm^hQq߶ F"؂OTµ?O4,JOG~w9QWCRnW*S[(碔 +BihcA^K5hS5m5 &kළ[Xxhqz Vuה6ݕQS&nǻS鮍-q=ݵQr.CEhPX5]#=TΐrU7QO)1 ؜R"/!*%K_Dݦ ,}qT=RuPcKق%nv\-R] uw#+d-U鮌+j91ˎ0Kpe ʗUyE:N,aDa:ǂW|?dghbtS[ˀ&x ⓛ%J7Nvld(Q U"4Rh+D +oC.zQS7Q?ŕB4j3iFdLw{ݒsҤ%]LO) ꞓ!ʉNdsmNg8Z|εKWz:Khba42=]IA5Z"`)2=7ez{l}ސ7zbW^*1ut%OJoQ76mzbJSnJG!&=Rx6=O?. ^] OН.(.*E0Vt[E]BXhQ/A8]{_j}/O069|_'Dkae<9#0-~*R#k+<鮍T/bm4`1m,Fg1J }MVE?-E-b4 p\,F95[ězx1Jm^` ҌR:;v3TZXBазװJ~,q%t5B}[rn&BC{!ŠPEhܿ,B`p?+M!N]AR)|rczo+gU0pݥAuRNIϳ1J ):q[He[L)d_8u4[  [iM-%j_iO~|K6* UYTȲa70փ"= l0+Rb2+>O`.b+K_ßf%-4Ca\& cK~ Io$TuУ}C (ON@,*CAa!+Ɖ"3NYؤx(4:xkG Pv&P9Nʑ>;h&EtI8fRE\<`oσ oV+j,@B=( P"Ut*Ab]> D@*BNj!b4dg1Q Om:|r%F9hiѸڌ2kl?R2鮍 ?ے e]*({{>/|?ZS_ KSTu8 )z>Iʞ3phe .aKW~t kxmDKrmb,--M Xu|T^6!nH$7(rl o@P omx]%E3@MIBdG)BH-t#!!RqȋZ`j8\CQRtL.b9o9Py+i%o[j2P U庪*)oZWU,iާ({Z2R©lC+-Q~X?hV;;׵Ux4uO]ڪ讍oxʶP(G3JIO "֌RnVrt}&Kr{ [-)>GF (ˈ8\ƩfZ= ĤʎǑz#){UrˬMO 4I1DIPM,J nShUJt{)e ErUJt?eD)=fJY(s=Kf{)O^faʀYJSSR P]4]J9=6 J~,n R;߂g݂X"mڡCσ6ǞҲu6nUrAWRSñN6Hw铻+ocf-DzAa9k4{9u,B-N!tmƲ0Cr9ujCPX#-mȟ2)Eԯ?SX?k_Z<{ pp yNcAm soox5ǂB;]#- ^,uvcS )ݮn7R]޵.vSA^c-07nBN_:)č:]G-X.NHlN_Iըf5.6KF+o\'IF%H{Ł6KR+AzL)X#І~lhO xD:{Lڅ-7+AzEVr e_+W@[v?cm.hYXj• ~Ճ 3S{, Hʐ~J @zt?ED'GV]Ml.CX4pf <_ң:裗K>;SPr%&`{УMLrMPDe(2,播V@OyУ=8r6y:Ү &ͻWMYƟzʴ3qGF&p/AGkrnB /A6iqb'MeH7;'h$ÊQSISrQ=(;;~ՎThb4h[Q3]kʍ7)9GV$g{6kNϊmF=;jl4r\=(e[b*_I+SlFY^ynFاJK jrJ0WrtgOXr{MIc8=&Vhz7`zIo>4qֳ=rrSIOkzC6pK yWF?YƉb[)5wF'\G?1BVjc _F ]OmY;ϳ@^wIwmV*Ղ5&VWj .nSuPzt׆1VzuX -M*%o!IAF9ޢ +kN J>Cz2<2t 8R@ĕo*D#Q/!Xgu%G7n.(+3 bя%.-KhȠbח=q*T^c ~?Kn T^t[(y8RR.HyU× i$ Rn}i(EHAoA: 囖 k< Ub( x F7"7ii;XǸR^^Q/<) q RNK>#Rjtw5gA܂ʋt!E+5ߔ enE?e Qjt[JnAN=я%na6[`H RrPjt ]5FS{P`XN2N m˩YNVԶo ]S5ǂ[Z g iʬ?z Ki??-e )vxg p~[,5M(cпi8߁I }ԨFrYW5r+-U=N,UzNWQT}lmfP C_Z~.#8FҸ )r3'J܅.]zRfOzJQ5V"("KKECPSbf!KeX`^3O_2zE)?bګ,>8YpK~Btnwafqp YXRfW@..*QIVT*Aɪ$^Hn&) ʐCи[\ ['ev!^vOxGc'le9ne7Yf䣌sgX*8cO IVtd|QM c]Q& Er{;Bm\6xJB:,d@6#ՙQN*SKutw[>O:§uDi䑋ҐE)W%Lє0RXm ?==^|3˟׏B0]TI7W8 ϪD6q)!PI2k{>QIwf/eMq} ˋ) L7(ި2#Mo?;7MogmzY%LE22=lzPjWBޓ2<ȿoC]n2ݡګvl)OВ=mmܴ-jJ~C2KhB)juhw׆[*•K:PXgzFMM.{aEUr{m)HR=K!M:ڶ ۓWʿ^M+{I8R1VP)QpPxWW(8RF5KK>QH*HPSy^]" D(J~Zr{G-'a-pO\6ҥKr#뾔\ .>^h}yQNIrw馊e('(Zt«b1eh) b'w,J%Kߨh$V;""UtSE ^FZ? h FyR%I8??+FIQ gH%Kwniq([pu^[r}Pt} Y+Kda9-,Nnqec,AY~:e0VSm]_^cso 7  Zk_M /kOm?e mr ySY-7c i'%KO(?RVmZ)E٤Ƶk+xz+]!!nR5\0=!2qq&Q)m\ml- {5WdW'7x)ndx鬌 TJ /??(K%FN ʘR}(e FV =R6-Z>_ ` JXw*!R qqÔAJUR3iz2Eg,8"6#VhN~ZBS)cz`KFXRf4JWGxwDHїutae2#,e`R-4+ob"'e>ǒwN>g9V،c0Ni34V1M|FeI@iZ)moBPn6eJ]*.SD(7Eh}A]*c=W9LVhF9.mYsTSǿҏNhy1_,=0&$g0+zJm5_P#3ʽxOM}IUyHݥB+w`xinfָy X)EO.͍]VhvOys=5fˡ_yw!m*ҧDŽ,z*i0qF/X鍿VyL,=& }obW6*iz9+z*<ԗ*zL:28۳KۑHIb$Rwe_Om$< [ƋJ~jCm6> qONPu[M|J`RQDs=Q=.2_"2R,BCq]F9^FY(-"4RER-SyX%OwDd KEYQ1 qRF2bкT}8CZ9SsQ+yi[(iASBtJn"WcnafBo%OK )tAJ%.AוJ݁?HL}vB;&dErPgy=l( q]nW巿)S߿Dc!ZopGMGY#/}•חCe>_0~rB_s~_\ս22h0`[Чxt"E)K8%SB*&5RjOlVDofP:6(CBn;8Z==g3+y 񌂥P$3GظiF6t3? lR 7;oCw,ilJQݰkl^+uZ-e?m$A[꩐Jn\KnK$ u ZV1®c,Xv,\SP.Pٝ”C To4j;=>FlroMhsi5U]qIVX_֑S`(a䛑d'I+gʙܛ LMJI5sCRvd틏2Su0Hf~s}QqxHo@;jb Юh(b44r1 bpHjf3wQJf~[ 0S\,zjL_C?o (8hOOq඿ܴ]JQDB=^m_ ʩm!RGחT?}`Gz5Ci֍S*S pib4ZNmvJJqҴ-(:7H}m%RDI􆤼8@{8 G-Z4KoHn6 鍃Mo)o􂨥QO#F>Mo+az<~^V"ZHKXplz鍥QE/7 ]GfzXyS&%0V/-qG. I,Kڢ k!͞"/ W))BJ7-B%QOm9K ZN"L#+[i݂,zZ@XiҧZ7NsBjNc?(#SR+z Y, VB?|nPOt!p2[4s Qu/n[|6EӶ*X *XL(koObB67-Eh .B璨;LY{O4`BD\y0VXB4vsEu B4#Dn!zmVo|)CUx(JXR\Ms0hdKuƩ=C屩+zF u`Q_|ArI=QOmTEi JeT>GFֺ(rvmE)7F=}GQYRZ;K1 fuS|Ic-J9 XMO"6 Q]vK[):(ա-mNr,N1"Sl5пXo S~[ʀ5 (Y,0k_z{ s cs zn xt P]#M)w )EӘu*FEk)T^Mz;B6~EW&m]RBXi]&t|Dž WIK~Z-mKFu_򏲕@qoO8&=Qt,`ou:%-R4! H\61p[{ظYR*u+5hWzB2N?j NwS~ l`=D*Ukʪ)KRl,BiqyXpr&St{)qE9Y`*.iRut7,qؓ,)N?%pP>8}i_EPhÕ8}yHV$Lqy(6Cu> q0E'7J>»餜I)Utu<'.3ـk|Wd4-m=B4d00l%WDWtu#J9vgd O1hi D#JRuƹJ%M-q.9JV|+h@޾|mE_xNurw-+"n*’ǽIt/ҧ¸z[JZSiR綘:HS&OMZ_uƷLTM!}Z] lߪ3ewn!}zLqgL/#jI,mB@orSgezCd6qbg-S7uȑVTJMo\Š鍃Mo}_[%M?ObeO%.#66zQ {gox~ʊ585~PSIOoXԒ 7VJ~,!3{ŭPQ Rf4ݓXuXpqunK>OK T^b)‹Py=jmF͈I,_R)-d.$%Mۨ" у4݄BDž{ET|g_S] J4]4?hT]Kwҥ/~3yOQr.>%>#qY|/uZtWT=!ukl~N%NAIb<ǟye>Jq͟J!6lS:H^yY0%%Lwqp9WLW H0WB+yzZJ/=ca;Ӄ0ms)'%Lw/=+a -WvK[8]N wdar<+[8#3Ԯ-[|m9ujC.؟2xS?mᯗk_ڶX\,~ Ko?mi,kOmxm^ߖH?^#܃W鋷2a0=iPq ,6cY%C;ݥwOuw["mYڢ5l:Pkh5^HFp25q3pxUWQW*4=S5qN Cr5kXzk"Td1!|,=Pd^y"L%M?ܒg\C!rWz}]8=+ J?-c9*o IA) ”QzPVQpɛX(