openxlsx/0000755000176200001440000000000013572447172012146 5ustar liggesusersopenxlsx/NAMESPACE0000644000176200001440000000356513572430307013366 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("sheetVisibility<-") export("sheetVisible<-") export("worksheetOrder<-") export(addFilter) export(addStyle) export(addWorksheet) export(cloneWorksheet) 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(freezePane) export(getBaseFont) export(getCellRefs) export(getDateOrigin) export(getNamedRegions) export(getSheetNames) export(getStyles) export(getTables) export(insertImage) export(insertPlot) export(int2col) export(loadWorkbook) export(makeHyperlinkString) export(mergeCells) export(modifyBaseFont) export(openXL) 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(setRowHeights) export(sheetVisibility) export(sheetVisible) export(sheets) export(showGridLines) export(worksheetOrder) export(write.xlsx) export(writeComment) export(writeData) export(writeDataTable) export(writeFormula) import(grDevices) import(methods) import(stats) import(stringi) importFrom(Rcpp,sourceCpp) importFrom(utils,download.file) importFrom(utils,head) importFrom(utils,menu) importFrom(utils,unzip) importFrom(zip,zipr) useDynLib(openxlsx, .registration=TRUE) openxlsx/LICENSE0000644000176200001440000000006313560564727013155 0ustar liggesusersYEAR: 2014-2018 COPYRIGHT HOLDER: Alexander Walker openxlsx/README.md0000644000176200001440000000444213572442033013420 0ustar liggesusers[openxlsx](https://ycphs.github.io/openxlsx/) ======== [![Build Status](https://travis-ci.com/ycphs/openxlsx.svg?branch=master)](https://travis-ci.com/ycphs/openxlsx) [![AppVeyor build status](https://ci.appveyor.com/api/projects/status/github/ycphs/openxlsx?branch=master&svg=true)](https://ci.appveyor.com/project/ycphs/openxlsx) [![Coverage Status](https://codecov.io/github/ycphs/openxlsx/coverage.svg?branch=master)](https://codecov.io/github/ycphs/openxlsx?branch=master) [![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) 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", "devtools"), dependencies = TRUE) require(devtools) 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). ## Authors and Contributors for the current release [@aavanesy](https://github.com/aavanesy), [@ale275](https://github.com/ale275), [@alexb523](https://github.com/alexb523), [@david-f1976](https://github.com/david-f1976), [@davidgohel](https://github.com/davidgohel), [@dovrosenberg](https://github.com/dovrosenberg), [@JoshuaSturm](https://github.com/JoshuaSturm), [@SHAESEN2](https://github.com/SHAESEN2), [@soliac](https://github.com/soliac), [@theclue](https://github.com/theclue), and [@ycphs](https://github.com/ycphs) openxlsx/man/0000755000176200001440000000000013572442033012710 5ustar liggesusersopenxlsx/man/protectWorkbook.Rd0000644000176200001440000000222713563175347016413 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 ) } \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} } \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.Rd0000644000176200001440000000162313560564727015651 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}} } \author{ Alexander Walker } openxlsx/man/writeFormula.Rd0000644000176200001440000000445413562237767015705 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, 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{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 } \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)} ## Writing internal hyperlinks wb <- createWorkbook() addWorksheet(wb, "Sheet1") addWorksheet(wb, "Sheet2") writeFormula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")') \dontrun{saveWorkbook(wb, "writeFormulaHyperlinkExample.xlsx", overwrite = TRUE)} } \seealso{ \code{\link{writeData}} } \author{ Alexander Walker } openxlsx/man/getStyles.Rd0000644000176200001440000000075013560564727015200 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}} } openxlsx/man/addFilter.Rd0000644000176200001440000000205213560564727015110 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}} \code{\link{addFilter}} } openxlsx/man/protectWorksheet.Rd0000644000176200001440000000500413563175347016565 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.Rd0000644000176200001440000000157113560564727015673 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) } \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.} } \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}} \code{\link{addWorksheet}} \code{\link{loadWorkbook}} \code{\link{writeData}} \code{\link{writeDataTable}} } \author{ Alexander Walker } openxlsx/man/names.Rd0000644000176200001440000000113713560564727014320 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.Rd0000644000176200001440000000205213563175347016172 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}} \code{\link{saveWorkbook}} } \author{ Alexander Walker } openxlsx/man/worksheetOrder.Rd0000644000176200001440000000260013560564727016220 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/cloneWorksheet.Rd0000644000176200001440000000142013560564727016204 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/conditionalFormatting.Rd0000644000176200001440000001611613565534170017550 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', 'colorscale', 'databar', 'duplicates' or "contains' (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}}} \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{\bold{showvalue} If FALSE the cell value is hidden. Default TRUE.} \item{\bold{gradient} If FALSE colour gradient is removed. Default TRUE.} \item{\bold{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}}} \item{rule is ignored.} } If type == "contains" \itemize{ \item{style is a Style object. See \code{\link{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}}} \item{rule is a numeric vector of length 2 specifying lower and upper bound (Inclusive)} } } \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, "colourScale", zoom = 30) addWorksheet(wb, "databar") addWorksheet(wb, "between") 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") ## 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 ## Betweem # 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)) ## Logical Operators # You can use Excels logical Opertors 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}} } \author{ Alexander Walker } openxlsx/man/removeTable.Rd0000644000176200001440000000256313560564727015466 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}}} } \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.Rd0000644000176200001440000000151313560564727015641 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.Rd0000644000176200001440000001003113563175347015631 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 = TRUE, tabColour = NULL, zoom = 100, header = NULL, footer = NULL, evenHeader = NULL, evenFooter = NULL, firstHeader = NULL, firstFooter = NULL, visible = TRUE, paperSize = getOption("openxlsx.paperSize", default = 9), orientation = getOption("openxlsx.orientation", default = "portrait"), vdpi = getOption("openxlsx.vdpi", default = getOption("openxlsx.dpi", default = 300)), hdpi = getOption("openxlsx.hdpi", default = getOption("openxlsx.dpi", default = 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{\bold{&[Page]}}{ Page number} \item{\bold{&[Pages]}}{ Number of pages} \item{\bold{&[Date]}}{ Current date} \item{\bold{&[Time]}}{ Current time} \item{\bold{&[Path]}}{ File path} \item{\bold{&[File]}}{ File name} \item{\bold{&[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/int2col.Rd0000644000176200001440000000046713560564727014574 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.Rd0000644000176200001440000000471213565534170015121 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 = 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{ 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. } \seealso{ \code{\link{getNamedRegions}} } \author{ Alexander Walker } openxlsx/man/openXL.Rd0000644000176200001440000000243113560564727014420 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.Rd0000644000176200001440000000250313563175347015072 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 to simplify formatting: \itemize{ \item{\code{options("openxlsx.borderColour" = "black")}} \item{\code{options("openxlsx.borderStyle" = "thin")}} \item{\code{options("openxlsx.dateFormat" = "mm/dd/yyyy")}} \item{\code{options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss")}} \item{\code{options("openxlsx.numFmt" = NULL)}} \item{\code{options("openxlsx.paperSize" = 9)}} ## A4 \item{\code{options("openxlsx.orientation" = "portrait")}} ## page orientation } See the Formatting vignette for examples. Additional options \itemize{ \item{\code{options("openxlsx.compressionLevel" = "9")}} ## set zip compression level, default is "1". } } \seealso{ \itemize{ \item{\code{vignette("Introduction", package = "openxlsx")}} \item{\code{vignette("formatting", package = "openxlsx")}} \item{\code{\link{writeData}}} \item{\code{\link{writeDataTable}}} \item{\code{\link{write.xlsx}}} \item{\code{\link{read.xlsx}}} } for examples } openxlsx/man/removeWorksheet.Rd0000644000176200001440000000127413560564727016410 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.Rd0000644000176200001440000000167113560564727016400 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.Rd0000644000176200001440000000144113560564727015774 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{TRUE}, 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.Rd0000644000176200001440000001271713565534170016111 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 = "TableStyleLight9", tableName = NULL, headerStyle = NULL, withFilter = TRUE, keepNA = FALSE, na.string = NULL, sep = ", ", stack = FALSE, firstColumn = FALSE, lastColumn = FALSE, bandedRows = TRUE, bandedCols = FALSE ) } \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}, 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\bold{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} } \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}} \code{\link{writeData}} \code{\link{removeTable}} \code{\link{getTables}} } openxlsx/man/createNamedRegion.Rd0000644000176200001440000000277413560564727016601 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wrappers.R \name{createNamedRegion} \alias{createNamedRegion} \title{Create a named region.} \usage{ createNamedRegion(wb, sheet, cols, rows, 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.} } \description{ Create 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 ## read named regions df <- read.xlsx(wb, namedRegion = "iris") head(df) df <- read.xlsx(out_file, namedRegion = "iris2") head(df)} } \seealso{ \code{\link{getNamedRegions}} } \author{ Alexander Walker } openxlsx/man/writeComment.Rd0000644000176200001440000000231313572442033015653 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}}.} \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}} } openxlsx/man/freezePane.Rd0000644000176200001440000000246113563175347015301 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.Rd0000644000176200001440000000131313560564727015772 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}} } openxlsx/man/convertFromExcelRef.Rd0000644000176200001440000000073613560564727017143 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.Rd0000644000176200001440000000507213565534170015642 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}} \code{\link{read.xlsx}} } \author{ Alexander Walker } openxlsx/man/write.xlsx.Rd0000644000176200001440000001073613565534170015343 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, ...) } \arguments{ \item{x}{object or a list of objects that can be handled by \code{\link{writeData}} to write to file} \item{file}{xlsx file name} \item{asTable}{write using writeDataTable as opposed to writeData} \item{...}{optional parameters to pass to functions: \itemize{ \item{createWorkbook} \item{addWorksheet} \item{writeData} \item{freezePane} \item{saveWorkbook} } see details.} } \value{ A workbook object } \description{ write a data.frame or list of data.frames to an xlsx file } \details{ Optional parameters are: \bold{createWorkbook Parameters} \itemize{ \item{\bold{creator}}{ A string specifying the workbook author} } \bold{addWorksheet Parameters} \itemize{ \item{\bold{sheetName}}{ Name of the worksheet} \item{\bold{gridLines}}{ A logical. If \code{FALSE}, the worksheet grid lines will be hidden.} \item{\bold{tabColour}}{ Colour of the worksheet tab. A valid colour (belonging to colours()) or a valid hex colour beginning with "#".} \item{\bold{zoom}}{ A numeric between 10 and 400. Worksheet zoom level as a percentage.} } \bold{writeData/writeDataTable Parameters} \itemize{ \item{\bold{startCol}}{ A vector specifying the starting column(s) to write df} \item{\bold{startRow}}{ A vector specifying the starting row(s) to write df} \item{\bold{xy}}{ An alternative to specifying startCol and startRow individually. A vector of the form c(startCol, startRow)} \item{\bold{colNames or col.names}}{ If \code{TRUE}, column names of x are written.} \item{\bold{rowNames or row.names}}{ If \code{TRUE}, row names of x are written.} \item{\bold{headerStyle}}{ Custom style to apply to column names.} \item{\bold{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{\bold{borderColour}}{ Colour of cell border} \item{\bold{borderStyle}}{ Border line style.} \item{\bold{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{\bold{na.string}} {If not NULL, and if \code{keepNA} is \code{TRUE}, NA values are converted to this string in Excel. Defaults to NULL.} } \bold{freezePane Parameters} \itemize{ \item{\bold{firstActiveRow}} {Top row of active region to freeze pane.} \item{\bold{firstActiveCol}} {Furthest left column of active region to freeze pane.} \item{\bold{firstRow}} {If \code{TRUE}, freezes the first row (equivalent to firstActiveRow = 2)} \item{\bold{firstCol}} {If \code{TRUE}, freezes the first column (equivalent to firstActiveCol = 2)} } \bold{colWidths Parameters} \itemize{ \item{\bold{colWidths}} {Must be value "auto". Sets all columns containing data to auto width.} } \bold{saveWorkbook Parameters} \itemize{ \item{\bold{overwrite}}{ Overwrite existing file (Defaults to TRUE as with write.table)} } columns of x with class Date or POSIXt are automatically styled as dates and datetimes respectively. } \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))} } \seealso{ \code{\link{addWorksheet}} \code{\link{writeData}} \code{\link{createStyle}} for style parameters } \author{ Alexander Walker } openxlsx/man/removeCellMerge.Rd0000644000176200001440000000110413560564727016264 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}} } \author{ Alexander Walker } openxlsx/man/setHeader.Rd0000644000176200001440000000175213560564727015124 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") \dontrun{saveWorkbook(wb, "headerHeaderExample.xlsx", overwrite = TRUE)} } } \author{ Alexander Walker } openxlsx/man/conditionalFormat.Rd0000644000176200001440000000234313563175347016670 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}} } \details{ DEPRECATED! USE \code{\link{conditionalFormatting}} Valid operators are "<", "<=", ">", ">=", "==", "!=". See Examples. Default style given by: createStyle(fontColour = "#9C0006", bgFill = "#FFC7CE") } \seealso{ \code{\link{createStyle}} } \author{ Alexander Walker } openxlsx/man/pageSetup.Rd0000644000176200001440000001362413563175347015155 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 ) } \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.} } \description{ Set page margins, orientation and print scaling } \details{ paperSize is an integer corresponding to: \itemize{ \item{\bold{1}}{ Letter paper (8.5 in. by 11 in.)} \item{\bold{2}}{ Letter small paper (8.5 in. by 11 in.)} \item{\bold{3}}{ Tabloid paper (11 in. by 17 in.)} \item{\bold{4}}{ Ledger paper (17 in. by 11 in.)} \item{\bold{5}}{ Legal paper (8.5 in. by 14 in.)} \item{\bold{6}}{ Statement paper (5.5 in. by 8.5 in.)} \item{\bold{7}}{ Executive paper (7.25 in. by 10.5 in.)} \item{\bold{8}}{ A3 paper (297 mm by 420 mm)} \item{\bold{9}}{ A4 paper (210 mm by 297 mm)} \item{\bold{10}}{ A4 small paper (210 mm by 297 mm)} \item{\bold{11}}{ A5 paper (148 mm by 210 mm)} \item{\bold{12}}{ B4 paper (250 mm by 353 mm)} \item{\bold{13}}{ B5 paper (176 mm by 250 mm)} \item{\bold{14}}{ Folio paper (8.5 in. by 13 in.)} \item{\bold{15}}{ Quarto paper (215 mm by 275 mm)} \item{\bold{16}}{ Standard paper (10 in. by 14 in.)} \item{\bold{17}}{ Standard paper (11 in. by 17 in.)} \item{\bold{18}}{ Note paper (8.5 in. by 11 in.)} \item{\bold{19}}{ #9 envelope (3.875 in. by 8.875 in.)} \item{\bold{20}}{ #10 envelope (4.125 in. by 9.5 in.)} \item{\bold{21}}{ #11 envelope (4.5 in. by 10.375 in.)} \item{\bold{22}}{ #12 envelope (4.75 in. by 11 in.)} \item{\bold{23}}{ #14 envelope (5 in. by 11.5 in.)} \item{\bold{24}}{ C paper (17 in. by 22 in.)} \item{\bold{25}}{ D paper (22 in. by 34 in.)} \item{\bold{26}}{ E paper (34 in. by 44 in.)} \item{\bold{27}}{ DL envelope (110 mm by 220 mm)} \item{\bold{28}}{ C5 envelope (162 mm by 229 mm)} \item{\bold{29}}{ C3 envelope (324 mm by 458 mm)} \item{\bold{30}}{ C4 envelope (229 mm by 324 mm)} \item{\bold{31}}{ C6 envelope (114 mm by 162 mm)} \item{\bold{32}}{ C65 envelope (114 mm by 229 mm)} \item{\bold{33}}{ B4 envelope (250 mm by 353 mm)} \item{\bold{34}}{ B5 envelope (176 mm by 250 mm)} \item{\bold{35}}{ B6 envelope (176 mm by 125 mm)} \item{\bold{36}}{ Italy envelope (110 mm by 230 mm)} \item{\bold{37}}{ Monarch envelope (3.875 in. by 7.5 in.).} \item{\bold{38}}{ 6 3/4 envelope (3.625 in. by 6.5 in.)} \item{\bold{39}}{ US standard fanfold (14.875 in. by 11 in.)} \item{\bold{40}}{ German standard fanfold (8.5 in. by 12 in.)} \item{\bold{41}}{ German legal fanfold (8.5 in. by 13 in.)} \item{\bold{42}}{ ISO B4 (250 mm by 353 mm)} \item{\bold{43}}{ Japanese double postcard (200 mm by 148 mm)} \item{\bold{44}}{ Standard paper (9 in. by 11 in.)} \item{\bold{45}}{ Standard paper (10 in. by 11 in.)} \item{\bold{46}}{ Standard paper (15 in. by 11 in.)} \item{\bold{47}}{ Invite envelope (220 mm by 220 mm)} \item{\bold{50}}{ Letter extra paper (9.275 in. by 12 in.)} \item{\bold{51}}{ Legal extra paper (9.275 in. by 15 in.)} \item{\bold{52}}{ Tabloid extra paper (11.69 in. by 18 in.)} \item{\bold{53}}{ A4 extra paper (236 mm by 322 mm)} \item{\bold{54}}{ Letter transverse paper (8.275 in. by 11 in.)} \item{\bold{55}}{ A4 transverse paper (210 mm by 297 mm)} \item{\bold{56}}{ Letter extra transverse paper (9.275 in. by 12 in.)} \item{\bold{57}}{ SuperA/SuperA/A4 paper (227 mm by 356 mm)} \item{\bold{58}}{ SuperB/SuperB/A3 paper (305 mm by 487 mm)} \item{\bold{59}}{ Letter plus paper (8.5 in. by 12.69 in.)} \item{\bold{60}}{ A4 plus paper (210 mm by 330 mm)} \item{\bold{61}}{ A5 transverse paper (148 mm by 210 mm)} \item{\bold{62}}{ JIS B5 transverse paper (182 mm by 257 mm)} \item{\bold{63}}{ A3 extra paper (322 mm by 445 mm)} \item{\bold{64}}{ A5 extra paper (174 mm by 235 mm)} \item{\bold{65}}{ ISO B5 extra paper (201 mm by 276 mm)} \item{\bold{66}}{ A2 paper (420 mm by 594 mm)} \item{\bold{67}}{ A3 transverse paper (297 mm by 420 mm)} \item{\bold{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 } openxlsx/man/addStyle.Rd0000644000176200001440000000340413563175347014764 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") addStyle(wb, sheet = 1, headerStyle, rows = 1, cols = 1:6, gridExpand = TRUE) ## 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}} expand.grid } \author{ Alexander Walker } openxlsx/man/setFooter.Rd0000644000176200001440000000175213560564727015172 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") \dontrun{saveWorkbook(wb, "headerFooterExample.xlsx", overwrite = TRUE)} } } \author{ Alexander Walker } openxlsx/man/pageBreak.Rd0000644000176200001440000000154013560564727015074 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}} } openxlsx/man/getNamedRegions.Rd0000644000176200001440000000223613560564727016271 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}} } openxlsx/man/figures/0000755000176200001440000000000013560564727014370 5ustar liggesusersopenxlsx/man/figures/tableoptions.png0000644000176200001440000000636113560564727017607 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[> 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/removeRowHeights.Rd0000644000176200001440000000144413560564727016517 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}} } \author{ Alexander Walker } openxlsx/man/dataValidation.Rd0000644000176200001440000000430613563175347016141 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}{Columns to apply conditional formatting to} \item{rows}{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.Rd0000644000176200001440000000167413560564727015660 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}} } \author{ Alexander Walker } openxlsx/man/insertPlot.Rd0000644000176200001440000000361613563175347015363 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. xy[[2]] when xy is given.} \item{startCol}{Column coordinate of upper left corner of figure. 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}} } \author{ Alexander Walker } openxlsx/man/renameWorksheet.Rd0000644000176200001440000000200413560564727016352 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}} } \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.Rd0000644000176200001440000001451113565534170015133 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, xy = NULL, colNames = TRUE, rowNames = FALSE, headerStyle = NULL, borders = c("none", "surrounding", "rows", "columns", "all"), borderColour = getOption("openxlsx.borderColour", "black"), borderStyle = getOption("openxlsx.borderStyle", "thin"), withFilter = FALSE, keepNA = FALSE, na.string = NULL, name = NULL, sep = ", " ) } \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{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{http://www.colorpicker.com}{here}).} \item{borderStyle}{Border line style \itemize{ \item{\bold{none}}{ no border} \item{\bold{thin}}{ thin border} \item{\bold{medium}}{ medium border} \item{\bold{dashed}}{ dashed border} \item{\bold{dotted}}{ dotted border} \item{\bold{thick}}{ thick border} \item{\bold{double}}{ double line border} \item{\bold{hair}}{ hairline border} \item{\bold{mediumDashed}}{ medium weight dashed border} \item{\bold{dashDot}}{ dash-dot border} \item{\bold{mediumDashDot}}{ medium weight dash-dot border} \item{\bold{dashDotDot}}{ dash-dot-dot border} \item{\bold{mediumDashDotDot}}{ medium weight dash-dot-dot border} \item{\bold{slantDashDot}}{ slanted dash-dot border} }} \item{withFilter}{If \code{TRUE}, 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).} } \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}} } \author{ Alexander Walker } openxlsx/man/deleteData.Rd0000644000176200001440000000212113560564727015243 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.Rd0000644000176200001440000000161513560564727016015 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}} } \author{ Alexander Walker } openxlsx/man/getSheetNames.Rd0000644000176200001440000000073013560564727015747 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/all.equal.Rd0000644000176200001440000000063113560564727015071 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.Rd0000644000176200001440000000252613572442033015772 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}}.} \item{visible}{TRUE or FALSE. Is comment visible.} \item{width}{Textbox integer width in number of cells} \item{height}{Textbox integer height in number of cells} } \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}} } openxlsx/man/getBaseFont.Rd0000644000176200001440000000111313560564727015410 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.Rd0000644000176200001440000000321013563175347015622 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. } \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}} } \author{ Alexander Walker } openxlsx/man/sheets.Rd0000644000176200001440000000143013560564727014504 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}} } \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}} to rename a worksheet in a Workbook } \author{ Alexander Walker } openxlsx/man/removeFilter.Rd0000644000176200001440000000151713560564727015662 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.Rd0000644000176200001440000000102213560564727015676 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.Rd0000644000176200001440000000264713560564727015306 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}} } \author{ Alexander Walker } openxlsx/man/convertToDateTime.Rd0000644000176200001440000000133213560564727016612 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, tx = "Australia/Perth") } openxlsx/man/getCellRefs.Rd0000644000176200001440000000106613565534170015407 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/getDateOrigin.Rd0000644000176200001440000000174013560564727015742 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}} } \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}} } \author{ Alexander Walker } openxlsx/man/makeHyperlinkString.Rd0000644000176200001440000000122713563175347017206 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() } \seealso{ \code{\link{writeFormula}} } openxlsx/man/modifyBaseFont.Rd0000644000176200001440000000177513563175347016135 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.Rd0000644000176200001440000000272613563175347015470 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}} } \author{ Alexander Walker } openxlsx/man/setHeaderFooter.Rd0000644000176200001440000000557513563175347016311 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{\bold{&[Page]}}{ Page number} \item{\bold{&[Pages]}}{ Number of pages} \item{\bold{&[Date]}}{ Current date} \item{\bold{&[Time]}}{ Current time} \item{\bold{&[Path]}}{ File path} \item{\bold{&[File]}}{ File name} \item{\bold{&[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}} to set headers and footers when adding a worksheet } \author{ Alexander Walker } openxlsx/man/removeColWidths.Rd0000644000176200001440000000142713560564727016335 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}} } \author{ Alexander Walker } openxlsx/man/getTables.Rd0000644000176200001440000000117513560564727015131 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.Rd0000644000176200001440000000123513560564727016034 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}} \code{\link{writeComment}} } openxlsx/man/createStyle.Rd0000644000176200001440000001335613563175347015506 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 = "GENERAL", border = NULL, borderColour = getOption("openxlsx.borderColour", "black"), borderStyle = getOption("openxlsx.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{\bold{GENERAL}} \item{\bold{NUMBER}} \item{\bold{CURRENCY}} \item{\bold{ACCOUNTING}} \item{\bold{DATE}} \item{\bold{LONGDATE}} \item{\bold{TIME}} \item{\bold{PERCENTAGE}} \item{\bold{FRACTION}} \item{\bold{SCIENTIFIC}} \item{\bold{TEXT}} \item{\bold{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{\bold{"top"}}{ Top border} \item{\bold{bottom}}{ Bottom border} \item{\bold{left}}{ Left border} \item{\bold{right}}{ Right border} \item{\bold{TopBottom} or \bold{c("top", "bottom")}}{ Top and bottom border} \item{\bold{LeftRight} or \bold{c("left", "right")}}{ Left and right border} \item{\bold{TopLeftRight} or \bold{c("top", "left", "right")}}{ Top, Left and right border} \item{\bold{TopBottomLeftRight} or \bold{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{\bold{none}}{ No Border} \item{\bold{thin}}{ thin border} \item{\bold{medium}}{ medium border} \item{\bold{dashed}}{ dashed border} \item{\bold{dotted}}{ dotted border} \item{\bold{thick}}{ thick border} \item{\bold{double}}{ double line border} \item{\bold{hair}}{ Hairline border} \item{\bold{mediumDashed}}{ medium weight dashed border} \item{\bold{dashDot}}{ dash-dot border} \item{\bold{mediumDashDot}}{ medium weight dash-dot border} \item{\bold{dashDotDot}}{ dash-dot-dot border} \item{\bold{mediumDashDotDot}}{ medium weight dash-dot-dot border} \item{\bold{slantDashDot}}{ slanted dash-dot border} }} \item{bgFill}{Cell background fill colour. A valid colour (belonging to colours()) or a valid hex colour beginning with "#". -- \bold{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{\bold{left}}{ Left horizontal align cell contents} \item{\bold{right}}{ Right horizontal align cell contents} \item{\bold{center}}{ Center horizontal align cell contents} }} \item{valign}{A name Vertical alignment of cell contents \itemize{ \item{\bold{top}}{ Top vertical align cell contents} \item{\bold{center}}{ Center vertical align cell contents} \item{\bold{bottom}}{ Bottom vertical align cell contents} }} \item{textDecoration}{Text styling. \itemize{ \item{\bold{bold}}{ Bold cell contents} \item{\bold{strikeout}}{ Strikeout cell contents} \item{\bold{italic}}{ Italicise cell contents} \item{\bold{underline}}{ Underline cell contents} \item{\bold{underline2}}{ Double 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}} } \author{ Alexander Walker } openxlsx/DESCRIPTION0000644000176200001440000000372713572447172013665 0ustar liggesusersType: Package Package: openxlsx Title: Read, Write and Edit xlsx Files Version: 4.1.4 Date: 2019-12-04 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")) 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, utils, zip, stringi Suggests: knitr, testthat, roxygen2 LinkingTo: Rcpp VignetteBuilder: knitr Encoding: UTF-8 RoxygenNote: 7.0.2 Collate: 'CommentClass.R' 'HyperlinkClass.R' 'RcppExports.R' 'class_definitions.R' 'StyleClass.R' 'WorkbookClass.R' 'baseXML.R' 'borderFunctions.R' 'chartsheet_class.R' 'conditional_formatting.R' 'helperFunctions.R' 'loadWorkbook.R' 'onUnload.R' 'openXL.R' 'openxlsx.R' 'openxlsxCoerce.R' 'readWorkbook.R' 'sheet_data_class.R' 'workbook_column_widths.R' 'workbook_read_workbook.R' 'workbook_write_data.R' 'worksheet_class.R' 'wrappers.R' 'writeData.R' 'writeDataTable.R' 'writexlsx.R' NeedsCompilation: yes Packaged: 2019-12-06 12:16:01 UTC; ceadm Author: Philipp Schauberger [aut, cre], Alexander Walker [aut], Luca Braglia [ctb] Maintainer: Philipp Schauberger Repository: CRAN Date/Publication: 2019-12-06 12:50:02 UTC openxlsx/build/0000755000176200001440000000000013572443201013232 5ustar liggesusersopenxlsx/build/vignette.rds0000644000176200001440000000037113572443201015572 0ustar liggesusersP0" ZXaj!MJi[5 Vă{of bi YzDV+~ldӗ%^fxzTIP]$/*0 G]M‘OSi`{h|D2|{B-l tNXO϶_+|4תN|WwK۶Ǎ2ȋ ,Kr~d+3ɜO:openxlsx/tests/0000755000176200001440000000000013572442033013277 5ustar liggesusersopenxlsx/tests/testthat/0000755000176200001440000000000013572447172015150 5ustar liggesusersopenxlsx/tests/testthat/test-load_read_file_read_equality.R0000644000176200001440000000336113560564727024077 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 <- file.path(tempdir(), "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.R0000644000176200001440000002652513560564727021077 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 = 1:(nrow(iris)+1), cols = 1: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 <- tempfile(fileext = ".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(object = df1, expected = df2) df1 <- read.xlsx(wb, namedRegion = "region1") expect_equal(object = class(df1), expected = "data.frame") expect_equal(object = nrow(df1), expected = 0) expect_equal(object = ncol(df1), expected = 1) df1 <- read.xlsx(wb, namedRegion = "region1", colNames = FALSE) expect_equal(object = class(df1), expected = "data.frame") expect_equal(object = nrow(df1), expected = 1) expect_equal(object = ncol(df1), expected = 1) df1 <- read.xlsx(wb, namedRegion = "region1", rowNames = TRUE) expect_equal(object = class(df1), expected = "data.frame") expect_equal(object = nrow(df1), expected = 0) expect_equal(object = ncol(df1), expected = 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 <- tempfile(fileext = ".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, col = 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 <- tempfile(fileext = ".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, col = 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 <- tempfile(fileext = ".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) }) openxlsx/tests/testthat/test-write_data_to_sheetData.R0000644000176200001440000002324313571765333023064 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 <- file.path(tempdir(), "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) }) openxlsx/tests/testthat/test-read_from_created_wb.R0000644000176200001440000003034313565534073022367 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 <- file.path(tempdir(), "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) <- 1: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) <- 1: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.R0000644000176200001440000000063013560564727021565 0ustar liggesusers context("Protection") test_that("Protect Workbook", { wb <- createWorkbook() addWorksheet(wb, "s1") wb$protectWorkbook(password = "abcdefghij") expect_true(wb$workbookProtection == "") wb$protectWorkbook(protect = FALSE, password = "abcdefghij", lockStructure = TRUE, lockWindows = TRUE) expect_true(wb$workbookProtection == "") }) openxlsx/tests/testthat/test-write_data_to_sheetData_NAs.R0000644000176200001440000000701513565534073023622 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.R0000644000176200001440000000164413560564727020414 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) 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) 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)) }) openxlsx/tests/testthat/test-read_write_logicals.R0000644000176200001440000000232213560564727022254 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-border_parsing.R0000644000176200001440000004323113560564727021256 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.R0000644000176200001440000002062313560564727021514 0ustar liggesusers context("Skip Empty Rows") test_that("skip empty rows", { xlsxfile <- tempfile() 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 <- tempfile() 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.R0000644000176200001440000000334013560564727023137 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.R0000644000176200001440000000353413560564727022153 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.R0000644000176200001440000000315613560564727020736 0ustar liggesusers context("Read Sources") test_that("read.xlsx from different sources", { ## URL xlsxFile <- "https://github.com/awalker89/openxlsx/raw/master/inst/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/awalker89/openxlsx/raw/master/inst/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/awalker89/openxlsx/raw/master/inst/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/awalker89/openxlsx/raw/master/inst/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.R0000644000176200001440000014011513572234026022106 0ustar liggesusers context("Writing Sheet Data XML") test_that("Writing sheetData rows XML - iris", { temp_file <- tempfile(fileext = ".xlsx") openxlsx::write.xlsx(iris, temp_file) unzip(temp_file, exdir = tempdir()) x <- readLines(file.path(tempdir(), "xl", "worksheets", "sheet1.xml"), warn = FALSE, encoding = "UTF-8") 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 1:length(expected_rows)) expect_equal(rows[i], expected = expected_rows[i]) unlink(x = temp_file) }) test_that("Writing sheetData rows XML - mtcars", { temp_file <- tempfile(fileext = ".xlsx") openxlsx::write.xlsx(mtcars, temp_file, row.names = TRUE) unzip(temp_file, exdir = tempdir()) x <- readLines(file.path(tempdir(), "xl", "worksheets", "sheet1.xml"), warn = FALSE, encoding = "UTF-8") 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 1:length(expected_rows)) expect_equal(rows[i], expected = expected_rows[i]) }) openxlsx/tests/testthat/test-style_replacing.R0000644000176200001440000000073613560564727021445 0ustar liggesusers # context("Replacing styles") # # # # test_that("Replace styles", { # # # tempFile <- file.path(tempdir(), "temp.xlsx") # # wb <- loadWorkbook(file = file.path(path.package("openxlsx"), "loadExample.xlsx")) # # # # ## create a new style and replace style 2 # # # # newStyle <- createStyle(fgFill = "#FF0000") # # # # ## replace style 2 # # getStyles(wb) ## prints styles # # replaceStyle(wb, 87, newStyle = newStyle) # # # # rm(wb) # # }) openxlsx/tests/testthat/test-date_time_conversion.R0000644000176200001440000000155013560564727022454 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) }) 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.R0000644000176200001440000001012713560564727020537 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.R0000644000176200001440000001464013560564727021464 0ustar liggesusers context("Skip Empty Cols") test_that("skip empty rows", { xlsxfile <- tempfile() 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-write_read_equality.R0000644000176200001440000002063613565534073022320 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" 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) }) 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) 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 1: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", { fileName <- file.path(tempdir(), "tmp.xlsx") curr_wd <- getwd() ## rowNames = colNames = TRUE write.xlsx(mtcars, file = fileName, overwrite = TRUE, row.names = TRUE) x <- read.xlsx(fileName, sheet = 1, rowNames = TRUE) expect_equal(object = x, expected = mtcars, check.attributes = TRUE) ## rowNames = TRUE, colNames = FALSE write.xlsx(mtcars, file = fileName, overwrite = TRUE, rowNames = TRUE, colNames = FALSE) x <- read.xlsx(fileName, sheet = 1, rowNames = TRUE, colNames = FALSE) expect_equal(object = x, expected = mtcars, check.attributes = FALSE) expect_equal(object = rownames(x), expected = rownames(mtcars)) ## rowNames = FALSE, colNames = TRUE write.xlsx(mtcars, file = fileName, overwrite = TRUE, rowNames = FALSE, colNames = TRUE) x <- read.xlsx(fileName, sheet = 1, rowNames = FALSE, colNames = TRUE) expect_equal(object = x, expected = mtcars, check.attributes = FALSE) ## rowNames = FALSE, colNames = FALSE write.xlsx(mtcars, file = fileName, overwrite = TRUE, rowNames = FALSE, colNames = FALSE) x <- read.xlsx(fileName, sheet = 1, rowNames = FALSE, colNames = FALSE) expect_equal(object = x, expected = mtcars, check.attributes = FALSE) expect_equal(object = getwd(), curr_wd) unlink(fileName, recursive = TRUE, force = TRUE) }) test_that("Writing then reading returns identical data.frame 3", { ## 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" = "yyyy-mm-dd") fileName <- file.path(tempdir(), "allClasses.xlsx") write.xlsx(df, file = fileName, overwrite = TRUE) ## rows, cols combinations rows <- 1:4 cols <- c(1, 3, 5) x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols) expect_equal(object = x, expected = genDf()[sort((rows-1)[(rows-1) <= nrow(df)]), sort(cols[cols <= ncol(df)])], check.attributes = FALSE) rows <- 1:4 cols <- 1:9 x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols) expect_equal(object = x, expected = genDf()[sort((rows-1)[(rows-1) <= nrow(df)]), sort(cols[cols <= ncol(df)])], check.attributes = FALSE) rows <- 1:200 cols <- c(5, 99, 2) x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols) expect_equal(object = x, expected = genDf()[sort((rows-1)[(rows-1) <= nrow(df)]), sort(cols[cols <= ncol(df)])], check.attributes = FALSE) rows <- 1000:900 cols <- c(5, 99, 2) suppressWarnings(x <- read.xlsx(xlsxFile = fileName, detectDates = TRUE, rows = rows, cols = cols)) expect_equal(object = x, expected = NULL, check.attributes = FALSE) unlink(fileName, recursive = TRUE, force = TRUE) }) 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 <- tempfile(fileext = ".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 <- tempfile(fileext = ".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 <- tempfile(fileext = ".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 <- tempfile(fileext = ".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.R0000644000176200001440000027446313560564727021625 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) }) openxlsx/tests/testthat/test-write_xlsx_vector_args.R0000644000176200001440000000236313560564727023065 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(object = getSheetNames(tmp_file), expected = c("a", "b")) expect_equal(object = names(wb), expected = c("a", "b")) expect_true(object = grepl('rgb="FFFF0000"', wb$worksheets[[1]]$sheetPr)) expect_true(object = grepl('rgb="FF0000FF"', wb$worksheets[[2]]$sheetPr)) expect_true(object = grepl('zoomScale="50"', wb$worksheets[[1]]$sheetViews)) expect_true(object = grepl('zoomScale="90"', wb$worksheets[[2]]$sheetViews)) expect_true(object = grepl('showGridLines="0"', wb$worksheets[[1]]$sheetViews)) expect_true(object = 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) }) openxlsx/tests/testthat/test-encoding.R0000644000176200001440000000134713560564727020046 0ustar liggesusers context("Encoding Tests") test_that("Write read encoding equality", { tempFile <- file.path(tempdir(), "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) }) openxlsx/tests/testthat/test-Workbook_properties.R0000644000176200001440000000220513560564727022323 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("Alex" %in% wb$core) expect_true("title here" %in% wb$core) expect_true("this & that" %in% wb$core) expect_true("some category" %in% 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("Alex 2" %in% wb$core) expect_true("title here 2" %in% wb$core) expect_true("this & that 2" %in% wb$core) expect_true("some category 2" %in% wb$core) ## maintain on load wb_loaded <- loadWorkbook(fl) expect_equal(object = wb_loaded$core, expected = paste0(wb$core, collapse = "")) }) openxlsx/tests/testthat/test-getCellRefs.R0000644000176200001440000000065613563557547020466 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-validate_table_name.R0000644000176200001440000000455113560564727022220 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.R0000644000176200001440000002362013560564727023441 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.R0000644000176200001440000002173113560564727021404 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 <- tempfile(fileext = ".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 <- tempfile(fileext = ".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 <- tempfile(fileext = ".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.R0000644000176200001440000001640113560564727022161 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 <- file.path(tempdir(), "orderingTest.xlsx") ## 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 <- file.path(tempdir(), "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.R0000644000176200001440000001440713560564727023403 0ustar liggesusers context("Images and Tables.") test_that("Images and Tables - reordering and removing", { if(FALSE){ options('stringsAsFactors' = FALSE) tempFile <- file.path(tempdir(), "break.xlsx") 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) } }) openxlsx/tests/testthat/test-read_xlsx_correct_sheet.R0000644000176200001440000000256113560564727023161 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-remove_worksheets.R0000644000176200001440000000315613560564727022033 0ustar liggesusers context("Removing worksheets.") test_that("Deleting worksheets", { tempFile <- file.path(tempdir(), "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-protect-worksheet.R0000644000176200001440000000151213560564727021743 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.R0000644000176200001440000001072213560564727021257 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-v3_0_0_bugs.R0000644000176200001440000000062313560564727020262 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.R0000644000176200001440000000140013560564727021701 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) mergeCells(wb = wb, sheet = 1, cols = 2:4, rows = 1) tmp_file <- tempfile(fileext = ".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")) }) openxlsx/tests/testthat/test-loading_workbook_unzipped.R0000644000176200001440000000107413560564727023525 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.R0000644000176200001440000000240213560564727021505 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") }) openxlsx/tests/testthat.R0000644000176200001440000000007313572442033015262 0ustar liggesuserslibrary(testthat) library(openxlsx) test_check("openxlsx")openxlsx/src/0000755000176200001440000000000013572443201012722 5ustar liggesusersopenxlsx/src/read_workbook.cpp0000644000176200001440000004242113561007164016263 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; // ifstream file; // file.open(xmlFile.c_str()); // while (file >> buf) // xml += buf + ' '; std::string xml = read_file_newline(xmlFile); 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 // 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("", start)) != string::npos){ ++ocs; start += 4; } if(ocs == 0){ res = List::create(Rcpp::Named("nRows") = 0, Rcpp::Named("r") = 0); return res; } // pull out cell merges CharacterVector merge_cell_xml = getChildlessNode(xml, "", pos + 8); // have to atleast pass if(vPos < nextPos){ cell = xml.substr(pos, nextPos - pos); // Pull out ref pos = cell.find(rtag, 0); // find r=" endPos = cell.find(tagEnd, pos + 3); // find next " r[i] = cell.substr(pos + 3, endPos - pos - 3).c_str(); // Pull out type pos = cell.find(ttag, 0); // find t=" if(pos != std::string::npos){ endPos = cell.find(tagEnd, pos + 4); // find next " t[i] = cell.substr(pos + 4, endPos - pos - 4).c_str(); } // Pull out style if(getDates){ pos = cell.find(stag, 0); // find s=" if(pos != std::string::npos){ endPos = cell.find(tagEnd, pos + 4); // find next " s[i] = cell.substr(pos + 4, endPos - pos - 4).c_str(); } } // If the value is s or shared we replace with sharedString // If it's b we replace with "TRUE" or "FALSE" // If the value is str it's already a string if(t[i] == "e"){ v[i] = NA_STRING; }else{ // find tag and end tag endPos = cell.find(vtagEnd, 0); if(endPos != std::string::npos){ pos = cell.find("", pos); v[i] = cell.substr(pos + 1, endPos - pos - 1); } // possible values for t are n, s, shared, b, str, e // do replacement if(t[i] == "s"){ ss_ind = atoi(v[i]); v[i] = sharedStrings[ss_ind]; if(v[i] == "openxlsx_na_vlu"){ v[i] = NA_STRING; } string_refs[i] = r[i]; }else if(t[i] == "b"){ if(v[i] == "1"){ v[i] = "TRUE"; }else{ v[i] = "FALSE"; } string_refs[i] = r[i]; }else if(t[i] == "str"){ string_refs[i] = r[i]; } } i++; // INCREMENT OVER OCCURENCES } pos = 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(unsigned short 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.cpp0000644000176200001440000001275313560750030015557 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; } } 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{ 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.h0000644000176200001440000000553513560564727015001 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); openxlsx/src/helper_functions.cpp0000644000176200001440000001422513560747756017024 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); } openxlsx/src/load_workbook.cpp0000644000176200001440000006026013565534346016303 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); // 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); }else{ colWidths[i] = List(0); rowHeights[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, " 0) this_worksheet.field("sheetPr") = sheetPr; // Freeze Panes CharacterVector node_xml = getChildlessNode(xml_pre, " 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; CharacterVector column_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; buf = cols[ci]; if(buf.find("customWidth", 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("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("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"; } while(min_c <= max_c){ widths.push_back(tmp_width); columns.push_back(min_c); column_hidden.push_back(tmp_hidden); min_c++; } } } if(widths.size() > 0){ CharacterVector tmp_widths(widths); tmp_widths.attr("names") = columns; tmp_widths.attr("hidden") = column_hidden; colWidths[i] = tmp_widths; } } /* --- 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, " 0) { this_worksheet.field("sheetProtection") = node_xml; } node_xml = getChildlessNode(xml_post, " 0) this_worksheet.field("autoFilter") = node_xml; node_xml = getChildlessNode(xml_post, " 0) this_worksheet.field("hyperlinks") = node_xml; node_xml = getChildlessNode(xml_post, " 0) this_worksheet.field("pageMargins") = node_xml; node_xml = getChildlessNode(xml_post, " 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, " 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, " 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; } // Pull out type pos_t = cell.find(" t=", 0); pos_f = 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; // 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") = rowNumbers; rowHeights[i] = heights; } // 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; 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()); } return wrap(r) ; } // [[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()); } return wrap(r) ; } // [[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 wrap(r) ; } // [[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(); if(xml.length() == 0) return wrap(NA_STRING); xml = " " + xml; 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 wrap(r) ; } // [[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()); } return wrap(r) ; } // [[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.cpp0000644000176200001440000002005113560564727015573 0ustar liggesusers #include "openxlsx.h" // [[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_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); 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()) ); //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 + ""; return wrap(table); } openxlsx/src/openxlsx_init.c0000644000176200001440000001320713560564727016012 0ustar liggesusers#include #include #include // for NULL #include /* generated with tools::package_native_routine_registration_skeleton(".") */ /* .Call calls */ extern SEXP _openxlsx_build_cell_merges(SEXP); extern SEXP _openxlsx_build_cell_types_integer(SEXP, SEXP); extern SEXP _openxlsx_build_table_xml(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_buildCellList(SEXP, SEXP, SEXP); extern SEXP _openxlsx_buildCellTypes(SEXP, SEXP); extern SEXP _openxlsx_buildMatrixMixed(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_buildMatrixNumeric(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_calc_column_widths(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_calc_number_rows(SEXP, SEXP); extern SEXP _openxlsx_cell_ref_to_col(SEXP); extern SEXP _openxlsx_convert_from_excel_ref(SEXP); extern SEXP _openxlsx_convert_to_excel_ref(SEXP, SEXP); extern SEXP _openxlsx_convert_to_excel_ref_expand(SEXP, SEXP, SEXP); extern SEXP _openxlsx_cppReadFile(SEXP); extern SEXP _openxlsx_get_extLst_Major(SEXP); extern SEXP _openxlsx_get_letters(); extern SEXP _openxlsx_get_shared_strings(SEXP, SEXP); extern SEXP _openxlsx_getAttr(SEXP, SEXP); extern SEXP _openxlsx_getCellInfo(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_getChildlessNode(SEXP, SEXP); extern SEXP _openxlsx_getChildlessNode_ss(SEXP, SEXP); extern SEXP _openxlsx_getNodes(SEXP, SEXP); extern SEXP _openxlsx_getOpenClosedNode(SEXP, SEXP, SEXP); extern SEXP _openxlsx_int_2_cell_ref(SEXP); extern SEXP _openxlsx_isInternalHyperlink(SEXP); extern SEXP _openxlsx_loadworksheets(SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_map_cell_types_to_char(SEXP); extern SEXP _openxlsx_map_cell_types_to_integer(SEXP); extern SEXP _openxlsx_matrixRowInds(SEXP); extern SEXP _openxlsx_read_file_newline(SEXP); extern SEXP _openxlsx_read_workbook(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_write_worksheet_xml(SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_write_worksheet_xml_2(SEXP, SEXP, SEXP, SEXP, SEXP); extern SEXP _openxlsx_write_file(SEXP, SEXP, SEXP, SEXP); static const R_CallMethodDef CallEntries[] = { {"_openxlsx_build_cell_merges", (DL_FUNC) &_openxlsx_build_cell_merges, 1}, {"_openxlsx_build_cell_types_integer", (DL_FUNC) &_openxlsx_build_cell_types_integer, 2}, {"_openxlsx_build_table_xml", (DL_FUNC) &_openxlsx_build_table_xml, 6}, {"_openxlsx_buildCellList", (DL_FUNC) &_openxlsx_buildCellList, 3}, {"_openxlsx_buildCellTypes", (DL_FUNC) &_openxlsx_buildCellTypes, 2}, {"_openxlsx_buildMatrixMixed", (DL_FUNC) &_openxlsx_buildMatrixMixed, 8}, {"_openxlsx_buildMatrixNumeric", (DL_FUNC) &_openxlsx_buildMatrixNumeric, 6}, {"_openxlsx_calc_column_widths", (DL_FUNC) &_openxlsx_calc_column_widths, 7}, {"_openxlsx_calc_number_rows", (DL_FUNC) &_openxlsx_calc_number_rows, 2}, {"_openxlsx_cell_ref_to_col", (DL_FUNC) &_openxlsx_cell_ref_to_col, 1}, {"_openxlsx_convert_from_excel_ref", (DL_FUNC) &_openxlsx_convert_from_excel_ref, 1}, {"_openxlsx_convert_to_excel_ref", (DL_FUNC) &_openxlsx_convert_to_excel_ref, 2}, {"_openxlsx_convert_to_excel_ref_expand", (DL_FUNC) &_openxlsx_convert_to_excel_ref_expand, 3}, {"_openxlsx_cppReadFile", (DL_FUNC) &_openxlsx_cppReadFile, 1}, {"_openxlsx_get_extLst_Major", (DL_FUNC) &_openxlsx_get_extLst_Major, 1}, {"_openxlsx_get_letters", (DL_FUNC) &_openxlsx_get_letters, 0}, {"_openxlsx_get_shared_strings", (DL_FUNC) &_openxlsx_get_shared_strings, 2}, {"_openxlsx_getAttr", (DL_FUNC) &_openxlsx_getAttr, 2}, {"_openxlsx_getCellInfo", (DL_FUNC) &_openxlsx_getCellInfo, 6}, {"_openxlsx_getChildlessNode", (DL_FUNC) &_openxlsx_getChildlessNode, 2}, {"_openxlsx_getChildlessNode_ss", (DL_FUNC) &_openxlsx_getChildlessNode_ss, 2}, {"_openxlsx_getNodes", (DL_FUNC) &_openxlsx_getNodes, 2}, {"_openxlsx_getOpenClosedNode", (DL_FUNC) &_openxlsx_getOpenClosedNode, 3}, {"_openxlsx_int_2_cell_ref", (DL_FUNC) &_openxlsx_int_2_cell_ref, 1}, {"_openxlsx_isInternalHyperlink", (DL_FUNC) &_openxlsx_isInternalHyperlink, 1}, {"_openxlsx_loadworksheets", (DL_FUNC) &_openxlsx_loadworksheets, 4}, {"_openxlsx_map_cell_types_to_char", (DL_FUNC) &_openxlsx_map_cell_types_to_char, 1}, {"_openxlsx_map_cell_types_to_integer", (DL_FUNC) &_openxlsx_map_cell_types_to_integer, 1}, {"_openxlsx_matrixRowInds", (DL_FUNC) &_openxlsx_matrixRowInds, 1}, {"_openxlsx_read_file_newline", (DL_FUNC) &_openxlsx_read_file_newline, 1}, {"_openxlsx_read_workbook", (DL_FUNC) &_openxlsx_read_workbook, 11}, {"_openxlsx_write_worksheet_xml", (DL_FUNC) &_openxlsx_write_worksheet_xml, 4}, {"_openxlsx_write_worksheet_xml_2", (DL_FUNC) &_openxlsx_write_worksheet_xml_2, 5}, {"_openxlsx_write_file", (DL_FUNC) &_openxlsx_write_file, 4}, {NULL, NULL, 0} }; void R_init__openxlsx(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, TRUE); R_forceSymbols(dll, FALSE); } openxlsx/src/write_file_2.cpp0000644000176200001440000000763513560750074016021 0ustar liggesusers #include "openxlsx.h" // [[Rcpp::export]] SEXP write_worksheet_xml_2( std::string prior , std::string post , Reference sheet_data , CharacterVector row_heights , 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; 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, jsut 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_rows = row_heights.attr("names"); size_t n_row_heights = row_heights.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; 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_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){ if((unique_rows[i] == row_heights_rows[h]) & row_has_data){ // this row has a row height and cell_xml data xmlFile << "" + cell_xml + ""; h++; }else if(row_has_data){ xmlFile << "" + cell_xml + ""; }else{ xmlFile << ""; h++; } }else{ xmlFile << "" + cell_xml + ""; } } // write closing tag and XML post data xmlFile << ""; xmlFile << post; //close file xmlFile.close(); return wrap(0); } openxlsx/src/RcppExports.cpp0000644000176200001440000005742313565534666015754 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; // 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 } // 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, CharacterVector row_heights, std::string R_fileName); RcppExport SEXP _openxlsx_write_worksheet_xml_2(SEXP priorSEXP, SEXP postSEXP, SEXP sheet_dataSEXP, SEXP row_heightsSEXP, 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< CharacterVector >::type row_heights(row_heightsSEXP); 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, 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_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, 5}, {NULL, NULL, 0} }; RcppExport void R_init_openxlsx(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } openxlsx/vignettes/0000755000176200001440000000000013572443201014143 5ustar liggesusersopenxlsx/vignettes/formatting.Rnw0000644000176200001440000003001713560564727017024 0ustar liggesusers \documentclass[11pt]{article} \usepackage{graphicx, verbatim} % \VignetteEngine{knitr::knitr} % \VignetteIndexEntry{Formatting Examples} % \VignetteDepends{openxlsx} % \VignetteKeyword{excel} % \VignetteKeyword{xlsx} % \VignetteKeyword{spreadsheet} \usepackage{geometry} \geometry{ a4paper, total={210mm,297mm}, left=15mm, right=15mm, top=20mm, bottom=20mm, } \begin{document} \title{Examples} \author{Alexander Walker\\ \texttt{Alexander.Walker1989@gmail.com}} \maketitle \section{Formatting with writeData and writeDataTable} \begin{verbatim} ## 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 \end{verbatim} \noindent The 'tableStyle' argument in writeDataTable can be any ofthe predefined tableStyles in Excel. \begin{center} \includegraphics[width=14cm]{tableStyles} \end{center} \newpage \section{Date Formatting} \begin{verbatim} # 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) \end{verbatim} \newpage \section{DateTime Formatting} \begin{verbatim} 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") \end{verbatim} \newpage \section{Conditional Formatting} \begin{verbatim} 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) \end{verbatim} \newpage \section{Numeric Formatting} \begin{verbatim} 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]*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) 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) } } \end{verbatim} \end{document} openxlsx/vignettes/tableStyles.PNG0000644000176200001440000007757113560564727017043 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/Introduction.Rnw0000644000176200001440000003621513560564727017341 0ustar liggesusers \documentclass[11pt]{article} \usepackage{graphicx, verbatim} % \VignetteEngine{knitr::knitr} % \VignetteIndexEntry{Examples} % \VignetteDepends{openxlsx} % \VignetteKeyword{excel} % \VignetteKeyword{xlsx} % \VignetteKeyword{spreadsheet} \usepackage{geometry} \geometry{ a4paper, total={210mm,297mm}, left=15mm, right=15mm, top=20mm, bottom=20mm, } \begin{document} \title{Introduction} \author{Alexander Walker\\ \texttt{Alexander.Walker1989@gmail.com}} \maketitle \newpage \section{Basic Examples} \subsection{write.xlsx} \begin{verbatim} 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 write.xlsx(iris, file = "writeXLSX1.xlsx") write.xlsx(iris, file = "writeXLSXTable1.xlsx", asTable = 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 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) ## Additional styling ## 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) \end{verbatim} \newpage \subsection{Basic Workbook} \begin{verbatim} require(ggplot2) ## set default border Colour and style wb <- createWorkbook() options("openxlsx.borderColour" = "#4F80BD") options("openxlsx.borderStyle" = "thin") modifyBaseFont(wb, fontSize = 10, fontName = "Arial Narrow") addWorksheet(wb, sheetName = "Motor Trend Car Road Tests", gridLines = FALSE) addWorksheet(wb, sheetName = "Iris", gridLines = FALSE) ## 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 iris data.frame as excel table 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) ## 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) ## 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) # 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)) saveWorkbook(wb, "basics.xlsx", overwrite = TRUE) ## save to working directory \end{verbatim} \newpage \subsection{Gallery} \begin{verbatim} ## 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) \end{verbatim} \newpage \section{Further Examples} \subsection{Stock Price} \begin{verbatim} require(ggplot2) wb <- createWorkbook() ## read historical prices from yahoo finance ticker <- "CBA.AX" csv.url <- paste("http://ichart.finance.yahoo.com/table.csv?s=", ticker, "&a=01&b=9&c=2009&d=01&e=9&f=2014&g=d&ignore=.csv") 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") \end{verbatim} \newpage \subsection{Image Compression using PCA} \begin{verbatim} 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) } } \end{verbatim} \end{document} openxlsx/NEWS0000644000176200001440000002351613560775241012652 0ustar liggesusersopenxlsx 4.1.3 ---------------------------------------------------------------- NEW FEATURES * 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 paramter added to write.xlsx. Passed to writeData/writeDataTable openxlsx 3.0.1 ---------------------------------------------------------------- NEW FEATURES * improved performance of read.xlsx and loadWorkbook * writeFormula funciton 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 whioch 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 focussed 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 charcters (&, <, >, ', ") 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/R/0000755000176200001440000000000013572443201012334 5ustar liggesusersopenxlsx/R/openxlsxCoerce.R0000644000176200001440000001222413560564727015477 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) } 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) 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.R0000644000176200001440000000530113560564727015427 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(1:length(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/helperFunctions.R0000644000176200001440000006255613572244514015653 0ustar liggesusers #' @name makeHyperlinkString #' @title create Excel hyperlink string #' @description Wrapper to create internal hyperlink string to pass to writeFormula() #' @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 \code{\link{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.")) #' #' \dontrun{ #' saveWorkbook(wb, "internalHyperlinks.xlsx",overwrite=TRUE) #' } #' #' makeHyperlinkString <- function(sheet, row = 1, col = 1, text = NULL, file = NULL){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), add = TRUE) 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 1:length(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) } 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) } ## color helper function: eg col2hex(colors()) 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 1:length(comment_list)){ authorInd <- which(authors == comment_list[[i]]$author) - 1L xml <- c(xml, sprintf('', comment_list[[i]]$ref, authorInd)) for(j in 1:length(comment_list[[i]]$comment)) xml <- c(xml, sprintf('%s%s', comment_list[[i]]$style[[j]], 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(1:length(vals), function(i){ names(vals[[i]]) <- nms[[i]]; vals[[i]]}) return(vals) } getAttrs <- function(xml, tag){ x <- lapply(xml, getChildlessNode, 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, " 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") } f <- lapply(1:length(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 <- dn[grepl(").*", 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 <- x[grepl(" 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 = " 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) } getDefinedNamesSheet <- 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(1:length(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 1:length(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 1:length(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) }openxlsx/R/StyleClass.R0000644000176200001440000001676413560564727014601 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.R0000644000176200001440000000011713560564727014253 0ustar liggesusers.onUnload <- function (libpath) { library.dynam.unload("openxlsx", libpath) }openxlsx/R/baseXML.R0000644000176200001440000005426313560564727014002 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 <- c(core, sprintf('%s', creator)) 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 = '', 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/writeData.R0000644000176200001440000004253113572244566014425 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 xy An alternative to specifying \code{startCol} and #' \code{startRow} individually. A vector of the form #' \code{c(startCol, startRow)}. #' @param colNames If \code{TRUE}, column names of x are written. #' @param rowNames If \code{TRUE}, data.frame row names of x are written. #' @param headerStyle Custom style to apply to column names. #' @param 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. #' @param borderColour Colour of cell border. A valid colour (belonging to \code{colours()} or a hex colour code, eg see \href{http://www.colorpicker.com}{here}). #' @param borderStyle Border line style #' \itemize{ #' \item{\bold{none}}{ no border} #' \item{\bold{thin}}{ thin border} #' \item{\bold{medium}}{ medium border} #' \item{\bold{dashed}}{ dashed border} #' \item{\bold{dotted}}{ dotted border} #' \item{\bold{thick}}{ thick border} #' \item{\bold{double}}{ double line border} #' \item{\bold{hair}}{ hairline border} #' \item{\bold{mediumDashed}}{ medium weight dashed border} #' \item{\bold{dashDot}}{ dash-dot border} #' \item{\bold{mediumDashDot}}{ medium weight dash-dot border} #' \item{\bold{dashDotDot}}{ dash-dot-dot border} #' \item{\bold{mediumDashDotDot}}{ medium weight dash-dot-dot border} #' \item{\bold{slantDashDot}}{ slanted dash-dot border} #' } #' @param withFilter If \code{TRUE}, add filters to the column name row. NOTE can only have one filter per worksheet. #' @param 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. #' @param na.string If not NULL, and if \code{keepNA} is \code{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 \code{\link{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, xy = NULL, colNames = TRUE, rowNames = FALSE, headerStyle = NULL, borders = c("none","surrounding","rows","columns", "all"), borderColour = getOption("openxlsx.borderColour", "black"), borderStyle = getOption("openxlsx.borderStyle", "thin"), withFilter = FALSE, keepNA = FALSE, na.string = NULL, name = NULL, sep = ", "){ ## increase scipen to avoid writing in scientific exSciPen <- getOption("scipen") od <- getOption("OutDec") exDigits <- getOption("digits") options("scipen" = 200) options("OutDec" = ".") options("digits" = 22) on.exit(options("scipen" = exSciPen), add = TRUE) on.exit(expr = options("OutDec" = od), add = TRUE) on.exit(options("digits" = exDigits), add = TRUE) 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) if(!"Workbook" %in% class(wb)) stop("First argument must be a Workbook.") if(!is.logical(colNames)) stop("colNames must be a logical.") if(!is.logical(rowNames)) stop("rowNames must be a logical.") if(!is.null(headerStyle) & !"Style" %in% class(headerStyle)) stop("headerStyle must be a style object or NULL.") if((!is.character(sep)) | (length(sep) != 1)) stop("sep must be a character vector of length 1") borders <- match.arg(borders) if(length(borders) != 1) stop("borders argument must be length 1.") ## borderColours validation borderColour <- validateColour(borderColour, "Invalid border colour") borderStyle <- validateBorderStyle(borderStyle)[[1]] ## special case - vector of hyperlinks hlinkNames <- NULL if("hyperlink" %in% class(x)){ hlinkNames <- names(x) colNames = FALSE } ## special case - formula if("formula" %in% class(x)){ x <- data.frame("X" = x, stringsAsFactors = FALSE) class(x[[1]]) <- "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-Z0-9_\\.]", name[1], ignore.case = TRUE)){ stop("Invalid characters in 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[sapply(colClasses, function(x) "formula" %in% x) & sapply(colClasses, function(x) "hyperlink" %in% x)] <- "formula" sheetX <- wb$validateSheet(sheet) if(wb$isChartSheet[[sheetX]]){ stop("Cannot write to chart sheet.") return(NULL) } ## 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("Style" %in% class(headerStyle) & 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 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 #' @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 xy An alternative to specifying \code{startCol} and #' \code{startRow} individually. A vector of the form #' \code{c(startCol, startRow)}. #' @seealso \code{\link{writeData}} #' @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)} #' #' #' ## Writing internal hyperlinks #' wb <- createWorkbook() #' addWorksheet(wb, "Sheet1") #' addWorksheet(wb, "Sheet2") #' writeFormula(wb, "Sheet1", x = '=HYPERLINK("#Sheet2!B3", "Text to Display - Link to Sheet2")') #' \dontrun{saveWorkbook(wb, "writeFormulaHyperlinkExample.xlsx", overwrite = TRUE)} #' writeFormula <- function(wb, sheet, x, startCol = 1, startRow = 1, xy = NULL){ if(!"character" %in% class(x)) stop("x must be a character vector.") dfx <- data.frame("X" = x, stringsAsFactors = FALSE) class(dfx$X) <- c("character", "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, xy = xy, colNames = FALSE, rowNames = FALSE) invisible(0) } openxlsx/R/class_definitions.R0000644000176200001440000001524013560564727016177 0ustar liggesusers Workbook <- setRefClass("Workbook", fields = c("sheet_names" = "character", "workbookProtection" = "ANY", "charts" = "ANY", "isChartSheet" = "logical", "colWidths" = "ANY", "connections" = "ANY", "Content_Types" = "character", "core" = "character", "drawings" = "ANY", "drawings_rels" = "ANY", "embeddings" = "ANY", "externalLinks" = "ANY", "externalLinksRels" = "ANY", "headFoot" = "ANY", "media" = "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", "workbook" = "ANY", "workbook.xml.rels" = "ANY", "worksheets" = "ANY", "worksheets_rels" = "ANY", "sheetOrder" = "integer") ) 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 <- 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 <- 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", "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 <- setRefClass("ChartSheet", fields = c("sheetPr" = "character", "sheetViews" = "character", "pageMargins" = "character", "drawing" = "character", "hyperlinks" = "ANY") ) openxlsx/R/readWorkbook.R0000644000176200001440000004536013564503456015132 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 \code{TRUE}, the first row of data will be used as column names. #' @param skipEmptyRows If \code{TRUE}, empty rows are skipped else empty rows after the first row containing data #' will return a row of NAs. #' @param rowNames If \code{TRUE}, first column of data will be used as row names. #' @param detectDates If \code{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 \code{TRUE}, empty columns are skipped. #' @seealso \code{\link{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 = 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) { UseMethod("read.xlsx", xlsxFile) } #' @export read.xlsx.default <- function(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) { ## Validate inputs and get files 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!") if (!is.logical(colNames)) stop("colNames must be TRUE/FALSE.") if (!is.logical(rowNames)) stop("rowNames must be TRUE/FALSE.") if (!is.logical(detectDates)) stop("detectDates must be TRUE/FALSE.") if (!is.logical(skipEmptyRows)) stop("skipEmptyRows must be TRUE/FALSE.") if (!is.logical(check.names)) stop("check.names must be TRUE/FALSE.") if (!is.character(sep.names) | nchar(sep.names) != 1) stop("sep.names must be a character and only one.") if (length(sheet) > 1) stop("sheet must be of length 1.") 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 xmlDir <- file.path(tempdir(), paste0(sample(LETTERS, 10), collapse = ""), "_excelXMLRead") xmlFiles <- unzip(xlsxFile, exdir = xmlDir) on.exit(unlink(xmlDir, recursive = TRUE), add = TRUE) sharedStringsFile <- xmlFiles[grepl("sharedStrings.xml$", xmlFiles, perl = TRUE)] workbook <- xmlFiles[grepl("workbook.xml$", xmlFiles, perl = TRUE)] workbookRelsXML <- xmlFiles[grepl("workbook.xml.rels$", xmlFiles, perl = TRUE)] ## get workbook names workbookRelsXML <- paste(readLines(workbookRelsXML, warn = FALSE, encoding = "UTF-8"), collapse = "") workbookRelsXML <- getChildlessNode(xml = workbookRelsXML, tag = ").*(?=)", 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(")[^\\<]+', dn, perl = TRUE)) sheet <- sheetNames[sapply(sheetNames, 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, perl = TRUE)))) 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 } ## get the file_name for each sheetrId file_name <- sapply(sheetrId, function(rId) { txt <- workbookRelsXML[grepl(sprintf('Id="%s"', rId), workbookRelsXML, fixed = TRUE)] regmatches(txt, regexpr('(?<=Target=").+xml(?=")', txt, perl = TRUE)) }) ## get the correct sheets if ("character" %in% class(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 - something has gone terribly wrong! Please report this bug on github (https://github.com/awalker89/openxlsx/issues) with an example xlsx file." ) ## get file worksheet <- xmlFiles[grepl(pattern = tolower(sheet), x = tolower(xmlFiles), fixed = TRUE)] if (length(worksheet) == 0) stop( "Length of worksheet is 0 - something has gone terribly wrong! Please report this bug on github (https://github.com/awalker89/openxlsx/issues) with an example xlsx file." ) ## 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 ("character" %in% class(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( pattern = "[A-Z]", replacement = "", x = 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.", 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 <- which(grepl(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 <- xmlFiles[grepl("styles.xml", xmlFiles)] styles <- readLines(stylesXML, warn = FALSE) styles <- removeHeadTag(styles) ## Number formats numFmts <- getChildlessNode(xml = styles, tag = " 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 = " 31)) stop("sheetName too long! Max length is 31 characters.") sheetName <- as.character(params$sheetName) if("list" %in% class(x) & length(sheetName) == length(x)) names(x) <- sheetName } tabColour <- NULL if("tabColour" %in% names(params)) tabColour <- validateColour(params$tabColour, "Invalid tabColour!") zoom <- 100 if("zoom" %in% names(params)){ if(is.numeric(params$zoom)){ zoom <- params$zoom }else{ stop("zoom must be numeric") } } ## AddWorksheet gridLines <- TRUE if("gridLines" %in% names(params)){ if(all(is.logical(params$gridLines))){ gridLines <- params$gridLines }else{ stop("Argument gridLines must be TRUE or FALSE") } } overwrite <- TRUE if("overwrite" %in% names(params)){ if(is.logical(params$overwrite)){ overwrite <- params$overwrite }else{ stop("Argument overwrite must be TRUE or FALSE") } } withFilter <- TRUE if("withFilter" %in% names(params)){ if(is.logical(params$withFilter)){ withFilter <- params$withFilter }else{ stop("Argument withFilter must be TRUE or FALSE") } } startRow <- 1 if("startRow" %in% names(params)){ if(all(startRow > 0)){ startRow <- params$startRow }else{ stop("startRow must be a positive integer") } } startCol <- 1 if("startCol" %in% names(params)){ if(all(startCol > 0)){ startCol <- params$startCol }else{ stop("startCol must be a positive integer") } } colNames <- TRUE if("colNames" %in% names(params)){ if(is.logical(params$colNames)){ colNames <- params$colNames }else{ stop("Argument colNames must be TRUE or FALSE") } } ## to be consistent with write.csv if("col.names" %in% names(params)){ if(is.logical(params$col.names)){ colNames <- params$col.names }else{ stop("Argument col.names must be TRUE or FALSE") } } rowNames <- FALSE if("rowNames" %in% names(params)){ if(is.logical(params$rowNames)){ rowNames <- params$rowNames }else{ stop("Argument colNames must be TRUE or FALSE") } } ## to be consistent with write.csv if("row.names" %in% names(params)){ if(is.logical(params$row.names)){ rowNames <- params$row.names }else{ stop("Argument row.names must be TRUE or FALSE") } } xy <- NULL if("xy" %in% names(params)){ if(length(params$xy) != 2) stop("xy parameter must have length 2") xy <- params$xy } headerStyle <- NULL if("headerStyle" %in% names(params)){ if(length(params$headerStyle) == 1){ if("Style" %in% class(params$headerStyle)){ headerStyle <- params$headerStyle }else{ stop("headerStyle must be a style object.") } }else{ if(all(sapply(params$headerStyle, function(x) "Style" %in% class(x)))){ headerStyle <- params$headerStyle }else{ stop("headerStyle must be a style object.") } } } borders <- NULL if("borders" %in% names(params)){ borders <- tolower(params$borders) if(!all(borders %in% c("surrounding", "rows", "columns", "all"))) stop("Invalid borders argument") } borderColour <- getOption("openxlsx.borderColour", "black") if("borderColour" %in% names(params)) borderColour <- params$borderColour borderStyle <- getOption("openxlsx.borderStyle", "thin") if("borderStyle" %in% names(params)){ borderStyle <- validateBorderStyle(params$borderStyle) } keepNA <- FALSE if("keepNA" %in% names(params)){ if(!"logical" %in% class(keepNA)){ stop("keepNA must be a logical.") }else{ keepNA <- params$keepNA } } na.string <- NULL if("na.string" %in% names(params)){ na.string <- as.character(params$na.string) } tableStyle <- "TableStyleLight9" if("tableStyle" %in% names(params)) tableStyle <- params$tableStyle ## auto column widths colWidths <- "" if("colWidths" %in% names(params)) colWidths <- params$colWidths ## create new Workbook object wb <- createWorkbook(creator = creator, title = title, subject = subject, category = category) ## If a list is supplied write to individual worksheets using names if available nSheets <- 1 if("list" %in% class(x)){ nms <- names(x) nSheets <- length(x) if(is.null(nms)){ nms <- paste("Sheet", 1:nSheets) }else if(any("" %in% nms)){ nms[nms %in% ""] <- paste("Sheet", (1:nSheets)[nms %in% ""]) }else{ nms <- make.unique(nms) } if(any(nchar(nms) > 31)){ warning("Truncating list names to 31 characters.") nms <- substr(nms, 1, 31) } ## make all inputs as long as the list if(!is.null(tabColour)){ if(length(tabColour) != nSheets) tabColour <- rep_len(tabColour, length.out = nSheets) } if(length(zoom) != nSheets) zoom <- rep_len(zoom, length.out = nSheets) if(length(gridLines) != nSheets) gridLines <- rep_len(gridLines, length.out = nSheets) if(length(withFilter) != nSheets) withFilter <- rep_len(withFilter, length.out = nSheets) if(length(colNames) != nSheets) colNames <- rep_len(colNames, length.out = nSheets) if(length(rowNames) != nSheets) rowNames <- rep_len(rowNames, length.out = nSheets) if(length(startRow) != nSheets) startRow <- rep_len(startRow, length.out = nSheets) if(length(startCol) != nSheets) startCol <- rep_len(startCol, length.out = nSheets) if(!is.null(headerStyle)) headerStyle <- lapply(1:nSheets, function(x) return(headerStyle)) if(length(borders) != nSheets & !is.null(borders)) borders <- rep_len(borders, length.out = nSheets) if(length(borderColour) != nSheets) borderColour <- rep_len(borderColour, length.out = nSheets) if(length(borderStyle) != nSheets) borderStyle <- rep_len(borderStyle, length.out = nSheets) if(length(keepNA) != nSheets) keepNA <- rep_len(keepNA, length.out = nSheets) if(length(na.string) != nSheets & !is.null(na.string)) na.string <- rep_len(na.string, length.out = nSheets) if(length(asTable) != nSheets) asTable <- rep_len(asTable, length.out = nSheets) if(length(tableStyle) != nSheets) tableStyle <- rep_len(tableStyle, length.out = nSheets) if(length(colWidths) != nSheets) colWidths <- rep_len(colWidths, length.out = nSheets) for(i in 1:nSheets){ wb$addWorksheet(nms[[i]], showGridLines = gridLines[i], tabColour = tabColour[i], zoom = zoom[i]) if(asTable[i]){ writeDataTable(wb = wb, sheet = i, x = x[[i]], startCol = startCol[[i]], startRow = startRow[[i]], xy = xy, colNames = colNames[[i]], rowNames = rowNames[[i]], tableStyle = tableStyle[[i]], tableName = NULL, headerStyle = headerStyle[[i]], withFilter = withFilter[[i]], keepNA = keepNA[[i]], na.string = na.string[[i]]) }else{ writeData(wb = wb, sheet = i, x = x[[i]], startCol = startCol[[i]], startRow = startRow[[i]], xy = xy, colNames = colNames[[i]], rowNames = rowNames[[i]], headerStyle = headerStyle[[i]], borders = borders[[i]], borderColour = borderColour[[i]], borderStyle = borderStyle[[i]], keepNA = keepNA[[i]], na.string = na.string[[i]]) } if(colWidths[i] %in% "auto") setColWidths(wb, sheet = i, cols = 1:ncol(x[[i]]) + startCol[[i]] - 1L, widths = "auto") } }else{ wb$addWorksheet(sheetName, showGridLines = gridLines, tabColour = tabColour, zoom = zoom) if(asTable){ if(!"data.frame" %in% class(x)) stop("x must be a data.frame is asTable == TRUE") writeDataTable(wb = wb, sheet = 1, x = x, startCol = startCol, startRow = startRow, xy = xy, colNames = colNames, rowNames = rowNames, tableStyle = tableStyle, tableName = NULL, headerStyle = headerStyle, keepNA = keepNA, na.string = na.string) }else{ writeData(wb = wb, sheet = 1, x = x, startCol = startCol, startRow = startRow, xy = xy, colNames = colNames, rowNames = rowNames, headerStyle = headerStyle, borders = borders, borderColour = borderColour, borderStyle = borderStyle, keepNA = keepNA, na.string = na.string) } if(colWidths[1] %in% "auto") setColWidths(wb, sheet = 1, cols = 1:ncol(x) + startCol - 1L, widths = "auto") } ###--Freeze Panes---### ## firstActiveRow = NULL ## firstActiveCol = NULL ## firstRow = FALSE ## firstCol = FALSE freezePanes <- FALSE firstActiveRow <- rep_len(1L, length.out = nSheets) if("firstActiveRow" %in% names(params)){ firstActiveRow <- params$firstActiveRow freezePanes <- TRUE if(length(firstActiveRow) != nSheets) firstActiveRow <- rep_len(firstActiveRow, length.out = nSheets) } firstActiveCol <- rep_len(1L, length.out = nSheets) if("firstActiveCol" %in% names(params)){ firstActiveCol <- params$firstActiveCol freezePanes <- TRUE if(length(firstActiveCol) != nSheets) firstActiveCol <- rep_len(firstActiveCol, length.out = nSheets) } firstRow <- rep_len(FALSE, length.out = nSheets) if("firstRow" %in% names(params)){ firstRow <- params$firstRow freezePanes <- TRUE if("list" %in% class(x) & length(firstRow) != nSheets) firstRow <- rep_len(firstRow, length.out = nSheets) } firstCol <- rep_len(FALSE, length.out = nSheets) if("firstCol" %in% names(params)){ firstCol <- params$firstCol freezePanes <- TRUE if("list" %in% class(x) & length(firstCol) != nSheets) firstCol <- rep_len(firstCol, length.out = nSheets) } if(freezePanes){ for(i in 1:nSheets) freezePane(wb = wb, sheet = i, firstActiveRow = firstActiveRow[i], firstActiveCol = firstActiveCol[i], firstRow = firstRow[i], firstCol = firstCol[i]) } saveWorkbook(wb = wb, file = file, overwrite = overwrite) invisible(wb) } openxlsx/R/workbook_read_workbook.R0000644000176200001440000002362213560564727017247 0ustar liggesusers #' @export read.xlsx.Workbook <- function(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){ ## Validate inputs and get files if(!is.logical(colNames)) stop("colNames must be TRUE/FALSE.") if(!is.logical(rowNames)) stop("rowNames must be TRUE/FALSE.") if(!is.logical(detectDates)) stop("detectDates must be TRUE/FALSE.") if(!is.logical(skipEmptyRows)) stop("skipEmptyRows must be TRUE/FALSE.") if(!is.logical(check.names)) stop("check.names must be TRUE/FALSE.") if(!is.character(sep.names) | nchar(sep.names)!=1) stop("sep.names must be a character and only one.") if(length(sheet) != 1) stop("sheet must be of length 1.") ## Named region logic reading_named_region <- FALSE if(!is.null(namedRegion)){ dn <- xlsxFile$workbook$definedNames if(length(dn) == 0){ warning("Workbook has no named regions.") return(NULL) } dn_names <- replaceXMLEntities(regmatches(dn, regexpr('(?<=name=")[^"]+', dn, perl = TRUE))) ind <- tolower(dn_names) == tolower(namedRegion) if(!any(ind)) stop(sprintf("Region '%s' not found!", namedRegion)) ## pull out first node value dn <- dn[ind] region <- regmatches(dn, regexpr('(?<=>)[^\\<]+', 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 == 3) ## "str" 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 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.R0000644000176200001440000001770313560564727015075 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 1:length(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 \code{\link{createStyle}}. #' @param visible TRUE or FALSE. Is comment visible. #' @param width Textbox integer width in number of cells #' @param height Textbox integer height in number of cells #' @export #' @seealso \code{\link{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(!"character" %in% class(author)) stop("author argument must be a character vector") if(!"character" %in% class(comment)) stop("comment argument must be a character vector") if(!"numeric" %in% class(width)) stop("width argument must be a numeric vector") if(!"numeric" %in% class(height)) stop("height argument must be a numeric vector") if(!"logical" %in% class(visible)) stop("visible argument must be a logical vector") width <- round(width) height <- round(height) n <- length(comment) author <- author[1] visible <- visible[1] 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 \code{\link{createComment}}. #' @param xy An alternative to specifying \code{col} and #' \code{row} individually. A vector of the form #' \code{c(col, row)}. #' @export #' @seealso \code{\link{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 \code{TRUE}, all data in rectangle min(rows):max(rows) X min(cols):max(cols) #' will be removed. #' @export #' @seealso \code{\link{createComment}} #' @seealso \code{\link{writeComment}} removeComment <- function(wb, sheet, cols, rows, gridExpand = TRUE){ sheet <- wb$validateSheet(sheet) if(!"Workbook" %in% class(wb)) stop("First argument must be a 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.R0000644000176200001440000001036113572252217014756 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`) } 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) } 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, R_fileName) { .Call(`_openxlsx_write_worksheet_xml_2`, prior, post, sheet_data, row_heights, R_fileName) } openxlsx/R/writeDataTable.R0000644000176200001440000002564713565534073015403 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 \code{TRUE}, column names of x are written. #' @param rowNames If \code{TRUE}, row names of x are written. #' @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 \code{TRUE}, columns with have filters in the first row. #' @param 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. #' @param na.string If not NULL, and if \code{keepNA} is \code{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 \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\bold{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 \code{\link{addWorksheet}} #' @seealso \code{\link{writeData}} #' @seealso \code{\link{removeTable}} #' @seealso \code{\link{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 = "TableStyleLight9", tableName = NULL, headerStyle= NULL, withFilter = TRUE, keepNA = FALSE, na.string = NULL, sep = ", ", stack = FALSE, firstColumn = FALSE, lastColumn = FALSE, bandedRows = TRUE, bandedCols = FALSE){ if(!is.null(xy)){ if(length(xy) != 2) stop("xy parameter must have length 2") startCol = xy[[1]] startRow = xy[[2]] } ## Input validating if(!"Workbook" %in% class(wb)) stop("First argument must be a Workbook.") if(!"data.frame" %in% class(x)) stop("x must be a data.frame.") if(!is.logical(colNames)) stop("colNames must be a logical.") if(!is.logical(rowNames)) stop("rowNames must be a logical.") if(!is.null(headerStyle) & !"Style" %in% class(headerStyle)) stop("headerStyle must be a style object or NULL.") if(!is.logical(withFilter)) stop("withFilter must be a logical.") if((!is.character(sep)) | (length(sep) != 1)) stop("sep must be a character vector of length 1") if(!is.logical(firstColumn)) stop("firstColumn must be a logical.") if(!is.logical(lastColumn)) stop("lastColumn must be a logical.") if(!is.logical(bandedRows)) stop("bandedRows must be a logical.") if(!is.logical(bandedCols)) stop("bandedCols must be a logical.") if(is.null(tableName)){ tableName <- paste0("Table", as.character(length(wb$tables) + 3L)) }else{ tableName <- wb$validate_table_name(tableName) } ## increase scipen to avoid writing in scientific exSciPen <- getOption("scipen") od <- getOption("OutDec") exDigits <- getOption("digits") options("scipen" = 200) options("OutDec" = ".") options("digits" = 22) on.exit(options("scipen" = exSciPen), add = TRUE) on.exit(expr = options("OutDec" = od), add = TRUE) on.exit(options("digits" = exDigits), add = TRUE) ## 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 validNames <- c("none", paste0("TableStyleLight", 1:21), paste0("TableStyleMedium", 1:28), paste0("TableStyleDark", 1:11)) if(!tolower(tableStyle) %in% tolower(validNames)){ stop("Invalid table style.") }else{ tableStyle <- validNames[grepl(paste0("^", tableStyle, "$"), validNames, ignore.case = TRUE)] } tableStyle <- na.omit(tableStyle) if(length(tableStyle) == 0) stop("Unknown table style.") ## header style if("Style" %in% class(headerStyle)) addStyle(wb = wb, sheet = sheet, style=headerStyle, rows = startRow, cols = 0:(ncol(x) - 1L) + startCol, gridExpand = TRUE) showColNames <- colNames if(colNames){ colNames <- colnames(x) if(any(duplicated(tolower(colNames)))) stop("Column names of x must be case-insensitive unique.") ## 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", 1:ncol(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 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.R0000644000176200001440000114433313567520066015264 0ustar liggesusers #' @include class_definitions.R #' @import stringi Workbook$methods( initialize = function(creator = "", title = NULL, subject = NULL, category = NULL) { charts <<- list() isChartSheet <<- logical(0) colWidths <<- list() connections <<- NULL Content_Types <<- genBaseContent_Type() core <<- genBaseCore( creator = creator, title = title, subject = subject, category = category ) comments <<- list() drawings <<- list() drawings_rels <<- list() embeddings <<- NULL externalLinks <<- NULL externalLinksRels <<- NULL headFoot <<- NULL media <<- list() pivotTables <<- NULL pivotTables.xml.rels <<- NULL pivotDefinitions <<- NULL pivotRecords <<- NULL pivotDefinitionsRels <<- NULL queryTables <<- NULL rowHeights <<- list() 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() workbookProtection <<- NULL worksheets <<- list() worksheets_rels <<- list() } ) Workbook$methods( addWorksheet = function(sheetName , showGridLines = TRUE , tabColour = NULL , zoom = 100 , oddHeader = NULL , oddFooter = NULL , evenHeader = NULL , evenFooter = NULL , firstHeader = NULL , firstFooter = NULL , visible = TRUE , paperSize = 9 , orientation = 'portrait' , hdpi = 300 , 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 } ## fix visible value visible <- tolower(visible) if (visible == "true") visible <- "visible" if (visible == "false") visible <- "hidden" 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() rowHeights[[newSheetIndex]] <<- list() colWidths[[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 <- readLines(fl, warn = FALSE, encoding = "UTF-8") 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]] rowHeights[[newSheetIndex]] <<- rowHeights[[clonedSheet]] colWidths[[newSheetIndex]] <<- colWidths[[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() 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) 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) } 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 1:length(pivotTables)) file.copy( from = pivotTables[i], to = file.path(pivotTablesDir, sprintf("pivotTable%s.xml", i)), overwrite = TRUE, copy.date = TRUE ) for (i in 1:length(pivotDefinitions)) file.copy( from = pivotDefinitions[i], to = file.path(pivotCacheDir, sprintf("pivotCacheDefinition%s.xml", i)), overwrite = TRUE, copy.date = TRUE ) for (i in 1:length(pivotRecords)) file.copy( from = pivotRecords[i], to = file.path(pivotCacheDir, sprintf("pivotCacheRecords%s.xml", i)), overwrite = TRUE, copy.date = TRUE ) for (i in 1:length(pivotDefinitionsRels)) file.copy( from = pivotDefinitionsRels[i], to = file.path( pivotCacheRelsDir, sprintf("pivotCacheDefinition%s.xml.rels", i) ), overwrite = TRUE, copy.date = TRUE ) for (i in 1:length(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 1:length(slicers)) { if (nchar(slicers[i]) > 0) file.copy(from = slicers[i], to = file.path(slicersDir, sprintf("slicer%s.xml", i))) } for (i in 1:length(slicerCaches)) write_file(body = slicerCaches[[i]], fl = file.path(slicerCachesDir, sprintf("slicerCache%s.xml", i))) } ## Write content ## write .rels write_file( head = '', body = ' ', tail = '', fl = file.path(relsDir, ".rels") ) ## write app.xml write_file( head = '', body = 'Microsoft Excel', 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 1:length(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 1:length(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 1:length(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 1:length(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), "") if (length(workbookProtection) > 0) { # Worksheet protection needs to be right after fileVersion, fileSharing and workbookPr, otherwise Excel will complain workbookXML <- append(workbookXML, list(workbookProtection = workbookProtection), after = max(which( names(workbookXML) %in% c("fileVersion", "fileSharing", "workbookPr") ))) } 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)) sheetName <- replaceIllegalCharacters(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(sprintf("This Workbook only has %s sheets.", length(sheet_names)), call. = FALSE) return(sheetName) } else if (!sheetName %in% sheet_names) { stop(sprintf("Sheet '%s' does not exist.", sheetName), call. = FALSE) } return(which(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 1:length(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, "" ## 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(1:length(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 ("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 1:nSheets) { ## 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") )) 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) { write_worksheet_xml( prior = prior, post = post, sheet_data = ws$sheet_data, 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]]), 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(1:length(worksheets[[i]]$hyperlinks), "h") ws_rels <- c(ws_rels, unlist( lapply(1:length(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 <- which(grepl("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( 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 comments[[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 <- worksheets_rels[[sheet]][grepl("pivotTables", worksheets_rels[[sheet]])] 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 <- which(grepl(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(1:length(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]]$extLst <<- c(worksheets[[sheet]]$extLst, 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 length(worksheets[[sheet]]$conditionalFormatting):1) { 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 == "between") { cfRule <- sprintf( '%s%s', dxfId, formula[1], formula[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 -> tables -> calcChain # Assign workbook.xml.rels children rIds, 1:length(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 <- which(grepl( "(worksheets|chartsheets)/sheet[0-9]+\\.xml", workbook.xml.rels )) stylesInd <- which(grepl("styles\\.xml", workbook.xml.rels)) themeInd <- which(grepl("theme/theme[0-9]+.xml", workbook.xml.rels)) connectionsInd <- which(grepl("connections.xml", workbook.xml.rels)) extRefInds <- which(grepl("externalLinks/externalLink[0-9]+.xml", workbook.xml.rels)) sharedStringsInd <- which(grepl("sharedStrings.xml", workbook.xml.rels)) tableInds <- which(grepl("table[0-9]+.xml", workbook.xml.rels)) ## Reordering of workbook.xml.rels ## don't want to re-assign rIds for pivot tables or slicer caches pivotNode <- workbook.xml.rels[grepl("pivotCache/pivotCacheDefinition[0-9].xml", workbook.xml.rels)] slicerNode <- workbook.xml.rels[which(grepl("slicerCache[0-9]+.xml", workbook.xml.rels))] ## Reorder children of workbook.xml.rels workbook.xml.rels <<- workbook.xml.rels[c( sheetInds, extRefInds, themeInd, connectionsInd, stylesInd, sharedStringsInd, tableInds )] ## Re assign rIds to children of workbook.xml.rels workbook.xml.rels <<- unlist(lapply(1:length(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(1:length(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 != 1: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]] workbook$bookViews <<- sprintf( '', visible_sheet_index - 1L, visible_sheet_index - 1L ) 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="0"', 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 1:length(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(1:length(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 1:length(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 XML if (length(colWidths[[i]]) > 0) invisible(.self$setColWidths(i)) } } ) 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 <- 1:length(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 1:length(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) exSheets <- replaceXMLEntities(exSheets) showText <- "A Workbook object.\n" ## 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(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", stri_join(sheetOrder, sep = " ", collapse = ", ") )) cat(unlist(showText)) } ) # ## function to create the below # strs <- "data.frame(" # for(i in 1:nrow(tab)) # strs <- append(strs, stri_join('"', gsub(" ", ".", tolower(tab$Font[[i]])), '" = ', stri_join(capture.output(dput(unname(unlist(tab[1,2:ncol(tab)])))), collapse = ""), ", \n")) # strs[length(strs)] <- gsub(", \\\n$", ")", strs[length(strs)]) # cat(strs) ## Character width lookup table openxlsxFontSizeLookupTable <- data.frame( "agency.fb" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.25, 2.42875, 2.7325, 2.7325, 2.875, 2.875, 3.07125, 3.07125 ), "aharoni" = c( 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 2.875 ), "algerian" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "andalus" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "angsana.new" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25 ), "angsanaupc" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25 ), "aparajita" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "arial.black" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625 ), "arial.narrow" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375, 3.21375 ), "arial.rounded.mt.bold" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "arial.unicode.ms" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "arial" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "calibri.light" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "calibri" = c( 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "californian.fb" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "calisto.mt" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "cambria.math" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "cambria" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "candara" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "castellar" = c( 1.17875, 1.17875, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125, 4.6075, 4.7675, 4.7675, 5.08875, 5.25, 5.25 ), "centaur" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "century.gothic" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "century.schoolbook" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "century" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "chiller" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "colonna.mt" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "comic.sans.ms" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "consolas" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "constantia" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "cooper.black" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "copperplate.gothic.bold" = c( 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.32125, 4.32125, 4.6075, 4.6075, 4.7675, 5.08875, 5.08875, 5.25 ), "copperplate.gothic.light" = c( 1.17875, 1.17875, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.7675, 5.08875, 5.25, 5.25 ), "corbel" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "cordia.new" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "david" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375 ), "dfkai-sb" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "dilleniaupc" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075 ), "dokchampa" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "dotum" = c( 0.71375, 0.71375, 1.17875, 1, 1, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125 ), "dotumche" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ebrima" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "edwardian.script.itc" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.21375, 3.3575, 3.3575 ), "elephant" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.5, 3.5, 3.69625, 3.9825, 3.9825, 4.1425, 4.46375, 4.46375, 4.6075, 4.94625, 4.94625, 5.08875, 5.3925, 5.3925, 5.57125 ), "engravers.mt" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675 ), "eras.bold.itc" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125, 4.6075, 4.6075, 4.7675 ), "eras.demi.itc" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "eras.light.itc" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "eras.medium.itc" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425 ), "estrangelo.edessa" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "eucrosiaupc" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "euphemia" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125 ), "fangsong" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "felix.titling" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125 ), "fixedsys" = c( 1, 1, 1, 1, 1, 1, 1, 1, 2.25, 2.25, 2.25, 2.25, 2.25, 2.25, 2.25, 2.25, 3.5, 3.5, 3.5, 3.5, 3.5, 3.5, 3.5, 3.5, 3.5, 4.7675, 4.7675, 4.7675, 4.7675 ), "footlight.mt.light" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "forte" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "franklin.gothic.book" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "franklin.gothic.demi.cond" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "franklin.gothic.demi" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "franklin.gothic.heavy" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "franklin.gothic.medium.cond" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625 ), "franklin.gothic.medium" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "frankruehl" = c( 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 2.875 ), "freesiaupc" = c( 0.57125, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375 ), "freestyle.script" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875 ), "french.script.mt" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "gabriola" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "gadugi" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "gautami" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "georgia" = c( 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125, 4.46375 ), "gigi" = c( 0.8575, 0.8575, 0.8575, 0.8575, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.625, 1.625, 1.96375, 1.96375, 1.96375, 2.42875, 2.42875, 2.42875, 2.7325, 2.7325, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.5, 3.83875 ), "gill.sans.mt.condensed" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "gill.sans.mt.ext.condensed.bold" = c( 0.3925, 0.3925, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875 ), "gill.sans.mt" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "gill.sans.ultra.bold.condensed" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.1425 ), "gill.sans.ultra.bold" = c( 1.32125, 1.4825, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.875, 3.07125, 3.07125, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.57125, 5.71375, 5.71375, 6.0175, 6.19625, 6.3575 ), "gisha" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "gloucester.mt.extra.condensed" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325 ), "goudy.old.style" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "goudy.stout" = c( 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 2.25, 2.25, 2.42875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.83875, 3.83875, 3.9825, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925, 5.57125, 5.875, 6.0175, 6.0175 ), "gulim" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.80375, 1.80375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "gulimche" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "gungsuh" = c( 0.71375, 0.71375, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125 ), "gungsuhche" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "haettenschweiler" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375 ), "harlow.solid.italic" = c( 0.785, 0.785, 0.92875, 0.92875, 1.1075, 1.285, 1.285, 1.42875, 1.625, 1.625, 1.7675, 1.94625, 1.94625, 2.1425, 2.1425, 2.285, 2.285, 2.46375, 2.66, 2.66, 2.80375, 2.9825, 2.9825, 3.125, 3.32125, 3.32125, 3.5, 3.5, 3.6425 ), "harrington" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "high.tower.text" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5 ), "impact" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825 ), "imprint.mt.shadow" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "informal.roman" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.69625, 3.83875 ), "irisupc" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325 ), "iskoola.pota" = c( 0.71375, 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "jasmineupc" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375, 3.21375 ), "jokerman" = c( 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.32125, 4.32125, 4.6075, 4.6075, 4.7675, 5.08875, 5.08875, 5.25 ), "juice.itc" = c( 0.3925, 0.57125, 0.71375, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.58875 ), "kaiti" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "kalinga" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825 ), "kartika" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "khmer.ui" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "kodchiangupc" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.42875 ), "kristen.itc" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "kunstler.script" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.42875 ), "lao.ui" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "latha" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "leelawadee" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "levenim.mt" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "lilyupc" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375 ), "lucida.bright" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "lucida.calligraphy" = c( 1.07125, 1.25, 1.42875, 1.58875, 1.58875, 1.91, 1.94625, 2.1075, 2.25, 2.3925, 2.58875, 2.7675, 2.91, 2.91, 3.285, 3.285, 3.42875, 3.57125, 3.7675, 3.94625, 4.08875, 4.2325, 4.285, 4.6075, 4.6075, 4.75, 4.94625, 5.08875, 5.2675 ), "lucida.console" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "lucida.fax" = c( 1, 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075 ), "lucida.handwriting" = c( 1.07125, 1.25, 1.42875, 1.58875, 1.58875, 1.91, 2.1075, 2.1075, 2.3925, 2.3925, 2.625, 2.7675, 2.91, 3.05375, 3.285, 3.42875, 3.42875, 3.7325, 3.7675, 3.94625, 4.08875, 4.2325, 4.42875, 4.6075, 4.75, 4.75, 5.08875, 5.08875, 5.2675 ), "lucida.sans.typewriter" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "lucida.sans.unicode" = c( 1, 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075 ), "lucida.sans" = c( 1, 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075 ), "magneto" = c( 1.17875, 1.32125, 1.625, 1.625, 1.80375, 2.1075, 2.1075, 2.25, 2.58875, 2.7325, 2.7325, 3.07125, 3.21375, 3.21375, 3.5, 3.69625, 3.69625, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925, 5.57125, 5.71375 ), "maiandra.gd" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "malgun.gothic" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "mangal" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "marlett" = c( 1.625, 1.80375, 2.1075, 2.25, 2.42875, 2.7325, 2.875, 3.07125, 3.3575, 3.5, 3.69625, 3.9825, 4.1425, 4.32125, 4.6075, 4.7675, 4.94625, 5.25, 5.3925, 5.57125, 5.875, 6.0175, 6.19625, 6.5, 6.6425, 6.82125, 7.125, 7.2675, 7.46375 ), "matura.mt.script.capitals" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.7675 ), "meiryo.ui" = c( 0.8575, 1, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "meiryo" = c( 0.8575, 1, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "microsoft.himalaya" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25 ), "microsoft.jhenghei.ui" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.1425 ), "microsoft.jhenghei" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.1425 ), "microsoft.new.tai.lue" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "microsoft.phagspa" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "microsoft.sans.serif" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "microsoft.tai.le" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "microsoft.uighur" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "microsoft.yahei.ui" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "microsoft.yahei" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "microsoft.yi.baiti" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "miriam.fixed" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "miriam" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "mistral" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375 ), "modern.no..20" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 2.875, 3.07125, 3.21375, 3.21375 ), "modern" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "mongolian.baiti" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "monotype.corsiva" = c( 0.6425, 0.785, 0.92875, 0.92875, 0.96375, 1.1075, 1.285, 1.285, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.9825, 2.1425, 2.17875, 2.32125, 2.32125, 2.46375, 2.46375, 2.69625, 2.83875, 2.83875, 2.9825, 3.0175, 3.17875, 3.3575, 3.3575, 3.535 ), "moolboran" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875 ), "ms.gothic" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ms.mincho" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ms.outlook" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ms.pgothic" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ms.pmincho" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ms.reference.sans.serif" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.6075 ), "ms.reference.specialty" = c( 1.96375, 2.25, 2.58875, 2.7325, 2.875, 3.3575, 3.5, 3.69625, 4.1425, 4.32125, 4.46375, 4.7675, 5.08875, 5.25, 5.57125, 5.71375, 6.0175, 6.3575, 6.5, 6.6425, 7.125, 7.2675, 7.46375, 7.8925, 8.08875, 8.2325, 8.535, 8.8575, 9 ), "ms.sans.serif" = c( 0.71375, 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 1.96375, 2.58875, 2.58875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.07125, 3.07125, 3.07125, 3.5, 3.5, 3.5, 3.83875, 3.83875 ), "ms.serif" = c( 0.57125, 0.71375, 0.71375, 1, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.625, 1.625, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.25, 2.58875, 2.58875, 2.58875, 3.69625, 3.69625, 3.69625, 2.875, 3.69625, 3.69625, 3.5, 3.5 ), "ms.ui.gothic" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "mt.extra" = c( 1.625, 1.80375, 2.1075, 2.25, 2.42875, 2.7325, 2.875, 3.07125, 3.3575, 3.5, 3.69625, 4.1425, 4.32125, 4.46375, 4.7675, 4.94625, 5.08875, 5.3925, 5.57125, 5.71375, 6.0175, 6.19625, 6.3575, 6.6425, 6.82125, 6.9825, 7.2675, 7.46375, 7.6075 ), "mv.boli" = c( 1, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.08875 ), "narkisim" = c( 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 2.875 ), "niagara.engraved" = c( 0.3925, 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375 ), "niagara.solid" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375 ), "nirmala.ui" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "nsimsun" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "nyala" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "ocr.a.extended" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "old.english.text.mt" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "onyx" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375, 1.96375 ), "palace.script.mt" = c( 0.42875, 0.42875, 0.6425, 0.6425, 0.6425, 0.785, 0.785, 0.92875, 0.96375, 1.1075, 1.1075, 1.1075, 1.285, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.66, 1.80375, 1.80375, 1.9825, 1.9825, 1.9825, 2.17875, 2.17875, 2.32125, 2.32125, 2.32125 ), "palatino.linotype" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "papyrus" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "parchment" = c( 0.25, 0.25, 0.25, 0.25, 0.3925, 0.3925, 0.3925, 0.3925, 0.57125, 0.57125, 0.57125, 0.71375, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 0.8575, 1, 1, 1, 1, 1.17875, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125 ), "perpetua.titling.mt" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625 ), "perpetua" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.21375 ), "plantagenet.cherokee" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "playbill" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.42875 ), "pmingliu-extb" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "pmingliu" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "poor.richard" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "pristina" = c( 0.57125, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5 ), "raavi" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.7675 ), "rage.italic" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "rockwell.condensed" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "rockwell.extra.bold" = c( 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875 ), "rockwell" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825 ), "rod" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "roman" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "sakkal.majalla" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "script.mt.bold" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "script" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "segoe.print" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 2.1075, 2.1075, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.3575, 3.3575, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925 ), "segoe.script" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 2.1075, 2.1075, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.3575, 3.3575, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925 ), "segoe.ui.light" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "segoe.ui.semibold" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "segoe.ui.semilight" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "segoe.ui.symbol" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "segoe.ui" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "shonar.bangla" = c( 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.42875, 2.7325, 2.7325, 2.7325, 2.875, 2.875, 3.07125 ), "showcard.gothic" = c( 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.32125 ), "shruti" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "simhei" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "simplified.arabic.fixed" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "simplified.arabic" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "simsun-extb" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "simsun" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "small.fonts" = c( 0.71375, 0.71375, 1, 1.32125, 1.32125, 1.32125, 1.625, 1.625, 1.625, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.58875, 2.875, 2.58875, 2.58875, 2.875, 2.875, 2.875, 3.5, 3.5, 3.5, 3.5, 3.69625, 3.69625, 3.69625, 3.69625 ), "snap.itc" = c( 1.32125, 1.4825, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 5.08875, 5.25, 5.3925, 5.57125, 5.71375, 5.875, 6.19625, 6.3575, 6.3575 ), "sylfaen" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "symbol" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "tahoma" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "tempus.sans.itc" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625 ), "terminal" = c( 1, 1, 1, 1.625, 1.625, 1.32125, 1.32125, 1.32125, 1.32125, 2.25, 2.25, 2.25, 2.25, 2.25, 3.5, 3.5, 3.5, 3.5, 2.875, 2.875, 2.875, 4.46375, 4.46375, 4.46375, 4.46375, 4.46375, 5.3925, 5.3925, 5.3925 ), "times.new.roman" = c( 0.71375, 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "traditional.arabic" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "trebuchet.ms" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "tunga" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "tw.cen.mt.condensed.extra.bold" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "tw.cen.mt.condensed" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "tw.cen.mt" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "utsaah" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "vani" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125, 4.46375 ), "verdana" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.6075 ), "vijaya" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "viner.hand.itc" = c( 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 4.94625 ), "vivaldi" = c( 0.6425, 0.785, 0.785, 0.92875, 0.96375, 1.1075, 1.285, 1.285, 1.4825, 1.4825, 1.625, 1.7675, 1.80375, 1.80375, 1.9825, 2.1425, 2.17875, 2.32125, 2.32125, 2.46375, 2.69625, 2.69625, 2.69625, 2.83875, 3.0175, 3.0175, 3.17875, 3.17875, 3.3575 ), "vladimir.script" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575 ), "vrinda" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "webdings" = c( 1.625, 1.80375, 2.1075, 2.25, 2.42875, 2.7325, 2.875, 3.07125, 3.3575, 3.5, 3.69625, 3.9825, 4.1425, 4.32125, 4.6075, 4.7675, 4.94625, 5.25, 5.3925, 5.57125, 5.875, 6.0175, 6.19625, 6.5, 6.6425, 6.82125, 7.125, 7.2675, 7.46375 ), "wide.latin" = c( 2.1075, 2.25, 2.7325, 2.875, 3.07125, 3.5, 3.69625, 3.83875, 4.1425, 4.46375, 4.6075, 4.94625, 5.25, 5.3925, 5.71375, 6.0175, 6.19625, 6.5, 6.82125, 6.9825, 7.2675, 7.46375, 7.75, 8.08875, 8.2325, 8.535, 8.8575, 9, 9.33875 ) ) openxlsxFontSizeLookupTableBold <- data.frame( "agency.fb" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.21375, 3.5 ), "aharoni" = c( 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 2.875 ), "algerian" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "andalus" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "angsana.new" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25 ), "angsanaupc" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25 ), "aparajita" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "arial" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "arial.black" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625 ), "arial.narrow" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375, 3.21375 ), "arial.rounded.mt.bold" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "arial.unicode.ms" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "calibri" = c( 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "calibri.light" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "californian.fb" = c( 0.8575, 1, 1, 1.17875, 1.17875, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.21375, 3.21375, 3.5, 3.69625, 3.69625, 3.69625, 3.83875, 4.1425, 4.1425 ), "calisto.mt" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "cambria" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.32125 ), "cambria.math" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "candara" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "castellar" = c( 1.32125, 1.32125, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375, 4.7675, 4.94625, 4.94625, 5.25, 5.3925, 5.3925 ), "centaur" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "century" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "century.gothic" = c( 0.8575, 0.8575, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825 ), "century.schoolbook" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "chiller" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "colonna.mt" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "comic.sans.ms" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "consolas" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "constantia" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "cooper.black" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "copperplate.gothic.bold" = c( 1.32125, 1.32125, 1.625, 1.625, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.46375, 4.46375, 4.7675, 4.7675, 4.94625, 5.25, 5.25, 5.3925 ), "copperplate.gothic.light" = c( 1.32125, 1.32125, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 4.94625, 5.25, 5.3925, 5.3925 ), "corbel" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "cordia.new" = c( 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "david" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375 ), "dfkai-sb" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "dilleniaupc" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075 ), "dokchampa" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "dotum" = c( 0.8575, 0.8575, 1.32125, 1.17875, 1.17875, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "dotumche" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ebrima" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "edwardian.script.itc" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.3575, 3.5, 3.5 ), "elephant" = c( 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.69625, 3.69625, 3.83875, 4.1425, 4.1425, 4.32125, 4.6075, 4.6075, 4.7675, 5.08875, 5.08875, 5.25, 5.57125, 5.57125, 5.71375 ), "engravers.mt" = c( 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625 ), "eras.bold.itc" = c( 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375, 4.7675, 4.7675, 4.94625 ), "eras.demi.itc" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075 ), "eras.light.itc" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825 ), "eras.medium.itc" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.1425, 4.32125 ), "estrangelo.edessa" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "eucrosiaupc" = c( 0.57125, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 2.875, 3.07125, 3.21375, 3.21375 ), "euphemia" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "fangsong" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "felix.titling" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "fixedsys" = c( 1.21375, 1.21375, 1.21375, 1.21375, 1.21375, 1.21375, 1.21375, 1.21375, 2.46375, 2.46375, 2.46375, 2.46375, 2.46375, 2.46375, 2.46375, 2.46375, 3.7325, 3.7325, 3.7325, 3.7325, 3.7325, 3.7325, 3.7325, 3.7325, 3.7325, 4.9825, 4.9825, 4.9825, 4.9825 ), "footlight.mt.light" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "forte" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "franklin.gothic.book" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "franklin.gothic.demi" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "franklin.gothic.demi.cond" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "franklin.gothic.heavy" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "franklin.gothic.medium" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "franklin.gothic.medium.cond" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875 ), "frankruehl" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.07125 ), "freesiaupc" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.21375 ), "freestyle.script" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325 ), "french.script.mt" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325 ), "gabriola" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.07125 ), "gadugi" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "gautami" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "georgia" = c( 1.17875, 1.32125, 1.32125, 1.32125, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.08875 ), "gigi" = c( 1, 1, 1, 1, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.80375, 1.80375, 2.1075, 2.1075, 2.1075, 2.58875, 2.58875, 2.58875, 2.875, 2.875, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.69625, 3.9825 ), "gill.sans.mt" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "gill.sans.mt.condensed" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325 ), "gill.sans.mt.ext.condensed.bold" = c( 0.57125, 0.57125, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875 ), "gill.sans.ultra.bold" = c( 1.4825, 1.625, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 3.07125, 3.21375, 3.21375, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925, 5.71375, 5.875, 5.875, 6.19625, 6.3575, 6.5 ), "gill.sans.ultra.bold.condensed" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.32125 ), "gisha" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "gloucester.mt.extra.condensed" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.58875, 2.7325, 2.875, 2.875 ), "goudy.old.style" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "goudy.stout" = c( 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.42875, 2.42875, 2.58875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.9825, 3.9825, 4.1425, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925, 5.57125, 5.71375, 6.0175, 6.19625, 6.19625 ), "gulim" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.625, 1.96375, 1.96375, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "gulimche" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "gungsuh" = c( 0.8575, 0.8575, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "gungsuhche" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "haettenschweiler" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575 ), "harlow.solid.italic" = c( 0.92875, 0.92875, 1.07125, 1.07125, 1.285, 1.42875, 1.42875, 1.58875, 1.7675, 1.7675, 1.94625, 2.1075, 2.1075, 2.285, 2.285, 2.42875, 2.42875, 2.66, 2.80375, 2.80375, 2.94625, 3.125, 3.125, 3.32125, 3.46375, 3.46375, 3.6425, 3.6425, 3.80375 ), "harrington" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "high.tower.text" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "impact" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425 ), "imprint.mt.shadow" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "informal.roman" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.83875, 3.9825 ), "irisupc" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575 ), "iskoola.pota" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875 ), "jasmineupc" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5 ), "jokerman" = c( 1.32125, 1.32125, 1.625, 1.625, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.46375, 4.46375, 4.7675, 4.7675, 4.94625, 5.25, 5.25, 5.3925 ), "juice.itc" = c( 0.57125, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.7325 ), "kaiti" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "kalinga" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125 ), "kartika" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "khmer.ui" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "kodchiangupc" = c( 0.3925, 0.3925, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.42875 ), "kristen.itc" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "kunstler.script" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.58875 ), "lao.ui" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "latha" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "leelawadee" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "levenim.mt" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.32125 ), "lilyupc" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5 ), "lucida.bright" = c( 0.8575, 1, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075 ), "lucida.calligraphy" = c( 1.25, 1.3925, 1.58875, 1.7325, 1.7325, 2.07125, 2.1075, 2.25, 2.3925, 2.58875, 2.7325, 2.91, 3.05375, 3.05375, 3.42875, 3.42875, 3.57125, 3.7325, 3.94625, 4.08875, 4.2325, 4.3925, 4.42875, 4.75, 4.75, 4.91, 5.08875, 5.2675, 5.42875 ), "lucida.console" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.32125, 4.46375, 4.6075 ), "lucida.fax" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.7675 ), "lucida.handwriting" = c( 1.25, 1.3925, 1.58875, 1.7325, 1.7325, 2.07125, 2.25, 2.25, 2.58875, 2.58875, 2.7675, 2.91, 3.05375, 3.25, 3.42875, 3.57125, 3.57125, 3.91, 3.94625, 4.08875, 4.2325, 4.3925, 4.6075, 4.75, 4.91, 4.91, 5.2675, 5.2675, 5.42875 ), "lucida.sans" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.6075 ), "lucida.sans.typewriter" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375 ), "lucida.sans.unicode" = c( 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125, 4.6075, 4.6075, 4.7675 ), "magneto" = c( 1.17875, 1.32125, 1.625, 1.625, 1.80375, 2.1075, 2.1075, 2.25, 2.58875, 2.7325, 2.7325, 3.07125, 3.21375, 3.21375, 3.5, 3.69625, 3.69625, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925, 5.57125, 5.71375 ), "maiandra.gd" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "malgun.gothic" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.1425 ), "mangal" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "marlett" = c( 1.80375, 1.96375, 2.25, 2.42875, 2.58875, 2.875, 3.07125, 3.21375, 3.5, 3.69625, 3.83875, 4.1425, 4.32125, 4.46375, 4.7675, 4.94625, 5.08875, 5.3925, 5.57125, 5.71375, 6.0175, 6.19625, 6.3575, 6.6425, 6.82125, 6.9825, 7.2675, 7.46375, 7.6075 ), "matura.mt.script.capitals" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 4.94625 ), "meiryo" = c( 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375, 4.7675, 4.7675, 4.94625 ), "meiryo.ui" = c( 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375, 4.7675, 4.7675, 4.94625 ), "microsoft.himalaya" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.42875 ), "microsoft.jhenghei" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "microsoft.jhenghei.ui" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125 ), "microsoft.new.tai.lue" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "microsoft.phagspa" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "microsoft.sans.serif" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "microsoft.tai.le" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "microsoft.uighur" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.58875 ), "microsoft.yahei" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "microsoft.yahei.ui" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "microsoft.yi.baiti" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "miriam" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "miriam.fixed" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "mistral" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "modern" = c( 0.75, 0.75, 0.8925, 1.035, 1.035, 1.21375, 1.3575, 1.3575, 1.5175, 1.5175, 1.66, 1.83875, 1.83875, 2, 2.1425, 2.1425, 2.285, 2.46375, 2.46375, 2.625, 2.7675, 2.7675, 2.91, 2.91, 3.1075, 3.1075, 3.25, 3.3925, 3.3925 ), "modern.no..20" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "mongolian.baiti" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "monotype.corsiva" = c( 0.785, 0.92875, 1.07125, 1.07125, 1.1075, 1.285, 1.42875, 1.42875, 1.625, 1.7675, 1.7675, 1.9825, 1.9825, 2.1425, 2.285, 2.32125, 2.46375, 2.46375, 2.66, 2.66, 2.83875, 2.9825, 2.9825, 3.125, 3.17875, 3.3575, 3.5, 3.5, 3.67875 ), "moolboran" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875 ), "ms.gothic" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ms.mincho" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ms.outlook" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ms.pgothic" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ms.pmincho" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ms.reference.sans.serif" = c( 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.7675 ), "ms.reference.specialty" = c( 2.1075, 2.42875, 2.7325, 2.875, 3.07125, 3.5, 3.69625, 3.83875, 4.32125, 4.46375, 4.6075, 4.94625, 5.25, 5.3925, 5.71375, 5.875, 6.19625, 6.5, 6.6425, 6.82125, 7.2675, 7.46375, 7.6075, 8.08875, 8.2325, 8.375, 8.71375, 9, 9.16 ), "ms.sans.serif" = c( 0.8925, 1.035, 1.035, 1.3575, 1.3575, 1.5175, 1.5175, 1.5175, 1.83875, 2, 2, 2.1425, 2.1425, 2.1425, 2.7675, 2.7675, 2.7675, 2.7675, 3.1075, 3.1075, 3.25, 3.25, 3.25, 3.25, 3.7325, 3.7325, 3.7325, 4.0175, 4.0175 ), "ms.serif" = c( 0.75, 0.8925, 0.8925, 1.21375, 1.21375, 1.3575, 1.3575, 1.3575, 1.5175, 1.83875, 1.83875, 2.285, 2.285, 2.285, 2.46375, 2.46375, 2.46375, 2.46375, 2.7675, 2.7675, 2.7675, 3.875, 3.875, 3.875, 3.1075, 3.875, 3.875, 3.7325, 3.7325 ), "ms.ui.gothic" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "mt.extra" = c( 1.80375, 1.96375, 2.25, 2.42875, 2.58875, 2.875, 3.07125, 3.21375, 3.5, 3.69625, 3.83875, 4.32125, 4.46375, 4.6075, 4.94625, 5.08875, 5.25, 5.57125, 5.71375, 5.875, 6.19625, 6.3575, 6.5, 6.82125, 6.9825, 7.125, 7.46375, 7.6075, 7.75 ), "mv.boli" = c( 1.17875, 1.32125, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.3575, 3.3575, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.25 ), "narkisim" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.07125 ), "niagara.engraved" = c( 0.57125, 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075 ), "niagara.solid" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075 ), "nirmala.ui" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "nsimsun" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "nyala" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "ocr.a.extended" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.32125, 4.46375, 4.6075 ), "old.english.text.mt" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "onyx" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.17875, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.625, 1.96375, 1.96375, 1.96375, 2.1075, 2.1075, 2.1075 ), "palace.script.mt" = c( 0.6075, 0.6075, 0.785, 0.785, 0.785, 0.92875, 0.92875, 1.07125, 1.1075, 1.285, 1.285, 1.285, 1.42875, 1.4825, 1.625, 1.625, 1.625, 1.7675, 1.80375, 1.9825, 1.9825, 2.1425, 2.1425, 2.1425, 2.32125, 2.32125, 2.46375, 2.46375, 2.46375 ), "palatino.linotype" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "papyrus" = c( 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.32125, 4.32125, 4.46375 ), "parchment" = c( 0.3925, 0.3925, 0.3925, 0.3925, 0.57125, 0.57125, 0.57125, 0.57125, 0.71375, 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1, 1, 1.17875, 1.17875, 1.17875, 1.17875, 1.32125, 1.32125, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825 ), "perpetua" = c( 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.21375 ), "perpetua.titling.mt" = c( 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 4.94625 ), "plantagenet.cherokee" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "playbill" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.58875 ), "pmingliu" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "pmingliu-extb" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5 ), "poor.richard" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425 ), "pristina" = c( 0.71375, 1, 1.17875, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "raavi" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "rage.italic" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "rockwell" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875, 3.9825 ), "rockwell.condensed" = c( 0.57125, 0.71375, 0.71375, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 2.875, 3.07125 ), "rockwell.extra.bold" = c( 1, 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.42875, 2.42875, 2.7325, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875 ), "rod" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "roman" = c( 0.75, 0.75, 0.8925, 1.035, 1.035, 1.21375, 1.3575, 1.3575, 1.5175, 1.5175, 1.66, 1.83875, 1.83875, 2, 2.1425, 2.1425, 2.285, 2.46375, 2.46375, 2.625, 2.7675, 2.7675, 2.91, 2.91, 3.1075, 3.1075, 3.25, 3.3925, 3.3925 ), "sakkal.majalla" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "script" = c( 0.6075, 0.6075, 0.75, 0.75, 0.8925, 1.035, 1.035, 1.035, 1.21375, 1.3575, 1.3575, 1.5175, 1.5175, 1.66, 1.83875, 1.83875, 1.83875, 2, 2.1425, 2.1425, 2.285, 2.285, 2.46375, 2.46375, 2.625, 2.625, 2.7675, 2.7675, 2.91 ), "script.mt.bold" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "segoe.print" = c( 1.17875, 1.17875, 1.4825, 1.625, 1.625, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.875, 2.875, 3.07125, 3.3575, 3.3575, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925 ), "segoe.script" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 2.1075, 2.1075, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.3575, 3.3575, 3.5, 3.83875, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.25, 5.3925 ), "segoe.ui" = c( 0.8575, 0.8575, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425 ), "segoe.ui.light" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.83875 ), "segoe.ui.semibold" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625, 3.83875, 3.9825, 3.9825 ), "segoe.ui.semilight" = c( 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425 ), "segoe.ui.symbol" = c( 0.8575, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 3.9825 ), "shonar.bangla" = c( 0.71375, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 2.875 ), "showcard.gothic" = c( 1.17875, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.32125, 4.46375 ), "shruti" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875 ), "simhei" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "simplified.arabic" = c( 0.8575, 0.8575, 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425 ), "simplified.arabic.fixed" = c( 1, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 3.9825, 4.1425, 4.32125, 4.46375, 4.46375 ), "simsun" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "simsun-extb" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "small.fonts" = c( 0.8925, 0.8925, 1.21375, 1.5175, 1.5175, 1.5175, 1.83875, 1.83875, 1.83875, 2.285, 2.285, 2.285, 2.46375, 2.46375, 2.7675, 3.1075, 2.7675, 2.7675, 3.1075, 3.1075, 3.1075, 3.7325, 3.7325, 3.7325, 3.7325, 3.875, 3.875, 3.875, 3.875 ), "snap.itc" = c( 1.4825, 1.625, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.25, 5.3925, 5.57125, 5.71375, 5.875, 6.0175, 6.3575, 6.5, 6.5 ), "sylfaen" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "symbol" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "tahoma" = c( 1, 1, 1.32125, 1.32125, 1.4825, 1.625, 1.80375, 1.80375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.58875, 2.875, 2.875, 3.07125, 3.21375, 3.3575, 3.5, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.6075 ), "tempus.sans.itc" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.5, 3.69625, 3.83875, 3.9825, 4.1425, 4.1425, 4.46375, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875 ), "terminal" = c( 1.21375, 1.21375, 1.21375, 1.83875, 1.83875, 1.5175, 1.5175, 1.5175, 1.5175, 2.58875, 2.58875, 2.58875, 2.58875, 2.58875, 3.7325, 3.7325, 3.7325, 3.7325, 3.1075, 3.1075, 3.1075, 4.46375, 4.46375, 4.46375, 4.46375, 4.46375, 5.6075, 5.6075, 5.6075 ), "times.new.roman" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "traditional.arabic" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "trebuchet.ms" = c( 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125 ), "tunga" = c( 0.71375, 0.71375, 0.8575, 1, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575 ), "tw.cen.mt" = c( 0.71375, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625, 3.83875 ), "tw.cen.mt.condensed" = c( 0.57125, 0.71375, 0.8575, 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.07125 ), "tw.cen.mt.condensed.extra.bold" = c( 0.8575, 0.8575, 1, 1.17875, 1.17875, 1.32125, 1.4825, 1.4825, 1.625, 1.80375, 1.80375, 1.96375, 2.1075, 2.1075, 2.25, 2.42875, 2.42875, 2.58875, 2.7325, 2.7325, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5, 3.69625, 3.69625 ), "utsaah" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "vani" = c( 1, 1.17875, 1.4825, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 2.875, 3.21375, 3.21375, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.08875 ), "verdana" = c( 1.17875, 1.17875, 1.4825, 1.4825, 1.625, 1.96375, 1.96375, 2.1075, 2.25, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.83875, 3.83875, 4.1425, 4.1425, 4.32125, 4.6075, 4.6075, 4.7675, 4.94625, 5.08875, 5.25 ), "vijaya" = c( 0.57125, 0.57125, 0.71375, 0.71375, 0.8575, 1, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.4825, 1.625, 1.625, 1.80375, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.42875, 2.58875, 2.58875, 2.7325, 2.7325, 2.875 ), "viner.hand.itc" = c( 1.17875, 1.32125, 1.4825, 1.625, 1.80375, 1.96375, 2.1075, 2.1075, 2.42875, 2.42875, 2.58875, 2.7325, 2.875, 3.07125, 3.21375, 3.3575, 3.3575, 3.69625, 3.69625, 3.83875, 3.9825, 4.1425, 4.32125, 4.46375, 4.6075, 4.7675, 4.94625, 5.08875, 5.08875 ), "vivaldi" = c( 0.785, 0.92875, 0.92875, 1.07125, 1.1075, 1.285, 1.42875, 1.42875, 1.625, 1.625, 1.7675, 1.94625, 1.9825, 1.9825, 2.1425, 2.285, 2.32125, 2.46375, 2.46375, 2.66, 2.83875, 2.83875, 2.83875, 2.9825, 3.17875, 3.17875, 3.3575, 3.3575, 3.5 ), "vladimir.script" = c( 0.8575, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.07125, 3.21375, 3.3575, 3.3575, 3.5 ), "vrinda" = c( 0.71375, 0.8575, 1, 1, 1.17875, 1.32125, 1.32125, 1.4825, 1.625, 1.625, 1.80375, 1.96375, 1.96375, 2.1075, 2.25, 2.25, 2.42875, 2.58875, 2.58875, 2.7325, 2.875, 2.875, 3.07125, 3.21375, 3.21375, 3.3575, 3.5, 3.5, 3.69625 ), "webdings" = c( 1.80375, 1.96375, 2.25, 2.42875, 2.58875, 2.875, 3.07125, 3.21375, 3.5, 3.69625, 3.83875, 4.1425, 4.32125, 4.46375, 4.7675, 4.94625, 5.08875, 5.3925, 5.57125, 5.71375, 6.0175, 6.19625, 6.3575, 6.6425, 6.82125, 6.9825, 7.2675, 7.46375, 7.6075 ), "wide.latin" = c( 2.25, 2.42875, 2.875, 3.07125, 3.21375, 3.69625, 3.83875, 3.9825, 4.32125, 4.6075, 4.7675, 5.08875, 5.3925, 5.57125, 5.875, 6.19625, 6.3575, 6.6425, 6.9825, 7.125, 7.46375, 7.6075, 7.8925, 8.2325, 8.375, 8.71375, 9, 9.16, 9.4825 ) ) ## 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 length(worksheets[[sheet]]$conditionalFormatting):1) 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 <- readLines(stylesXML, warn = FALSE, encoding = "UTF-8") 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 <- getNodes(xml = stylesTxt, tagIn = " 0) styles$tableStyles <<- stri_join(tableStyles, ">") extLst <- getNodes(xml = stylesTxt, tagIn = "") if (length(extLst) > 0) styles$extLst <<- extLst ## Number formats numFmts <- getChildlessNode(xml = stylesTxt, tag = " 0) { numFmtsIds <- sapply(numFmts, getAttr, tag = 'numFmtId="', USE.NAMES = FALSE) formatCodes <- sapply(numFmts, getAttr, tag = 'formatCode="', USE.NAMES = FALSE) numFmts <- lapply(1:length(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 = "" ) ) } else { workbookProtection <<- "" } } ) openxlsx/R/wrappers.R0000644000176200001440000040670313564471432014344 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 \code{\link{loadWorkbook}} #' @seealso \code{\link{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 #' @param wb A Workbook object to write to file #' @param file A character string naming an xlsx file #' @param overwrite If \code{TRUE}, overwrite any existing file. #' @seealso \code{\link{createWorkbook}} #' @seealso \code{\link{addWorksheet}} #' @seealso \code{\link{loadWorkbook}} #' @seealso \code{\link{writeData}} #' @seealso \code{\link{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), add = TRUE) ## increase scipen to avoid writing in scientific sci_pen <- getOption("scipen") options("scipen" = 10000) on.exit(options("scipen" = sci_pen), add = TRUE) if(!"Workbook" %in% class(wb)) stop("First argument must be a Workbook.") if(!is.logical(overwrite)) overwrite = FALSE if(file.exists(file) & !overwrite) stop("File already exists!") xlsx_file <- wb$saveWorkbook() file.copy(from = xlsx_file, to = file, overwrite = overwrite) ## delete temporary dir unlink(dirname(xlsx_file), force = TRUE, recursive = TRUE) invisible(1) } #' @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 \code{\link{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), add = TRUE) if(!is.numeric(x)) stop("x must be numeric.") convert_to_excel_ref(cols = x, LETTERS = LETTERS) } #' @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 \code{\link{mergeCells}} removeCellMerge <- 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.") 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 \code{\link{names}} to rename a worksheet in a Workbook #' @details DEPRECATED. Use \code{\link{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){ 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.") 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 \code{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{\bold{&[Page]}}{ Page number} #' \item{\bold{&[Pages]}}{ Number of pages} #' \item{\bold{&[Date]}}{ Current date} #' \item{\bold{&[Time]}}{ Current time} #' \item{\bold{&[Path]}}{ File path} #' \item{\bold{&[File]}}{ File name} #' \item{\bold{&[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 = TRUE, tabColour = NULL, zoom = 100, header = NULL, footer = NULL, evenHeader = NULL, evenFooter = NULL, firstHeader = NULL, firstFooter = NULL, visible = TRUE, paperSize = getOption("openxlsx.paperSize", default = 9), orientation = getOption("openxlsx.orientation", default = "portrait"), vdpi = getOption("openxlsx.vdpi", default = getOption("openxlsx.dpi", default = 300)), hdpi = getOption("openxlsx.hdpi", default = getOption("openxlsx.dpi", default = 300))){ 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(tolower(sheetName) %in% tolower(wb$sheet_names)) stop("A worksheet by that name 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("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 \code{\link{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.") od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 exSciPen <- getOption("scipen") od <- getOption("OutDec") options("scipen" = 10000) options("OutDec" = ".") on.exit(options("scipen" = exSciPen), add = TRUE) on.exit(expr = options("OutDec" = od), 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(1:length(d), function(i) sum(d[[i]]*(26^(0:(length(d[[i]])-1L)))) )) } 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 \code{\link{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{\bold{GENERAL}} #' \item{\bold{NUMBER}} #' \item{\bold{CURRENCY}} #' \item{\bold{ACCOUNTING}} #' \item{\bold{DATE}} #' \item{\bold{LONGDATE}} #' \item{\bold{TIME}} #' \item{\bold{PERCENTAGE}} #' \item{\bold{FRACTION}} #' \item{\bold{SCIENTIFIC}} #' \item{\bold{TEXT}} #' \item{\bold{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{\bold{"top"}}{ Top border} #' \item{\bold{bottom}}{ Bottom border} #' \item{\bold{left}}{ Left border} #' \item{\bold{right}}{ Right border} #' \item{\bold{TopBottom} or \bold{c("top", "bottom")}}{ Top and bottom border} #' \item{\bold{LeftRight} or \bold{c("left", "right")}}{ Left and right border} #' \item{\bold{TopLeftRight} or \bold{c("top", "left", "right")}}{ Top, Left and right border} #' \item{\bold{TopBottomLeftRight} or \bold{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{\bold{none}}{ No Border} #' \item{\bold{thin}}{ thin border} #' \item{\bold{medium}}{ medium border} #' \item{\bold{dashed}}{ dashed border} #' \item{\bold{dotted}}{ dotted border} #' \item{\bold{thick}}{ thick border} #' \item{\bold{double}}{ double line border} #' \item{\bold{hair}}{ Hairline border} #' \item{\bold{mediumDashed}}{ medium weight dashed border} #' \item{\bold{dashDot}}{ dash-dot border} #' \item{\bold{mediumDashDot}}{ medium weight dash-dot border} #' \item{\bold{dashDotDot}}{ dash-dot-dot border} #' \item{\bold{mediumDashDotDot}}{ medium weight dash-dot-dot border} #' \item{\bold{slantDashDot}}{ slanted dash-dot border} #' } #' #' @param bgFill Cell background fill colour. #' A valid colour (belonging to colours()) or a valid hex colour beginning with "#". #' -- \bold{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{\bold{left}}{ Left horizontal align cell contents} #' \item{\bold{right}}{ Right horizontal align cell contents} #' \item{\bold{center}}{ Center horizontal align cell contents} #' } #' #' @param valign A name #' Vertical alignment of cell contents #' \itemize{ #' \item{\bold{top}}{ Top vertical align cell contents} #' \item{\bold{center}}{ Center vertical align cell contents} #' \item{\bold{bottom}}{ Bottom vertical align cell contents} #' } #' #' @param textDecoration #' Text styling. #' \itemize{ #' \item{\bold{bold}}{ Bold cell contents} #' \item{\bold{strikeout}}{ Strikeout cell contents} #' \item{\bold{italic}}{ Italicise cell contents} #' \item{\bold{underline}}{ Underline cell contents} #' \item{\bold{underline2}}{ Double underline cell contents} #' } #' #' @param wrapText Logical. If \code{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 = "GENERAL", border = NULL, borderColour = getOption("openxlsx.borderColour", "black"), borderStyle = getOption("openxlsx.borderStyle", "thin"), bgFill = NULL, fgFill = NULL, halign = NULL, valign = NULL, textDecoration = NULL, wrapText = FALSE, textRotation = NULL, indent = NULL, locked = NULL, hidden = NULL){ ### Error checking od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 <- getOption("openxlsx.dateFormat", getOption("openxlsx.dateformat", "date")) }else if(numFmt == "longdate"){ numFmt <- getOption("openxlsx.datetimeFormat", getOption("openxlsx.datetimeformat", getOption("openxlsx.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")) 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", ""))) 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 }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 }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" = 9999, 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 \code{TRUE}, style will be applied to all combinations of rows and cols. #' @param 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. #' @seealso \code{\link{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") #' #' addStyle(wb, sheet = 1, headerStyle, rows = 1, cols = 1:6, gridExpand = TRUE) #' #' ## 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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), add = TRUE) 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!") } od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 \code{TRUE}, freezes the first row (equivalent to firstActiveRow = 2) #' @param firstCol If \code{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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. #' @seealso \code{\link{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 \code{\link{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.") od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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. #' #' @seealso \code{\link{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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" 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)) } 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 \code{\link{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) od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 \code{\link{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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 \code{\link{insertImage}} #' @export #' @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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 \code{\link{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 \code{\link{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 \code{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.") od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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{\bold{&[Page]}}{ Page number} #' \item{\bold{&[Pages]}}{ Number of pages} #' \item{\bold{&[Date]}}{ Current date} #' \item{\bold{&[Time]}}{ Current time} #' \item{\bold{&[Path]}}{ File path} #' \item{\bold{&[File]}}{ File name} #' \item{\bold{&[Tab]}}{ Worksheet name} #' } #' @export #' @seealso \code{\link{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.") od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 #' @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 \code{TRUE}, worksheet is scaled to fit to page width on printing. #' @param fitToHeight If \code{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. #' @export #' @details #' paperSize is an integer corresponding to: #' \itemize{ #' \item{\bold{1}}{ Letter paper (8.5 in. by 11 in.)} #' \item{\bold{2}}{ Letter small paper (8.5 in. by 11 in.)} #' \item{\bold{3}}{ Tabloid paper (11 in. by 17 in.)} #' \item{\bold{4}}{ Ledger paper (17 in. by 11 in.)} #' \item{\bold{5}}{ Legal paper (8.5 in. by 14 in.)} #' \item{\bold{6}}{ Statement paper (5.5 in. by 8.5 in.)} #' \item{\bold{7}}{ Executive paper (7.25 in. by 10.5 in.)} #' \item{\bold{8}}{ A3 paper (297 mm by 420 mm)} #' \item{\bold{9}}{ A4 paper (210 mm by 297 mm)} #' \item{\bold{10}}{ A4 small paper (210 mm by 297 mm)} #' \item{\bold{11}}{ A5 paper (148 mm by 210 mm)} #' \item{\bold{12}}{ B4 paper (250 mm by 353 mm)} #' \item{\bold{13}}{ B5 paper (176 mm by 250 mm)} #' \item{\bold{14}}{ Folio paper (8.5 in. by 13 in.)} #' \item{\bold{15}}{ Quarto paper (215 mm by 275 mm)} #' \item{\bold{16}}{ Standard paper (10 in. by 14 in.)} #' \item{\bold{17}}{ Standard paper (11 in. by 17 in.)} #' \item{\bold{18}}{ Note paper (8.5 in. by 11 in.)} #' \item{\bold{19}}{ #9 envelope (3.875 in. by 8.875 in.)} #' \item{\bold{20}}{ #10 envelope (4.125 in. by 9.5 in.)} #' \item{\bold{21}}{ #11 envelope (4.5 in. by 10.375 in.)} #' \item{\bold{22}}{ #12 envelope (4.75 in. by 11 in.)} #' \item{\bold{23}}{ #14 envelope (5 in. by 11.5 in.)} #' \item{\bold{24}}{ C paper (17 in. by 22 in.)} #' \item{\bold{25}}{ D paper (22 in. by 34 in.)} #' \item{\bold{26}}{ E paper (34 in. by 44 in.)} #' \item{\bold{27}}{ DL envelope (110 mm by 220 mm)} #' \item{\bold{28}}{ C5 envelope (162 mm by 229 mm)} #' \item{\bold{29}}{ C3 envelope (324 mm by 458 mm)} #' \item{\bold{30}}{ C4 envelope (229 mm by 324 mm)} #' \item{\bold{31}}{ C6 envelope (114 mm by 162 mm)} #' \item{\bold{32}}{ C65 envelope (114 mm by 229 mm)} #' \item{\bold{33}}{ B4 envelope (250 mm by 353 mm)} #' \item{\bold{34}}{ B5 envelope (176 mm by 250 mm)} #' \item{\bold{35}}{ B6 envelope (176 mm by 125 mm)} #' \item{\bold{36}}{ Italy envelope (110 mm by 230 mm)} #' \item{\bold{37}}{ Monarch envelope (3.875 in. by 7.5 in.).} #' \item{\bold{38}}{ 6 3/4 envelope (3.625 in. by 6.5 in.)} #' \item{\bold{39}}{ US standard fanfold (14.875 in. by 11 in.)} #' \item{\bold{40}}{ German standard fanfold (8.5 in. by 12 in.)} #' \item{\bold{41}}{ German legal fanfold (8.5 in. by 13 in.)} #' \item{\bold{42}}{ ISO B4 (250 mm by 353 mm)} #' \item{\bold{43}}{ Japanese double postcard (200 mm by 148 mm)} #' \item{\bold{44}}{ Standard paper (9 in. by 11 in.)} #' \item{\bold{45}}{ Standard paper (10 in. by 11 in.)} #' \item{\bold{46}}{ Standard paper (15 in. by 11 in.)} #' \item{\bold{47}}{ Invite envelope (220 mm by 220 mm)} #' \item{\bold{50}}{ Letter extra paper (9.275 in. by 12 in.)} #' \item{\bold{51}}{ Legal extra paper (9.275 in. by 15 in.)} #' \item{\bold{52}}{ Tabloid extra paper (11.69 in. by 18 in.)} #' \item{\bold{53}}{ A4 extra paper (236 mm by 322 mm)} #' \item{\bold{54}}{ Letter transverse paper (8.275 in. by 11 in.)} #' \item{\bold{55}}{ A4 transverse paper (210 mm by 297 mm)} #' \item{\bold{56}}{ Letter extra transverse paper (9.275 in. by 12 in.)} #' \item{\bold{57}}{ SuperA/SuperA/A4 paper (227 mm by 356 mm)} #' \item{\bold{58}}{ SuperB/SuperB/A3 paper (305 mm by 487 mm)} #' \item{\bold{59}}{ Letter plus paper (8.5 in. by 12.69 in.)} #' \item{\bold{60}}{ A4 plus paper (210 mm by 330 mm)} #' \item{\bold{61}}{ A5 transverse paper (148 mm by 210 mm)} #' \item{\bold{62}}{ JIS B5 transverse paper (182 mm by 257 mm)} #' \item{\bold{63}}{ A3 extra paper (322 mm by 445 mm)} #' \item{\bold{64}}{ A5 extra paper (174 mm by 235 mm)} #' \item{\bold{65}}{ ISO B5 extra paper (201 mm by 276 mm)} #' \item{\bold{66}}{ A2 paper (420 mm by 594 mm)} #' \item{\bold{67}}{ A3 transverse paper (297 mm by 420 mm)} #' \item{\bold{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){ 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.") 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) ## 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 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 #' @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) { if (!"Workbook" %in% class(wb)) stop("First argument must be a Workbook.") invisible(wb$protectWorkbook(protect = protect, password = password, lockStructure = lockStructure, lockWindows = lockWindows)) } #' @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 \code{TRUE}, 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){ 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.") 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") wb$sheetOrder <- value 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 \code{\link{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) if(origin == "1900-01-01") x[notNa] <- x[notNa] - 2 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, tx = "Australia/Perth") convertToDateTime <- function(x, origin = "1900-01-01", ...){ sci_pen <- getOption("scipen") options("scipen" = 10000) on.exit(options("scipen" = sci_pen), 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 \code{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) { od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 a named region. #' @description Create 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. #' @details Region is given by: min(cols):max(cols) X min(rows):max(rows) #' @export #' @seealso \code{\link{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 #' #' ## read named regions #' df <- read.xlsx(wb, namedRegion = "iris") #' head(df) #' #' df <- read.xlsx(out_file, namedRegion = "iris2") #' head(df)} createNamedRegion <- function(wb, sheet, cols, rows, name){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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){ stop(sprintf("Named region with name '%s' already exists!", name)) }else if(grepl("[^A-Z0-9_\\.]", name[1], ignore.case = TRUE)){ stop("Invalid characters in name") }else 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]) ) } #' @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 \code{\link{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 <- file.path(tempdir(), "named_regions_tmp") xmlFiles <- unzip(x, exdir = xmlDir) workbook <- xmlFiles[grepl("workbook.xml$", xmlFiles, perl = TRUE)] workbook <- unlist(readLines(workbook, warn = FALSE, encoding = "UTF-8")) dn <- getChildlessNode(xml = removeHeadTag(workbook), tag = "', 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") #' #' \dontrun{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) 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") #' #' \dontrun{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) 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 Columns to apply conditional formatting to #' @param rows 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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 \code{\link{convertToDate}} #' @return One of "1900-01-01" or "1904-01-01". #' @seealso \code{\link{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 <- file.path(tempdir(), "_excelXMLRead") xmlFiles <- unzip(xlsxFile, exdir = xmlDir) on.exit(unlink(xmlDir, recursive = TRUE), add = TRUE) workbook <- xmlFiles[grepl("workbook.xml$", xmlFiles, perl = TRUE)] workbook <- paste(unlist(readLines(workbook, warn = FALSE)), 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 <- file.path(tempdir(), "_excelXMLRead") xmlFiles <- unzip(file, exdir = xmlDir) on.exit(unlink(xmlDir, recursive = TRUE), add = TRUE) workbook <- xmlFiles[grepl("workbook.xml$", xmlFiles, perl = TRUE)] workbook <- readLines(workbook, warn=FALSE, encoding="UTF-8") 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) { od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 1:length(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 \code{\link{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"){ 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.") 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 \code{\link{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 \code{\link{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 \code{\link{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 \code{Workbook} object #' @param current A \code{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 \code{\link{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) } openxlsx/R/openXL.R0000644000176200001440000000635113560564727013707 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 \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}). #' #' @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){ od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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) } 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.R0000644000176200001440000002050013564724127015666 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)) if(all(sapply(hf, length) == 0)) 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 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(1:length(hyperlinks), "h") xml <- paste(xml, paste("", paste(sapply(1:length(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(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(extLst) > 0) xml <- paste0(xml , sprintf('' , length(extLst)) , paste0(pxml(extLst), ''), collapse = "") 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.R0000644000176200001440000000457413560564727015775 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.R0000644000176200001440000001511513560564727017274 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(1:length(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 = getOption("openxlsx.minWidth", 3), maxW = getOption("openxlsx.maxWidth", 250)) missingAuto <- autoCols[!autoCols %in% names(calculatedWidths)] widths[names(calculatedWidths)] <- calculatedWidths + 0.71 } widths[missingAuto] <- 9.15 } ## Calculate width of auto colNodes <- sprintf('', cols, cols, widths, hidden) ## Append new col widths XML to worksheets[[sheet]]$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.R0000644000176200001440000001734213572442033016530 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) 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 pInds <- which(sapply(colClasses, function(x) any(c("posixct", "posixt", "posixlt") %in% x))) if(length(pInds) > 0 & nRows > 0){ t <- sapply(pInds, function(i) { tzi <- format(df[[i]][[1]], "%z") if(is.na(tzi)){ tz_tmp <- na.omit(df[[i]]) tzi <- ifelse(length(tz_tmp) > 0, format(tz_tmp[1], "%z"), NA) } return(tzi) }) offSet <- suppressWarnings(ifelse(substr(t,1,1) == "+", 1L, -1L) * (as.integer(substr(t,2,3)) + as.integer(substr(t,4,5)) / 60) / 24) for(i in 1:length(pInds)){ if(is.na(offSet[i])) offSet[i] <- 0 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))) df[[i]] <- sapply(lapply(df[[i]], unlist), stri_join, collapse = list_sep) } if("formula" %in% allColClasses){ for(i in which(sapply(colClasses, function(x) "formula" %in% x))){ df[[i]] <- replaceIllegalCharacters(as.character(df[[i]])) class(df[[i]]) <- "openxlsx_formula" } } if("hyperlink" %in% allColClasses){ for(i in which(sapply(colClasses, function(x) "hyperlink" %in% x))) class(df[[i]]) <- "hyperlink" } 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 } ## Forumlas f_in <- rep.int(as.character(NA), length(t)) any_functions <- FALSE if("openxlsx_formula" %in% colClasses){ ## alter the elements of t where we have a formula to be "str" 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), "") 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(1:length(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) 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.R0000644000176200001440000003257313564503372017235 0ustar liggesusers #' @name conditionalFormatting #' @aliases databar #' @title Add conditional formatting to cells #' @description Add conditional formatting to cells #' @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. 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', 'colorscale', 'databar', 'duplicates' or "contains' (case insensitive). #' @param ... See below #' @details See Examples. #' #' If type == "expression" #' \itemize{ #' \item{style is a Style object. See \code{\link{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{\bold{showvalue} If FALSE the cell value is hidden. Default TRUE.} #' \item{\bold{gradient} If FALSE colour gradient is removed. Default TRUE.} #' \item{\bold{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}}} #' \item{rule is ignored.} #' } #' #' If type == "contains" #' \itemize{ #' \item{style is a Style object. See \code{\link{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}}} #' \item{rule is a numeric vector of length 2 specifying lower and upper bound (Inclusive)} #' } #' #' @seealso \code{\link{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, "colourScale", zoom = 30) #' addWorksheet(wb, "databar") #' addWorksheet(wb, "between") #' 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") #' #' ## 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 #' #' ## Betweem #' # 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)) #' #' ## Logical Operators #' # You can use Excels logical Opertors #' 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", ...) { od <- getOption("OutDec") options("OutDec" = ".") on.exit(expr = options("OutDec" = od), 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 == "between") { type <- "between" } else if (type != "expression") { stop( "Invalid type argument. Type must be one of 'expression', 'colourScale', 'databar', 'duplicates' or 'contains'" ) } ## 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 == "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)) } 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.R0000644000176200001440000011736413564464415015143 0ustar liggesusers #' @name loadWorkbook #' @title Load an existing .xlsx file #' @author Alexander Walker #' @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 \code{\link{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) file <- getFile(file) if(!file.exists(file)) stop("File does not exist.") ## create temp dir xmlDir <- file.path(tempdir(), paste0(tempfile(tmpdir = ""), "_openxlsx_loadWorkbook")) ## 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 <- xmlFiles[grepl("drawings/drawing[0-9]+.xml$", xmlFiles, perl = TRUE)] worksheetsXML <- xmlFiles[grepl("/worksheets/sheet[0-9]+", xmlFiles, perl = TRUE)] coreXML <- xmlFiles[grepl("core.xml$", xmlFiles, perl = TRUE)] workbookXML <- xmlFiles[grepl("workbook.xml$", xmlFiles, perl = TRUE)] stylesXML <- xmlFiles[grepl("styles.xml$", xmlFiles, perl = TRUE)] sharedStringsXML <- xmlFiles[grepl("sharedStrings.xml$", xmlFiles, perl = TRUE)] themeXML <- xmlFiles[grepl("theme[0-9]+.xml$", xmlFiles, perl = TRUE)] drawingRelsXML <- xmlFiles[grepl("drawing[0-9]+.xml.rels$", xmlFiles, perl = TRUE)] sheetRelsXML <- xmlFiles[grepl("sheet[0-9]+.xml.rels$", xmlFiles, perl = TRUE)] media <- xmlFiles[grepl("image[0-9]+.[a-z]+$", xmlFiles, perl = TRUE)] vmlDrawingXML <- xmlFiles[grepl("drawings/vmlDrawing[0-9]+\\.vml$", xmlFiles, perl = TRUE)] vmlDrawingRelsXML <- xmlFiles[grepl("vmlDrawing[0-9]+.vml.rels$", xmlFiles, perl = TRUE)] commentsXML <- xmlFiles[grepl("xl/comments[0-9]+\\.xml", xmlFiles, perl = TRUE)] embeddings <- xmlFiles[grepl("xl/embeddings", xmlFiles, perl = TRUE)] charts <- xmlFiles[grepl("xl/charts/.*xml$", xmlFiles, perl = TRUE)] chartsRels <- xmlFiles[grepl("xl/charts/_rels", xmlFiles, perl = TRUE)] chartSheetsXML <- xmlFiles[grepl("xl/chartsheets/sheet[0-9]+\\.xml", xmlFiles, perl = TRUE)] tablesXML <- xmlFiles[grepl("tables/table[0-9]+.xml$", xmlFiles, perl = TRUE)] tableRelsXML <- xmlFiles[grepl("table[0-9]+.xml.rels$", xmlFiles, perl = TRUE)] queryTablesXML <- xmlFiles[grepl("queryTable[0-9]+.xml$", xmlFiles, perl = TRUE)] connectionsXML <- xmlFiles[grepl("connections.xml$", xmlFiles, perl = TRUE)] extLinksXML <- xmlFiles[grepl("externalLink[0-9]+.xml$", xmlFiles, perl = TRUE)] extLinksRelsXML <- xmlFiles[grepl("externalLink[0-9]+.xml.rels$", xmlFiles, perl = TRUE)] # pivot tables pivotTableXML <- xmlFiles[grepl("pivotTable[0-9]+.xml$", xmlFiles, perl = TRUE)] pivotTableRelsXML <- xmlFiles[grepl("pivotTable[0-9]+.xml.rels$", xmlFiles, perl = TRUE)] pivotDefXML <- xmlFiles[grepl("pivotCacheDefinition[0-9]+.xml$", xmlFiles, perl = TRUE)] pivotDefRelsXML <- xmlFiles[grepl("pivotCacheDefinition[0-9]+.xml.rels$", xmlFiles, perl = TRUE)] pivotCacheRecords <- xmlFiles[grepl("pivotCacheRecords[0-9]+.xml$", xmlFiles, perl = TRUE)] ## slicers slicerXML <- xmlFiles[grepl("slicer[0-9]+.xml$", xmlFiles, perl = TRUE)] slicerCachesXML <- xmlFiles[grepl("slicerCache[0-9]+.xml$", xmlFiles, perl = TRUE)] ## VBA Macro vbaProject <- xmlFiles[grepl("vbaProject\\.bin$", xmlFiles, perl = TRUE)] ## remove all EXCEPT media and charts if(!isUnzipped){ on.exit(expr = unlink(xmlFiles[!grepl("charts|media|vmlDrawing|comment|embeddings|pivot|slicer|vbaProject", xmlFiles, ignore.case = TRUE)], recursive = TRUE, force = TRUE), add = TRUE) } ## core if(length(coreXML) == 1){ coreXML <- paste(readLines(con = coreXML, encoding="UTF-8", warn = FALSE), 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 <- xmlFiles[grepl("workbook.xml.rels$", xmlFiles, perl = TRUE)] if(length(workbookRelsXML) > 0){ workbookRelsXML <- paste(readLines(con = workbookRelsXML, encoding="UTF-8", warn = FALSE), collapse = "") workbookRelsXML <- getChildlessNode(xml = workbookRelsXML, tag = " 0){ workbookRelsXML <- workbookRelsXML[grepl("chartsheets/sheet", workbookRelsXML, fixed = 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 <- xmlFiles[grepl("xl/chartsheets/_rels", xmlFiles, perl = 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 <- readLines(workbookXML, warn=FALSE, encoding="UTF-8") 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", unlist(strsplit(sheets, split = " 0) wb$workbook$calcPr <- calcPr workbookPr <- getChildlessNode(xml = workbook, tag = " 0) wb$workbook$workbookPr <- workbookPr workbookProtection <- getChildlessNode(xml = workbook, tag = " 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 <- readLines(sharedStringsXML, warn = FALSE, encoding = "UTF-8") 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(pivotTableXML) 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[1:length(pivotTableXML)] <- pivotTableXML pivot_content_type <- c(pivot_content_type, sprintf('', 1:length(pivotTableXML))) } if(length(pivotDefXML) > 0){ wb$pivotDefinitions[1:length(pivotDefXML)] <- pivotDefXML pivot_content_type <- c(pivot_content_type, sprintf('', 1:length(pivotDefXML))) } if(length(pivotCacheRecords) > 0){ wb$pivotRecords[1:length(pivotCacheRecords)] <- pivotCacheRecords pivot_content_type <- c(pivot_content_type, sprintf('', 1:length(pivotCacheRecords))) } if(length(pivotDefRelsXML) > 0) wb$pivotDefinitionsRels[1:length(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, 1:length(pivotDefXML)) ) caches <- getNodes(xml = workbook, tagIn = "") caches <- getChildlessNode(xml = caches, tag = "', 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]], function(x) readLines(x, warn = FALSE, encoding = "UTF-8"))), collapse = "")) ## externalLinks if(length(extLinksXML) > 0){ wb$externalLinks <- lapply(sort(extLinksXML), function(x) removeHeadTag(cppReadFile(x))) wb$Content_Types <-c(wb$Content_Types, sprintf('', 1:length(extLinksXML))) wb$workbook.xml.rels <- c(wb$workbook.xml.rels, sprintf('', 1:length(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(1:length(style_names), function(i) {wb$styleObjects[[i]]$sheet = style_names[[i]]; wb$styleObjects[[i]]}) } ## Fix headers/footers for(i in 1:length(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(1:length(allRels), function(i) { if(haveRels[i]){ xml <- readLines(allRels[[i]], warn = FALSE, encoding = "UTF-8") xml <- removeHeadTag(xml) xml <- gsub("", "", xml) xml <- gsub("", "", xml) xml <- getChildlessNode(xml = xml, tag = "" } 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 <- 1:length(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(1:length(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 1:length(tables)){ if(length(tables[[i]]) > 0){ k <- 1:length(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(readLines(x, warn = FALSE), 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('', 1:length(wb$tables)+2)) ## relabel ids for(i in 1:length(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", 1:length(tablesXML)) attr(wb$tables, "sheet") <- tableSheets attr(wb$tables, "tableName") <- displayNames for(i in 1:length(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) x[grepl("drawings/drawing", x)]) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing if(length(drawingRelsXML) > 0){ dRels <- lapply(drawingRelsXML, readLines, warn = FALSE) dRels <- unlist(lapply(dRels, removeHeadTag)) dRels <- gsub("", "", dRels) dRels <- gsub("", "", dRels) } if(length(drawingsXML) > 0){ dXML <- lapply(drawingsXML, readLines, warn = FALSE, encoding = "UTF-8") 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 1:length(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) x[grepl("drawings/vmlDrawing", x)]) 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 1:length(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(readLines(vmlDrawingXML[ind], warn = FALSE), 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) x[grepl("drawings/vmlDrawing[0-9]+\\.vml", x)]) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing commentXMLrelationship <- lapply(xml, function(x) x[grepl("comments[0-9]+\\.xml", x)]) hasComment <- sapply(commentXMLrelationship, length) > 0 ## which sheets have a comment for(i in 1:length(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(readLines(vmlDrawingXML[ind], warn = FALSE), 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(readLines(commentsXML[grepl(target, commentsXML)], warn = FALSE), 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 = ")).*?[^/]+', comments, perl = TRUE)) comments <- lapply(comments, function(x) gsub("<", "", x)) comments <- lapply(comments, function(x) gsub(".*?>", "", x, perl = TRUE)) wb$comments[[i]] <- lapply(1:length(comments), function(j){ comment_list <- list("ref" = refs[j], "author" = authors[j], "comment" = comments[[j]], "style" = style[[j]], "clientData" = cd[[j]]) }) } } } } ## rels image drawXMLrelationship <- lapply(xml, function(x) x[grepl("relationships/image", x)]) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing if(any(hasDrawing)){ for(i in 1:length(xml)){ if(hasDrawing[i]){ image_ids <- unlist(getId(drawXMLrelationship[[i]])) new_image_ids <- paste0("rId", 1:length(image_ids) + 70000) for(j in 1:length(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) x[grepl("relationships/package", x)]) hasDrawing <- sapply(drawXMLrelationship, length) > 0 ## which sheets have a drawing if(any(hasDrawing)){ for(i in 1:length(xml)){ if(hasDrawing[i]){ image_ids <- unlist(getId(drawXMLrelationship[[i]])) new_image_ids <- paste0("rId", 1:length(image_ids) + 90000) for(j in 1:length(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)))) sheetWithPivot <- which(sapply(pivotTableJ, length) > 0) pivotRels <- lapply(xml, function(x) {y <- x[grepl("pivotTable", x)]; y[order(nchar(y), y)]}) hasPivot <- sapply(pivotRels, length) > 0 ## Modify rIds for(i in 1:length(pivotRels)){ if(hasPivot[i]){ for(j in 1:length(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 <- 1:length(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 <- which(grepl(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('', 1:length(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)) } return(wb) } openxlsx/R/openxlsx.R0000644000176200001440000000277313563451401014351 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 #' @import grDevices #' @import stats #' @importFrom Rcpp sourceCpp #' @importFrom zip zipr #' @importFrom utils download.file head menu unzip #' #' @seealso #' \itemize{ #' \item{\code{vignette("Introduction", package = "openxlsx")}} #' \item{\code{vignette("formatting", package = "openxlsx")}} #' \item{\code{\link{writeData}}} #' \item{\code{\link{writeDataTable}}} #' \item{\code{\link{write.xlsx}}} #' \item{\code{\link{read.xlsx}}} #' } #' for examples #' #' @details #' The openxlsx package uses global options to simplify formatting: #' #' \itemize{ #' \item{\code{options("openxlsx.borderColour" = "black")}} #' \item{\code{options("openxlsx.borderStyle" = "thin")}} #' \item{\code{options("openxlsx.dateFormat" = "mm/dd/yyyy")}} #' \item{\code{options("openxlsx.datetimeFormat" = "yyyy-mm-dd hh:mm:ss")}} #' \item{\code{options("openxlsx.numFmt" = NULL)}} #' \item{\code{options("openxlsx.paperSize" = 9)}} ## A4 #' \item{\code{options("openxlsx.orientation" = "portrait")}} ## page orientation #' } #' See the Formatting vignette for examples. #' #' #' #' #' Additional options #' #' \itemize{ #' \item{\code{options("openxlsx.compressionLevel" = "9")}} ## set zip compression level, default is "1". #' } #' NULLopenxlsx/R/borderFunctions.R0000644000176200001440000003616613560564727015657 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("3" %in% cc | "comma" %in% cc){ colStyle$numFmt <- list("numFmtId" = "3") }else if("numeric" %in% cc & !grepl("[^0\\.,#\\$\\* %]", getOption("openxlsx.numFmt", "GENERAL")) ){ colStyle$numFmt <- list("numFmtId" = 9999, "formatCode" = getOption("openxlsx.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.R0000644000176200001440000000264413560564727016022 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.md0000644000176200001440000000106113572442770013241 0ustar liggesusers# openxlsx 4.1.4 * Use `zip::zipr()` instead of zip::zip(). * Keep correct visibility option for loadWorkbook. [#12](https://github.com/ycphs/openxlsx/issues/12]) * Added getCellRefs as function. [#7](https://github.com/ycphs/openxlsx/issues/7) * update to rogygen2 7.0.0 * Added parameter for customizing na.strings * Add space surrounding "wrapText" [#17](https://github.com/ycphs/openxlsx/issues/17) * Corrected Percentage, Accounting, Comma, Currency class on column level # openxlsx 4.1.3 * Added a `NEWS.md` file to track changes to the package. openxlsx/MD50000644000176200001440000002304313572447172012460 0ustar liggesusersbae417b1b5a0780087979db493189835 *DESCRIPTION 7b9a5ad7d625435b6cf87d0c7c15898d *LICENSE 84f9b79363f652d318be51a675a292fe *NAMESPACE da1495a4884048005c8d6980dfa601a0 *NEWS 60e0b8fe9af4ad7bd764edc45180adf6 *NEWS.md 5b4a40b8bf9ebf42e9f3974ac4457f19 *R/CommentClass.R 14b0922b0d4893fa23bb5f6cc717a8b3 *R/HyperlinkClass.R ae5a9f998854f6bbb2b38ae5ab67642d *R/RcppExports.R be56daaf0bb036f7f4f4422b346ffbf4 *R/StyleClass.R 500b5eb855b0ef83c2b8400ed4e3d6a9 *R/WorkbookClass.R 01289240c2f1e30386e265a555e8df34 *R/baseXML.R 88de36c682330635c56e7a6aaeba3f18 *R/borderFunctions.R 9f2e60b594fab29a3446754cbe07abe7 *R/chartsheet_class.R 7f3fa7bae0e0cfc78ca96eaacc984bcd *R/class_definitions.R 860ac898f3fa948584681862d87e86be *R/conditional_formatting.R f51869f3fe0e178f97d5735b06fc43a0 *R/helperFunctions.R 74fc7385ea72d8b577e2af72d46ef32c *R/loadWorkbook.R cff10092fc0656de7f4f7f79eff6fdce *R/onUnload.R be737edfdc3d4e0dc7cfedc5c2964a65 *R/openXL.R e5a1a230167c442b518c4d023e1f18f9 *R/openxlsx.R 905e4dc428b75e5913427b8fa04e6709 *R/openxlsxCoerce.R b0fc7e5f6d6b734a9df0cd5e63aa0aae *R/readWorkbook.R 284a6dfe41085c850d6c2f9e2f08e24a *R/sheet_data_class.R 2c8da1f899c2afecc3095e631ff7af17 *R/workbook_column_widths.R a82824ced43fe90889c4a73de4f032d2 *R/workbook_read_workbook.R 8aa96cb685a48a28d4559293d8dfc9e8 *R/workbook_write_data.R ae499e877e36d70f18643681b00170c2 *R/worksheet_class.R 655754c07ac6d68a9eaf4e39dc2f6a40 *R/wrappers.R 996c3471285a24206a1c05abfc27e940 *R/writeData.R a51106040209e2ad4f01dd9daf72091d *R/writeDataTable.R 1e71bb5a249fc014e6cfedace530f1f3 *R/writexlsx.R d985195ca75bc973ae3c1458285418e6 *README.md b4c4bb6a182d42f50d64b649b4efced5 *build/vignette.rds 3a1a595d34508175908c44a61c9999b1 *inst/doc/Introduction.Rnw e923e981260be9c41552ca8bcf28bce5 *inst/doc/Introduction.pdf 65616841c4b21b952b637c73ca724153 *inst/doc/formatting.Rnw 4d67c6ca5511b32cf1316b6b48570216 *inst/doc/formatting.pdf a661a77cb80f4259f448e3ce20c65259 *inst/extdata/build_font_size_lookup.R b04e6c38e085e7cda15632119b0f9826 *inst/extdata/conditional_formatting_testing.R 36a7feeb6214d7e79ac8b89df3c45df0 *inst/extdata/einstein.jpg fb9a2de7bc2ec82fe52394335d80050d *inst/extdata/loadExample.xlsx 492caea5ba46c694316447508e17fd6a *inst/extdata/load_xlsx_testing.R b880cccb0e6a0573c9107453505ee04a *inst/extdata/namedRegions.xlsx 5c6ee667b971ee565af8d65a715176fe *inst/extdata/namedRegions2.xlsx 1febf7741950a8f461c80f0975895d1e *inst/extdata/readTest.xlsx 87c13e763f8e6097bbdc81159798622f *inst/extdata/read_failure_test.xlsx 109077aaee41632d07a09795906021d1 *inst/extdata/stack_style_testing.R 5f5a1ab2dd061d9e825594dc07c9dcd2 *man/addFilter.Rd 78b1a969947ca075ed615a8ee73b51f8 *man/addStyle.Rd 414a4a0894e08f9501da551e62a9ae66 *man/addWorksheet.Rd 9a966e34696cf5c4ff264921000eb6f7 *man/all.equal.Rd 8c855ef1d767ba41d4cc342c9e0abfcf *man/cloneWorksheet.Rd 21580969126e86ab026bfd4cb5f4f7f4 *man/conditionalFormat.Rd 516632d1cfcde9655bbe63c1ef791023 *man/conditionalFormatting.Rd be2f5a877330a76c56886030b21f71fa *man/convertFromExcelRef.Rd 9540612f0deaf69d79b58ddaf8c6fa94 *man/convertToDate.Rd bb9a00bdab1bd53a8f70beb8b99f025c *man/convertToDateTime.Rd e5e2cc866b60c24f0f6e2102a885570a *man/copyWorkbook.Rd 58f3a4d375311f6d49f56d43035c9b0b *man/createComment.Rd 8c118ccd04b676bc1677619587de2f51 *man/createNamedRegion.Rd f51b7f103dbee62b93d2978f74612c44 *man/createStyle.Rd c5dab939e3af02bf9b4b319f606d0b41 *man/createWorkbook.Rd 8f825ab2881a1c798ac6134418d808b1 *man/dataValidation.Rd fc17ef8902bc3e8ba094a406cc03d463 *man/deleteData.Rd 9935549e5ab436e1a14b39aa6eb5f586 *man/figures/tableoptions.pdf 57f30d30484a5402e9fce7539de1d3a6 *man/figures/tableoptions.png 3b1d016ae9091d16d34e1165b7e60f6d *man/freezePane.Rd 3ef5e44807e4b1bcc6e11ed59cd7c88e *man/getBaseFont.Rd fe6fb17d39ca8559eb35a7899c7e2cae *man/getCellRefs.Rd 259fecea95fe66f1feb959df3a094602 *man/getDateOrigin.Rd 800f52c193218a0688eb1c12c9273ae8 *man/getNamedRegions.Rd 8cc8e1695dc4fd80bca7e3e1949f7d08 *man/getSheetNames.Rd dd01de31948f6004b1b16f1f0daa7ecd *man/getStyles.Rd ccb3d12cbad5012bb2fbb519e47b3679 *man/getTables.Rd 57515ffd0a5c9155e4dd14993845822b *man/insertImage.Rd e692e7bcea0db961a6195689723fcd58 *man/insertPlot.Rd 8ff8b8e6032d06730162721cb9a73551 *man/int2col.Rd 2c6356afb92d933589403a4503e2c651 *man/loadWorkbook.Rd e3e46fb1b78d5378d4703eb334768081 *man/makeHyperlinkString.Rd 2436e9c7ae904e228c52af118fe393b8 *man/mergeCells.Rd 6e9a7b6b1dff1d5b7effa0f03e2b4107 *man/modifyBaseFont.Rd 7e325d8306c2cd6d7cbbb596d2a22100 *man/names.Rd e2c7c36ee3cf67edb4a76f1aa4ddb93e *man/openXL.Rd 61261c16c41fb314ffb9018379209726 *man/openxlsx.Rd 2ab99ee69df657a5e4a00f7d03db0abb *man/pageBreak.Rd ee6b23f5282d378b919f0ada9ce3e814 *man/pageSetup.Rd ba2523fc8e96d383bfa95b6016cfd3b4 *man/protectWorkbook.Rd 487c29c3d22684b866aa6f01c404b17f *man/protectWorksheet.Rd 0eb01600293545de31d0f8d2426600ab *man/read.xlsx.Rd 39f3d56b18f3abae2abfb262b32073f3 *man/readWorkbook.Rd 9613ad4116dd742ab09a460dec152578 *man/removeCellMerge.Rd 49de1a184dfed50a7f4985febcadcd95 *man/removeColWidths.Rd 3d2ebb73e98722cdb5ee1ef8775deae1 *man/removeComment.Rd 525316182b2561e2a3cd7fb3525f2e10 *man/removeFilter.Rd e000f8f3dfaceee263a649daa078768d *man/removeRowHeights.Rd 56244c074b9fb9720a2a53919a843c7c *man/removeTable.Rd d495a6066159cb44375610b2338a1095 *man/removeWorksheet.Rd 110889e1ec7e20b3524885faf77280e2 *man/renameWorksheet.Rd 60bc05b1447d1265b2a3b12cfd99ab53 *man/replaceStyle.Rd ede85c2116c05279a24670d3a3bd57d7 *man/saveWorkbook.Rd 34ff405c06a9e02ce77a239e6a6cba96 *man/setColWidths.Rd 04249d9786488c0c3dfe0dc9376754fc *man/setFooter.Rd f08727cbbb92e94e8ef5a3eb825a7d58 *man/setHeader.Rd 0e6f406d0a10827c360ca94d1904a1f7 *man/setHeaderFooter.Rd 0313da62656906695f37299b93889901 *man/setRowHeights.Rd deb5ad7e17a8fc5e9a8f3b3d2b4a4867 *man/sheetVisibility.Rd 07adbf1e3d91aa421205ca9a8d3de6f3 *man/sheetVisible.Rd bd134cc42de12df801d0b09ef67e8dda *man/sheets.Rd 22938b267493a2337addd04997b074ee *man/showGridLines.Rd 5419631f8f9324de9b855ccbebfbe173 *man/worksheetOrder.Rd 5bb6702e46ad156612efa1384524cc56 *man/write.xlsx.Rd ff2675656cb7561969f3c4d61466ffa6 *man/writeComment.Rd d3268e98dee6e4f736ad8b012adf2024 *man/writeData.Rd 6474b737003f9da43329bbbc20f632bc *man/writeDataTable.Rd 373e7c523da87b9aec183e0c65d9427b *man/writeFormula.Rd b0b3787de65788103c58ec233be0d8c6 *src/RcppExports.cpp 475b9faa7a758feb1aa995d9c07de2cc *src/helper_functions.cpp 4353eb631e7930ab73ad25ba44f6654b *src/load_workbook.cpp a73ff29f9d5f14a3edcba7be91bcf094 *src/openxlsx.h 2155a804f21601eabd5f8c4fcdbf0105 *src/openxlsx_init.c d0e55e0bf868a2eb67a0a5c3bdcbd512 *src/read_workbook.cpp c0c6145239614a8169a12e1e27e5673b *src/write_data.cpp 611ded4261c97754f9559f1ca0ee2926 *src/write_file.cpp 4d6151cde1d83615f447730f2ed5f436 *src/write_file_2.cpp 8a26b329213ec56d1792ca5cc1acc0c6 *tests/testthat.R 6e3d8da179589c686c35b402eb6510f6 *tests/testthat/test-Workbook_properties.R 3c765bc17bf59a7e432b2b7a6a6a4f28 *tests/testthat/test-border_parsing.R 119176f939094db029d6fd3ac3b37d1b *tests/testthat/test-date_time_conversion.R ac90485369d2c6c69748edf509017d56 *tests/testthat/test-deleting_tables.R 565aab000a31c376e75099bafacac19e *tests/testthat/test-encoding.R 517c177c758380b29df5d76cf3c0a725 *tests/testthat/test-fill_merged_cells.R 74b043fd69dcccfe38b5f4b5090095b1 *tests/testthat/test-freeze_pane.R 1f95218672c6dbaa049df735120aac40 *tests/testthat/test-getCellRefs.R 4454d274a56140ca48a247d99654402a *tests/testthat/test-load_read_file_read_equality.R 7bb26451c98c6b8e1c9ad41b7b235232 *tests/testthat/test-loading_workbook.R 3f6bc1515167e4d7b7d7580a5eec56d4 *tests/testthat/test-loading_workbook_tables.R 75d897f8644aeec610712771e213ab26 *tests/testthat/test-loading_workbook_unzipped.R b8969b574e2992db8428ce76d8db8c45 *tests/testthat/test-named_regions.R 11c5391e078ba687749a83801e8d1487 *tests/testthat/test-page_setup.R eb75f1b2c3f8b3382fa1612e019f6556 *tests/testthat/test-protect-workbook.R c8d47b277625d85d870fe23fa18c6837 *tests/testthat/test-protect-worksheet.R 548b1ad0c252474790457bf802282bb6 *tests/testthat/test-read_from_created_wb.R a1857850400de53c6581c1964976952b *tests/testthat/test-read_from_loaded_workbook.R b12e4b0b310b48f9e8fba47dd4a6ab71 *tests/testthat/test-read_sources.R 8302929923d044ee8c0416d18f8b25dd *tests/testthat/test-read_write_logicals.R 5a83322da31d36173645cdfdffdf7646 *tests/testthat/test-read_xlsx_correct_sheet.R f57bc86052b8ed514b43e52066ec6283 *tests/testthat/test-remove_worksheets.R 502f69598c4734d607afc6ec67197625 *tests/testthat/test-skip_empty_cols.R 7983e1655746043f1a75fbae6f84223c *tests/testthat/test-skip_empty_rows.R 3dc1a6c68c68c06ae8dde6ba6087b7f0 *tests/testthat/test-style_replacing.R 2400fc44ce0b1a271f0a201d72a962aa *tests/testthat/test-table_overlaps.R 2b4212c81e0d77afcd12df9830bb48b6 *tests/testthat/test-trying_to_break_openxlsx.R 395ddd5dc2ada178b42a03823be12eb8 *tests/testthat/test-v3_0_0_bugs.R 43ab2ae35857928c7eaa9cb68c08ab1b *tests/testthat/test-validate_table_name.R 2b53314a9875c24e36084dd1a14add61 *tests/testthat/test-worksheet_ordering.R 053b57f7e7cd6c6341403c68a2d0e96a *tests/testthat/test-worksheet_renaming.R 1fb7d94dcaba317841450b6a9cc393cf *tests/testthat/test-write_data_to_sheetData.R b09e6caa34de5ef15f53eef1c8ad5f6b *tests/testthat/test-write_data_to_sheetData_NAs.R fed52b325be5436cdd7eaa7880804342 *tests/testthat/test-write_read_equality.R 4189f318f2dd120db42aa0f11cec449b *tests/testthat/test-write_xlsx_vector_args.R eb0cc5f418ab8c96ec283ee1dab15d42 *tests/testthat/test-writing_posixct.R e8eaaeeaff4c9a3d3f06cf5bd0ece08c *tests/testthat/test-writing_sheet_data.R 3a1a595d34508175908c44a61c9999b1 *vignettes/Introduction.Rnw 65616841c4b21b952b637c73ca724153 *vignettes/formatting.Rnw e37f875bb932ea389ed1a8abe3405ccf *vignettes/tableStyles.PNG openxlsx/inst/0000755000176200001440000000000013572443201013110 5ustar liggesusersopenxlsx/inst/doc/0000755000176200001440000000000013572443201013655 5ustar liggesusersopenxlsx/inst/doc/formatting.Rnw0000644000176200001440000003001713560564727016536 0ustar liggesusers \documentclass[11pt]{article} \usepackage{graphicx, verbatim} % \VignetteEngine{knitr::knitr} % \VignetteIndexEntry{Formatting Examples} % \VignetteDepends{openxlsx} % \VignetteKeyword{excel} % \VignetteKeyword{xlsx} % \VignetteKeyword{spreadsheet} \usepackage{geometry} \geometry{ a4paper, total={210mm,297mm}, left=15mm, right=15mm, top=20mm, bottom=20mm, } \begin{document} \title{Examples} \author{Alexander Walker\\ \texttt{Alexander.Walker1989@gmail.com}} \maketitle \section{Formatting with writeData and writeDataTable} \begin{verbatim} ## 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 \end{verbatim} \noindent The 'tableStyle' argument in writeDataTable can be any ofthe predefined tableStyles in Excel. \begin{center} \includegraphics[width=14cm]{tableStyles} \end{center} \newpage \section{Date Formatting} \begin{verbatim} # 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) \end{verbatim} \newpage \section{DateTime Formatting} \begin{verbatim} 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") \end{verbatim} \newpage \section{Conditional Formatting} \begin{verbatim} 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) \end{verbatim} \newpage \section{Numeric Formatting} \begin{verbatim} 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]*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) 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) } } \end{verbatim} \end{document} openxlsx/inst/doc/Introduction.pdf0000644000176200001440000023772313572443200017046 0ustar liggesusers%PDF-1.5 % 3 0 obj << /Length 231 /Filter /FlateDecode >> stream xuJ@EZv]vU;a"&q~Pp=Xx?yS{@ <ϐ`Jqq>aKy:x c5fE'Nפ]svpH|DrŶN~\l.bta.~Ԝԫ,ISX3)I# f:ie"y0>AJX endstream endobj 12 0 obj << /Length 1160 /Filter /FlateDecode >> stream xڭWn6}W>$Eb4q)ܠmHJKJu1S' dI3g ghg8_.u]7coA d2?+v]/Z@k۟W{b.A lo &#^wf8f'Lѵм{ 'ꁌ}B__y0kWYwLOw}u2>̲6 ૧D%Bn gxV >Bʕˈey̔&VSqB] 1L`bUVoneoI tĊV;4p}Pݎz U;G60G.b;5<!vjZ48USf=6Vg-u}#HY_yAOH}P_d0cUD*RV%ZsD0ϵ)ܫUEY1)tAq6".W< 3օ¨j/ƈޓhW|r:=o#B=8=PbC%9=< >MrA%vMy\b/ժt]$ : SBN4]mˈʩL'.t*a1zl ȧzܝ"[,fYha|68.%Bi'O7!K%dukν Gg)ͽ΃w.^{Er^ht{ko1ۑeFn{ker9fչPC\wh.]X+p"kHxzUg ƩvǖPiXޭ6MGvU"jKד(EEtL {YEsYJ*Dz(* 42`ʖ ŀ",`p#˴%.NyMHrot]c!FSkZ-=R0uɎ(ͪ3bTd<cމg:]7Y1jg-8 %]bsi:$pNJoSގ&48hqNaՙv;EQ~?8O ?Hj<& endstream endobj 17 0 obj << /Length 494 /Filter /FlateDecode >> stream xڝTMo0 WWP2eE;t)vZU+&-+v0$dl|\%ۇ8>bNv3D^~ruLwP$SÛ x2I3Qqљ_͒~o$?#?h9ȹ~$󺶆 j&J _FOL+Za5qVnY8SirS8ǐ햿"VQpxRs7F18{ݺ _ ~S@٘|ݽ{=<֬(3!([=\^rjD?(/3yJMϧlSR &Qb rڌߚK>e~R*yl%,UO{!2!%BNVp4 7R??Z@ `nOfPFᣧ-/3c9rt U uݟd@/#o ЊOo4x endstream endobj 20 0 obj << /Length 1105 /Filter /FlateDecode >> stream xWKs8WP epÐeRS S9A؍QX$0~$K6= =~w{uu>2SÌWJ8f)Sƺfb5.c%SM>Nn4V`1Vv=oUԍ`-3-T0.u8r[=W;ϑ lw^x;MWYc0" l"0nޠ7?q.. jT}5W_aP*\ly5N)dX [(e)(.!"80@)Qَ> stream xWo0@tMj=lRiH;t= Xq>$NISl Љ ̤,;{x{N4f[$qƤdL須52 Bu,s1|T21{lp~_@uLKD%eTCD$}+40"dv.mkRT/ASk+ErƘ:nrumdߌl75hf\DLQ9"Y,w+yR3Sqc2wcݭT۔,,CE؍ިƝ0ȎTfr9C| ,ȵ5F;R_WOK)o ہ)owv?Ӟf°G'Ij;AXt<ϐ'/n[+VyX(dC%>mHmoDs$4xIRkk2L'4?L?ʻ?fU!&9/NѮ_u{>Cl+Q|2 endstream endobj 26 0 obj << /Length 744 /Filter /FlateDecode >> stream xWr0+E2 ۝ҙtd6LiDCl, ~l+ֽѠ+Ob}_[b` lS (96zi5~u[?G# [Bry|) . z *Kf f>$M!^6Z_*~DBiztkuk Lb29.G O2,qS~/C]k=ASR ݕbK > "*꣚چ|$!{fNw=_h @{NEI9S"$YjN{ ZTe We O8 VrIaq+x Vt(`\JɂH58E"F8)$I\JDWNsgo*sN  o1|B[}Iy1Z}`]X2N a#GE*wg?`Ϣ GHmy:"0)p]^&"p"uv0,xx7 !uE}̣٤'b%\v=K @US(by7' Yud d5f`wbNK(kB/, }ٵaKYL;G*{֫ڰ_mn{r8]H.Wn! endstream endobj 29 0 obj << /Length 374 /Filter /FlateDecode >> stream xڽT]K0}߯ˆk ]NłYW\?h}6Q {ι=$V6Ax[0'8sH|4fds(:^P*iXčเÓFZ؆t*Z5[*nE*/B\"L ,g*M4CX"}&2)YV_,R6]cU]R}HE\M5i"X۵Q4a1]s=t$ǿj-q`_zu9F3lq0ra7e\^^$M ny~Y%FYA$VhԢvc\yO y endstream endobj 33 0 obj << /Length 1212 /Filter /FlateDecode >> stream xڥWko6_aa#1-ӆԋPR+ Ze"QTlk"Q}{Hٽg>^au5^=g &_jӛs{*}Ñ`9O9e[D<`Q6zR8{ı{#w f1Q>3g;`/ (g>^Nc=w TRo)(ń|$i9|,:R" kB^@k;5Oh3F0V;~J5:-!%Na"xSg7k27Ӹn9cl!e@/ ' ɘu=hGзڟߵy?ψ?o3G~QJ(UMb"H$<1Oh`/^ZFkTlN1zB~iEIXNӺˁXq@(ضx#\6V;`H3q9څ乎7M%*5ŦB0L0=olJjAie@e¨RSipd&"^it"5 1ɩoQr:ΑkõtPsYڷc.awN'V*>C^'q}{Ǻn[O &gb  0L"'mJO,JOǔC5A%sS槡Iqb@~}SLe&~rOuannp`rIVQۑKF%"L.K&{ov օZvn [auѴ K=Br %ŁGŭ0yR(K&pM;?Є\JgH+oS[OwEƸJu[WRnYmzмفVVAU C4@)QgUQ> Γe`r|R+P@)zld濦Ziz4Q>vFxx[MD?YONmQXb=ZJo;'|C l˖mN9H_"NKe*\g=yC")J)4S*?W u=Lp4lokSVW endstream endobj 36 0 obj << /Length 1337 /Filter /FlateDecode >> stream xڕW[o6~ϯ0H6[d l~pXAb4<-3EbHDINh\s?r:Q|$MΉfR oR\$b6h-Kr 5n뻅q`֜fUVnA[t%T /y_+{1L9aZ[7.s ֕ iH H>ȔTtPmih\|NxYBJ6岌X9n^-9G߅ȅ [Kn˹JVXV~f+e-~$P͓C 5Fo6b;YV^ McظuKyNK(H=]҆a2җPBRuj2+\UnYg#HɛA29kX:v$`@{H_BÜƦXAA3c٢jxybdRNM^:[f,m4\M㆏zt+E mk{eH68U3b.X%`9r.71mZj  0 %CRZkLBX%f ݊UԭFr \n ޚz<1Qmyޱ /(sia@d-zQ ER܋A[I:ihs+pjW ) bՏr>?=±Xy> endstream endobj 39 0 obj << /Length 1188 /Filter /FlateDecode >> stream xڵX[8}_s!܆a;ɢvvz=v$nWWAs!tS /Xt✲l`1,5DPL> \oZar|FFR o杖&-7E bP-RnØd^(npėuAI8^rc?:Ģy:Y :U3`?~OX/lXeh\N:?ޞ2>9b&hav*6GfPfv&܁Orn3 {D!%gP S X͒!Xvې%r,NXO@y:(gaVP*UzJ^ M ʰnS`Ӏ[ʧVM~7Vg4ؚtvˠ%.N~uYyE_`㾸87J>84XSGw+DbK}*~~nMIrpJ/]G,w2AJ:9zq}:m\n(9}Bl60Q:DӖmÚb4-0DHMu0|)I.|Jh4+L`bU!vS r96LɸtZ5Rd!\+&F>R7U+#P5;xmzۿ+Flg0/ZCݵf^^\ι/ 3$nLbVT'P4(} u.Ce^c ̒IN53[SpTDvQc;e8쮜Pr@+}䭎ܨ5\|[ݭkEE04TkG@f+3_,eX#;<<8AO۷Bw=u0Ό&gG endstream endobj 42 0 obj << /Length 656 /Filter /FlateDecode >> stream xڅT[O0}WdABCk\[*$@*, igv@=$}9žS:s$ݙ\DܟNZ8q%3^8i}Od(B{?h> stream xڍPրnݝ i  ABpwww  Kpp9sfU[TAkjUԙEMARv.,lqE1v' 5_9lo'/ q'M&ty3Tȹ9<ll66; $n`s" @D-nhtfv~~^? 'tپe4  A'h ufw(LpX@ '79J@[_ Q4,Q[@7 djgre*@v1V/?t:vT:"Y&o%y6n6_OGIh m]]vCmC6? -focu. h1 s迷lRwm̬緻Sz[M)igfoqpNN@O$x-9ٻޚX;!q<V?D!? `/UbJCVٿRzөCo&7C|V-![ԏB.G--Ϳ-?Vݿ-fK`uGvo#no/Ҝo9 <\Ʒmautm X]֋ _O2surz{^\/@ 3{!V!բD̻BԻکKNhU_֝nEGz1V%n>,xÇ&=<'M!-N~? A$fh %Gʇu/Q7PsېЉuY$!a4Ez {7k a)J(%y{8oÐD%1PV5r]^h}Jߎ͂E>@mpu Y4/MǦ[ ?ERrV'db+BهV)TH&Kf>!W-: 1r^YD)!qƞYƩ2>u%ǂrD0U@qEp>ARη` &=[ܑTUHT;}S[J,tZ3gJ{{͜`DE.m1{JTJ;c=-=7L7M8ުl W+:-+Z KL.E75vw!0Ozi2L$,ݖ)p Uo#C[؜`I1h-sRJH)'ɳ-s.rtG$}nyb*\H\1Ԓ^Y؅lז 5CԆzz$>fx!5#AaJa~viIG'(l/ (N0 0~˫'!CFiX>i Hz!( }T3@/iʅ_Jr8J$ Y⯋V]|]7d<k맍"60w2owopbS-%iI1WTi/˧84T67i/(ױ$?3hxaWfZJ88-Þٴ!įwnPsӨ&f8 iV/O;&N qWy;uÏ~Ͳ1( oD`[ U"HV.b%7"p%y4O` 3U Cj W\Kb6a>=60T\z@Xt|M{ߖA<-HBz:O+QBdݠg|}*<?a>9`W)l7y9x\$mlYg✡N6~k i}e C8.q0mrBSТ޷!q!H64 GGr&!ߘ4Isx~N-o@gj±teC*/{2(HOGͽ dcX8=;+Vz$*\ :Ɵ\)qjzTkdO\*XI]2`Iߐ}_^wj8T$\X^jḧm@cYؽD[R%m{scFSK8+fɯݟLӈ\uTk3-B PU.0#rICȦ$yyWzq%)QqJ'%kqЅ- KWY1׳řr l&ɈxG{ bl)P&3=KwoQt3AJ7ߌqtuHg_~Y2^WV%=lQc3$`>'yuK,:?y2?@Fx܁MxTan^zDF lJGj?xG\'L'NW_ădE/ `5PO|]LRJ;)xw"C]y*//}LI{Y$ˆ9s婒 L)3}xmjf5e=% ߩ,ƴg:Y\K=ps'Sn/~ǕQ/0"kIP$ybSlª˿᪜YUޛ~Ih"T[\͞tᰔLaPjS~KK%@3eVf6sxBO(óZS׌bv8gE_xFkX nʜ̢O1'?t= u2bԛp56{)8'k M>EhG(G)0V+N,,XD Ɠ1&Ǎ+[bG.7^AxkIjiN\Ǹ)?@ܴz*Ð?~4:U CF'i+Z-Ѐ$اM_Ή6^fh X XtWTֈpKS%j G| Xl"HTr^}D ^IEiJ8k/P2L*9l@sǤF/\ ]YPejMՈUVJ=i)Hev"|SH +_^\Vޞ 1{}5Ƚ b9Fv{6=Do]&MF?Emר=/U[#ͳ{nJϱd ,HU%>\:`TBM΂tHт3ݍԚHXV~J i݂tI %9oQ;Bj ΒO11xݦo[' XJTmwmc }YȞea߹ѧ>z[_v# [v;$j{ɚiPɳo: Re4eJE;1fm81r61~4n_;D. y9,rq'rs x+CQBmƒ>JKI]NaՓ[gAbR))y=hQG6ظ 89\?:a?-5-W `Np}"!Kh bm u ;ւG7.:@cW J/=V[\$K-1.9R{u74gWzrs[6? m1[)IZȢ"ITiTU+fcD 狑)DeT"KjMmViN}ÏIjyވBFs W2)rya|]`+i@@7S?(tQO2u~y,$'8H>O n9F;(z%olQgHկ?t@^{X3+67jT)3,I0:#C7>Wlmu2ڈeOaC{1e#w*~6ks: b24z_XBM#˿yJ!Y;B >XXb`ʮ|4vbB^%!q8Kzb,} y()EU(4R Kؚ`!hA[Qm*8.y3^w+˦[{ ^?-p1qj|lS&4eKMZ}=k:ȷl僚I],JS/OrH_^aq~3?wś k3m辫#g>|mTglg-CuӅldlE[ED)S~GiP0n臦 'bnG21Q^8?FN5JQ~H5szr-@u؟^צ;xADeN'JYW . 5uh,'>/*w_=niOr-CyIW<J3e//KO,Mn x^ŒIuNob[t$}!j1یllTKx^! 9Ҟn)Q㮩OGygHVD Z #.҉Ŋkd} 3,KSۆf,R5ZsӶSLͱsr^ny) C\AFKq@O NJQpAV\YKQ[ ӁtU}LIbA d/> E}^l(HCsLmZ69'93d*?qX-lE:d&=lԄ{_J)՜5(h&KuxɽetΣwp;a}Y~?lRET"%5%_iږw+ Z BQ>*dӼן@LejfG8K=XsmΞӨQ@y&(r%w׫U ,"p?^zӂ2\*!ɦNQ3`M\PJj+ob/-1uCҽ%{r\ALjnUYOzW0TCͦBdo> #Sh8ەXǁF/ԌjDmG8Xa\b/M,`: $䡵Ks.mY,61Zq3Q?9Q<9q5~3_5>0I7;s *%}KtQaH D-'M867n!Sخ(<'#)Y3:>Dcg۱Sq5;roP&m"T<26 YƔ.'e}G'\Onk T?2VI'ܝb+Hiɑ<YX (omˀ~(״M@cv+$hbvV иlpHd^W "|h&i{[+Zȡ`<~cq31Ej"(eDD{YLA|[ Z(5bɮFm÷aIΖ՜/ۙYd䙆ȅN:wS'ecu(G){ش!Du!eBZtn{ɨ"*>/>"lDz,B`4xdy/F]("Ddx<*FۜV+l7\n\8wU+0apRbM@ief&;ko|q6 )mGl_ we ;=*C}ݵ<{?N{: Q _`n:}dBɔQ%uY(܊Bς0eCLS탾3vV}y-4eXw?)b7/{*a [" Cf+fb!i z ^\KPxtŏaߌkq1swX"1[њtk w՜mj4Cûz|CPDoAN_}̟/,Y>]8(4q$V-* ]ʹӘՆ }#%[_X%dΞ łOz_=_U:Sݒ gLy xqOop z64HmJlPA$P0j_ĠӸe~uyyeVA3*Lr)n+r);bP Ы?P%32e8RC15*ػJM?W(DfXQ_~MW8gSH7fv+iRM׍l\"/ H1VC^G%A# P`$X > rJ L$=4otp``i+駊'L4d%Lw= ,|gl=#{j̡,s3\me9a@9!lH1">񥈢usIxo|W9v"c iO=EY0q!yS\A)` Cɇ 1Y ]Ū+mA /S~w؃UyW oc)11$dUD=y!:z>}z [D^=}y* JwwXc%? ~MLRc"jZIT 7#n)kN՞eH瞾" pyTP~#NtboS8Dǡ'^LvʄG;6r$S2>GPfbY28}Ie YF #4 VJ|嚧] q*%ďFٽ橠 ߛE)CJ}tH}g &AgOW+\-crdq{?L+n+?Q^W\<|/%\6ȱ[^5!ĐhF0D # F#/ksέaaR2e> -dѤEllsY(KKWؒ~++U66nR&O!uvh=eb%2e0h slQ^ 1~KJiuνu{&.'=Iʓwۻ8>6t~аW 5 #ד@D?Z![?zO8tuwi1^n r֗ţ? ~q$oElZxYy8C~^  s.3_/${;3=ǽv[W$RJL\,.rI=l ^v.SR2,U ~<}><\K|P!=mt3^kd"|QiqW\7 ,$#i- UAm0~EdVVh \D[aX'_+߸x:K.:C1e7F$`$GBtSϨNMD;V)ӭʯ Tnj|Ek|K˜EՏMw &ܟm#_\_DIĵ't ϙD:q6A;? :B{&,UwXF["z"vVėPtd̦eͨ7ù;Rale[-0&"gМy7hȕԄmOvHzG ofѰZC:M|bS3fRr F{w~ Tlxg)k\8+),w9Jۄ܇"~[F3.&q I[P{ ^_N3A]U-),i!WyGpR:zr)/2g꽋ޣ_W-mV~ghΞ Kfxl4x٥~mrf3QȆx۵[H_9rofA`aīae}(^biGscC{$,ϙ8ձLfTd&Ie%R]@q洍O]x5uĝVك 圔(sMnDF1E_Z^Qw'0W&Me   q2u-a[CD2Ɖ$B)\_L|1AS[9GTTuپ*'$Uco:Wer'Tqڟ6_1S5g3~9^5{RZT;ɐhqF!/Үvs-u4 O"'d26}h R`ko|E~6k`$>ַ tw)0Eeۑ(@ܜ1"aR"{z/$pͽ?Y浄aڊ(g@`F[ߞsDU}7|5J8,ib <JdWʹ; `i+X d2G79pdqV,hnAݮ>ɏZu?rj`Bt˸@@|KZy:v]i5.,Q\,g15lOmUŲ49KtjT4DEEP ˷ckk.-Q?Kͥ-8pp%qW(ʝP9~i^=);`>'= o3^ D\/z}燡pʳf_оVB%,g fau a0tcE#P@ Mcg ׃?b\U(ЀɷNsӥ+Od~yK )k\kXhV$(#M ٝklyԜJŐ|_M1J="g>_M-Sc O  m⯵Qe=uYc⪾bzV sA&~r_n6CVͷmx *\wB%0TX=zVeNܖ3 Q!MTBC3gཞM'KXv|/;+V9I0;rbeFJ~s6l@ᣥ~cz4P~W-Bi%'Y.P\8,)tf?)L|:y5&^˾gAFŤ*u]TEKǴ}I h.M!Q بF-O^SYDpkLai ~%bMϵ_eC6j9IWm,_u.ߤ2U&X;'[_Ize8CjGLs`_B; )Nc 8IŖ:9:vk]Bg9- /]H &/V:="qf)'5_/3 g1VON=#_^؃Rpym&ߴJx>=k22Qkuͅ?(iPghƣĀojn8R(WF(.]{5 }=:ʢc?h,?)_W KPv\&!]g.pf\Y/;1rA;u䘫sJr*b6/MđGj& gd'3((wlUw٪Q1ai2&LR۵:7VܦI(& r7!*,pY],u9!˺Lq\#^ tʥPD޿ `6Vs:؅ wq2/FDcWyUbae7Cp@h> o\BC5(!VL׶p12έ6jR;eov/5S!rLk ~ @l w[~A8Aob;!Otnjؓ{[qinQ ~[Ɨm;HZʠ1LRwu,-?cq y=թ\-V~}!RssYc!vCʔ@VI}>{=S@lNoߋZ$'m-LCUJ_ZLǫŋ݈ALn4Y t7݁X0IzE#6dv>pGGFJa] di -M tPjc&оp}1Ckc}L > 03u$ endstream endobj 51 0 obj << /Length1 1541 /Length2 8391 /Length3 0 /Length 9418 /Filter /FlateDecode >> stream xڍTZ6LIJw CwKwJ t#!)]ҡ4H_֬5sgaa瑳w+< ^>q'ǏbA@,F`' & p SA@ { l\1f8A<>6p0B0χ /=xv +@'9 P0@[I@=mm Pۇ?'(lMqGxzB)y'0{7WW0 |8u p~r ^`UſC c`@ODL}휀8AAn ; @Ao C[#0/p/A{ ?+yٻ~__˻x$u?n^" tb{=? lp#qwMQ /v^p[G/g$k.}xFXr۽0pTe>_ɽ&XxKv6`EKnuUV+8YX܇>Z,[@Pfj,^x:>ʾ#gtqn&yb cCMf}`DGbrO:"WKڏ( 0]㏻ހ߳Ҕknl@IRod}.w.FD/S=<}g'L)m$3jVZE#j\mgF]Ω4 bӸ&N{!M]uCy#S[.?mt铡qq>q2ZZϠM}TA4p/xyd!hTQ0͡PnciYE}ֽBJ''֙yO^ijJԂʀkd~Bs?U{f+̟9_U*e܏٭;]?+/$fty\R~(HxޫNO4eh/=XL RW#7>e,lE}$_[)PVȒ_ۮ}8`9%s{ɪ}R h;&PL*4av#$tU=&!iGn&c?8&τ.G"=(,e\("Iv^ϒo4]!%Q.ei̼(;I_<]@9uz&e&9av[,;FQ0CCvјH_HkS~2{q*#)B{*PAZǕNkn { \* =թ *j'(fѱzl j z3eXW}!߶[-?T1{? J'qP|;j~옃`)݈y C7iOncε2Wh(]>-2`аܠv{ 2 K:`K2sd>?a81Du+[Y*VF3n:x=s.tOy V $B&Zʳ7U]Q"Bj-q}MIn&`SmŕuQskl %[80zVz,:O85ےedBe rYPʀϝ8L-sO1"͹%voZiXL$mj X 1eA{y,$ ~tLiv}f[bѱejҕ`9#xcy#hKJЕwGzDdMdEJe:Yh!hVfO^ͭ5й{ ĝ~@vJjJ":<ƚ @y=ym%@.,@c=Oi8BjU3BoZ,%uX.  52ky9KTͫn- p̑2ZeTZRW0ȳ~ae:Vve-NV.34}I.z\lèp >.ЅQQS(>COiGx\lj*/ RTcVՀܱ&9RA_a+iןHcmjQ|L ^_lֱ͏"/ov(IӁ,m6U@Jїdl?\eHΫ,뱻`O2QZxpQ]cLc{Y.%n^KIdm,rdi7&j9Ev:FC<թ1wP [kj/ [3D %߱UkBl߯‰,F &gܽOph#JxbIaM&  ^D,y(Ҹ֋hj)IP+vtӨbdEwﻯ+nV9ۢ$lzvI+*<0>B4T޻>A\qB@\GlVl4S2F>|R\+hs@r#,CLKZV~AR?64qrD}vqN%"e`_Yn ˡds7JeKZ3{MH}Z-"U\zi3f𢌛A_}&࠶9Es&/t~b*!>?9RV[/l+;:`)ؼO4+eue=f6h`)SU7Z`MbYH>pTϰnhPCWaMԖx!f)=10);ck1EmP{(̫@ Քʥ);lO onLzc-qBgYGT:U1F.%v_Ԏ'5yՆZ,1a3c/)8Ʉ ISҶ|!TAn.$M..*N$izvfh-E񦭴naS0lԨͮ%NӞ uVaƆ Q/%fI/l ;..g _/: +;qEJ+v7Z%;qt2^kz;Vؗ xR wR [{]ȢPߧ'Yz[o L}9#*č2"~]Ch߆q6D&(SΐuAVP_/q#- ϸ|0N*iiODM;<רGnCe2-ޟ[-o^q&~Zeֱƈ;fk:܉4W~MeQ8Pe[t,QIϘ=If5<8}(1q\Dz4M:BN[b$q7s8ELD k.Oߥ ~9ȩ/׹`BWIyur^nW1WˏͭD DRR򶘅k L_1-RTj ;ܑ@c;? V"7R֘!$}|Wf l*A:CvyiJ?\!XL9'+!idR~6WځڗԖAQr8(o u$E1NM1&6)_Pݞ[N'RHau~(>U˖+0h+G/pJ?,^lKx҄PKJӾ?̤vSZT7Ist[@.Q:vƕ$XK؈!Q5Z]'*#?;e d甎wq0v-HhuZ3AmthVRlt_`ah.tˍT ϪsXGzcIs9ݝW!xrgV]aفlO^+ӧ,I߈Y] I)T "7g #<0\1䵩:<_KgǓ/Ee^pT;G}&_wf*jZ"}1S,,X;ej `zqTxC׶wrG38R 3fn2͗>UМ/P\Ft8&3vwQwjX'ucoV^~{^g"iT¢Z ցV뒼XEXزlƞږ7-mK0k91>/v9Hb6",I=&EET 9{Ujk lgg mT H:qNH\2[X/Y'Gs*:"]Ut&C(܌=r=F;Etj"K$Cte c?\}dvp(SҀǣrO/yV=ӳSwY6T1ۿUF2w;yCaڹ{&Xvꗫޜ49O[~O]~65E< ֑y˹l6,ɭ~mW c#knYDA>W(3h \Ch\4VpUb M5Xa5'[]P-Qa0G;-=cNyjߣNHNEh5Q9 .-2j07\2I:L\ tbUMf mv)M-M-B}O[N@f"@`{jέD=a} DZHBʠ |!7q}9p?GΊ ˜z/戨S5=*#UܡKLW*^1< {W]Q=zaOE{҅QPtmz9(;'J8u2ANHkTCwCUd'KB[*#ݼs`hL+!D}yСRэdyԾ`rfH UxLɛ(%_-TF Eٵ8% 3a_ ƑPȹLWӛ^ T@<5D͆51bʠjT%12+;d{v!oaʯܻc#UJT],Ҏ]މkD+*jE2S Yb=G8e#O!7(/_/3<~_WG0^5Iv`W5p ɵ\67qG85Ï%,+&Y[͒z'AGU֖5Sbuoocדi%'38ebnUh;n[+gTG4MQg T>JKGXI|j7h'Ja=nTT=3 WVՖt_0*tS ^QfOP^:Yi:M?Z8z%G)1UB7eG7qQ*T.@ReArW T)V!i`z鑼n֖@k\ uu]A*m|2 UBtΈ<gfg>%&ڍYR$Ab: }I[#>{l/H4ynH[kJ{`>m=p>׸䯶Hf?#0z-,Nu*zff =MT5ݰWcvbo(=r;cDpV_OT/uP᫤6M-5aQ۽zig#rFfUu,߭Yh6/yJlʹeplRxT16m4y[CEyȟ6dD%byBʵt2S#OP0~*:>w *n4F&FHbzrΦ.( '!8-,G?U'!`*/~D+ϮWj@gX!^xUu;{} ?bo9(LwBBSk9U9Z!H({dV~oVx&'6q!>~Þvw#V3nPWl1ʑ2 @l/{ JɇGNK$"B%oKU \1I_o0 HPvrS^]E:b`,-Ǟ4ӬV,>ּsu:)XV#fx;k8z8܍RWuV۝_ckk![r:cCH +s9KZތaD})i_> stream xڍT-whBp@# 4n5X< nCpKGf̽굺{ϮU=č( Q<@ 7*=6W^u;AEݞcn<'@@NPa Pt\Qe`d ` -!s7[-'K0$Dmܜ99===9]96lO-@ z TA:@hۂ]k9YyCAq}pXZ 5gO6_g[B`斖No0` v^*sy!VN`sg;7?˞% vm)AdA7WCACf; sq=>Y:s4V|@?uPh<D]s i"dKgf]vDNa~J%t%D{Ҁޚ4Asr u+ćC~ rvm? {NE<wAL[>yѰmj~%)h|Yb$7v,^X3WӸOT G1+ܮ]$t$W {DEkѴYŽ d.=&mU[ 0 1'<»W%ԉH9׼Z,\Oj|hB?s}Y5m R | 61DF+#+Bfr-6uA+߼f}"DcΪkD?/){ 9. .|c̝P놰*nbD#SX^eBv(G4C2{xX>2^GVD/Q߆$LgzrI,:A}6VKVtuC[ޭcBueV7ۧ_^?}͜l"n&c6g*FmgSD^SYd% 4o= Yp}}3iXI#57 e&ig#͙wV+S6%ޟN&M7!] fqSX?+"zU w|8y;a3-۾O2cp/ k[IKNI6t}=RTڵxz\}(Hm:b'1|Wɧ͚:7x RYR&XF 6Pi Ԃ!/օ9Y\`7f2i,v.'/]vhxK}pk!X6OYA|ؒ$ύY>%tD,c\Ap|G:UJgl AKdT5F߾-R)U^xu{LLD$Ì*$( (Ҙ\Z(pKع2 s2E+]BFiD O fͯf{_ "P&Թӫx$ک,ʋjl!bKî,}IPT5gKx+`w$ݮ399 OĘs2iuftՕ*k~ }U}6p$O!A4'Уd'n-ѕ2ZAn,8p/xNԎJC:+Y9;^I`?5]9!O -V~id̡LnSscȖt=R.)5~W#lo($!H[MPU㆘ ر(WPC{ʚ%7A"l9Xّ"NxaU@cO/;5(=PsBa?5fb[V6.LҲFz_I#gZ%j6W_w`='znVubIKylTd;́H'oRI{؝&g'xA;T:]F6b|]p={i6*/"2boW9s*)P }iANl6PMTBt/-7VNK5nP2R@ QۭjXT&O.S!hb!i e̽ONG'^mDr)V3έxH; *Ȉ:NEQ84>b}t:0Mi# F=W_OW368cpp8/OD,RHjXws&\D"Y;zMzU$f.B#|%o~>%stQQ]ɲ y뚊O0+eif&~" g׀SgX5:PIRU2=?ƺ]0j͇4_`1+jrHݱ>794"vg5R?+Qʹ4>FT]'zM+vIsBUƩ@Df#T&]H%)&X_x껹l) XM\iL54D ?R bxd|$al4m}s*'A*4p%:/oFXNI s 6μg1]㗥=ڰUmͲVC2feGxGf&~!!6(="o5WU39@tKq#K|MGeÌWK+Ʌ72+#x!aBq&8%!y`rO)o5>8wa4ZI2kw?J,::KyN-( e{]y3W|x.7tdDS =|RfDڻ_Dt;`ӌ9>'f2Ɩ{Aѭ! ,#wU*dyG$ V]ԏlEچHs!'إϋMbynSl=K_KCYq6xBu|)أ?N$F,elEZ#nOmp^Mhlb7&[F(¿ӯ?yk{a9SL i֑gRSs\k`tR~;~{yJ n8P%v$ h~v %"&M1$GN &">QO*VZ4tfDi'_@`WHb^fbbfo ^,7pZ]my( 5k^\uM-ikQzUXа8jCrФ\FNӻk QW0 Ratocf Y3Ѭo=\5m!wuLa"G禱$}$k_j.|xD%{+j5VvT0k<{V4RyL}]0[x!!/%洯L-|xZ6ٽ e>!۞[*K|<ؤneDgR'r?Nj%+BZJzd*m7cvqM:ESnNLeT_pYg7j!ͦK~DiUշd[=Tх~TJ|S86~L"yJ"~a= ':%29l,㕦 Ŋ.ܳ DZsDaq}֕jF|6~!µm L.4si!wWjÈ={_FR8lW3E^8IaTՓOU~5K0%Bްq_ M E]:G9KN* -e⥇w㬠fEK=P ^gH&T[>>@ 3lCú|;z᷌-FX|]h 7E{+ٯ)mǀ+]j}2L}&GɹwU N-okQZ HRqqPBnJ0'Զe^,)LY0#L#l.?cYj]٧rƿx'w+dߦs3 k t \fV`1^4Fc+tQ'HPSr$\șdEW`=+&&:;[R؅ľi'A6߉=B7zOf2 H`&V5Q1T|jqaCI 5@,! EVMryiV~6iJF`r)aA >Q,P\ CHKQ,Yr kE>c$0M?G:Ws*3T@7\Oh͜|<)¬~Y(^r8RɏC7PH#8*gܢ/hЯRh9n^GnPc%)ڴ]Ӷ( Nf+nEZc\cVQr]6} xg`.@MLL6,lCpR\oyL5%' jZHYEG/$,O:/kI/aYh=HG Zi}pˏ϶®Y0|,1]:g3%ǧf-p->&ᜡB5dQuU]I8?Lٟ>eD2< 㧈af_ܴWұhvkN"a˼d'~~fRX(c4sc#*c,Î ^3Z.W*\S5,ٜUЊ#g,ͤ3]r#vMF^E%\Qz$){@YN-c#۳_H-Hux$rF1iH{^'=13-# 20peb;QPu׬/srXf!Dl,Itb?۝\+(> stream xڍTT. RCIP)5CݝҝJwHJtt\[u֚s~{ - Xubag$y@ '+ȁFK qiF;8B`PH8AN6Ic ws_ @?@1(aP#ba_zS;t-b @N`MA6 )NNvll [GV03d P;\f_ [Xh?0s'WhP3qq"@ #Xfg`geܟٿ AA0[;CliEV'7'fj+d{ 67wH@hsrdu)KA$``#گIBGg0W53EَM wIhBfvp@>nv3dU^f0$_h 0o;; bx@hq>je(/3eӑa>qq qqx7  OA99 dz˟sb]A(e0?'_Uo I;v [Jvvz %l@7T$+ ζs=Nc8JCf'S?$W@`U#]`a8o֏c~%0_s rp dP3oeX0#=o9WGy8lrLok?lf6? dllN<6?lL_)`m~f*dUz])Fʲ5*4I9%cFåXn ) {ϽZwj-^wq[-hs_ jȞhn{{i[#4w;b]ʸ~RJt%Z+h6M4 *#$n|~4g*ۛirMNbb}"2 ;I򄳞#Y83 )鍶z#g }Y Ӫ6Tg[Ԭx¬Y3M2렦92ɫ8f?HDJg2vjDeʶ.R-9{xQ̵cgg ǚpPlL?r Fz1AߺKK4+ǜ@IsLuVi힄,(0:&aU0ihaO}cx+2nZF+.sKbkfrߡԨ #;w)'m!e2dhbsXBBZ7ݙV!\a3ĮfX˥ҮK-tz_'4Ԭ՞e*|p=SzлA?fbJÏWd3 ;^0]O~[y:@!2 /CgӁGɛ<=S,v"S1OgB0ey&\^$308aixUH8:e2nǺJE弗drjt$cqׄm{VH16AM:ڑU@+KIimW8BSN>{;\B!I-r )Q]OM(j:d,t8ynJd X ي1 z ,Ĩ#X-oj|eU^"Z鿛}H m$m.D5rTQ0{z`18ioP5ULN)>GKCB>c*6z:m΅>wghMݐ" 37ém]|U5,W}m ͖?`pU|(tܤ;*;2)BAfE G{:or4-Fw;0GNE>VF0`B|0PHA5yuM-' xx5)w{0 r˞cr(kR6_+<RFoLێѼTV? o2JҌS,JRp行UF/xP4WLJF8q\_jh0Tv#mR9BɎ׳Ȣ|#%D._BUp o~nu=2?q ? ov<['. hf6M#FO%]P!K9Szl7D7ϦR]$sxh]؅ecp!YDV#ڦL5U戄Mon1r>}D!.Zw}ȲfYMyd(\ 8dk6++\t^ND{OKY6U_\šy)iZ:9&"/q¹}YRoƎ 4i_H<^}UyQs8|ђi8)A;ȧ|tSlXY6˳a -|\\&҄}C,[P-vlE{eOLEȌ_O<1u X0P>o؁F6#A[S+jE)?B9~kih[,O2I#BWXk=")Nn=}e` [YwC?cLR].6.ʑ(d|_\~ԏNrqsRgBHg>`G"Mv JMj=By nBQh)⇊zsTZd4yi>|m1!-JU0we_ȨhqYU֘~$2P]t5q''($_=(+P殲OzL{u$2=5;6Fi6ӹ/λQdu1? ,P~|6LH6dxz.,^}! aYoS'&$אSMm A_rI0H,D? >yz>YRýKuy.r>;}NJ9w,gkW$fU"j!e~ . gasqd Ue z):3iA~ VtMA7:fE}E_#6& N]}\%OQkb*iޡ`ݧw<\bX TS<F2ld"-ƻ}}T } G8C|'L}>(*u( ]Ozʺ[F+֨AK_il˺:XB{C5Ayk:PLi՜❚5N0/bm C.̀;Κ il4}Rѥzk62p{y>T;?((U}t)?`W E= }F.(Wj@/o:[83 dcD򬔛`8 ]KƹM9A09r,;$UTQsE%VCL E$,Xu&@V2POPΧYdO _GE4O睯MV q @rM) [,7I bjvm$~dshqHoR;5X7=؄Z@&Ir^őNA%zW: HkQl [W#fo>Nzg'@[Cbﷅ˫!U %e'wxq~"eQkdhрwiS+(ZwFLS&nzA❥>Q%xfSfSvs4SO!#$Mj\͋ڤK6ޔRG!x:'4;a8o7Waپw ?ò *]8b4/q}+R_s᩟Td@jL;Yce~GsQF~H 0kHI+f ʜF{K((~7\qm-[ cJ2,xD鄛WxY J+{/mr͏1KsKK}@˰"zԍHVd [d?Cn{)0 '6|md>?NJRܭ͉|>S=?r Pmt>A_r?`Nݟ`-9zJ'"…1eLBy?"u8DBlwG}ԚIöQZ5i鷗H@ϒ@2z5-It#I`P@^ S|"]2rp5ϓ)w< [FpM)EIʨYGQAyEYd78rx%{ y<{Sʔ Q񹸟b![nF#&.Ճg!NuYh#鑰 ڻAu"sJYGvD¯?9HM9_ȍ,9Ii s}ڹCwbOu&;TP/I2paϻe"ƍ-x6E 0&%L;]ݹQh:ofGdDc-ٻ3:޳,w}>^[(Ĩ/͟ғ>ߚ$i8*`M'ȌgzQ,LBspHWN Ⱖ鵯pj fvTH>Z{pt[cm+j׊!Nbf􄢂5+Kuo|O6eeu]-FOP z~a| 䎜Xʎ VxlKˌ Begkqskfk;oDb~*-+0c/آf[H[퓕z/:; JYyza02o/Eԩ]lYX [*!eơ ů_kcKL~5? =8"xt48Mc>+PWd f6GY` D3LfF*;ۉkh 7W8{%.v ;rL~E vf}Ɵ*nIs$ILށW5ׁ̆Ƞ#s*P4ƍ [޽Lh '{%S3,>F?a/g=.H5_Ik]zO0F7=^;NK==V%G&h%h;M8Pt{a񉮚)Wr&]$}ťt} %;7Uϕ .C(ˍ&걺EŢj{5ڮ`jaM{+-G3##ٖ7[4OJŤK Л\?oxm(=ct؀8^qkc@!o`3`P2be ʹ}vXD-H ߊe"dvO& jel ;> stream xڌT6LI4# )1C ݍtHK7")ҠtH4R{_k<{wx߁楪H.T`ssh]@h]?,$A@W5jȻ8|Nvv:8 ^%VJ# vG9C@q{3(]AЈ@;9_.腭]]<<Ն:?Ay@ ESlW;S&ɍSڣOƳli$lvKmW+KVaЇi|,dȌ8'ϧ/.prǟ(Pcq^t"&A32I#NpΧ8f\;+H?pr8v 2#~mulA(Y[s/,=zA]`4}ݥ2r a򦂓{>rNřAxi9|b|Jw*QBoWcda59B~9x6zRsn y9.K}*^Ϗܗ9rʦOpbxd:upbaL}yzTY#u QPH ,RZǟKb, oq_7^cM[|{U3SIcё _7X)`wv2bX f)XkyBz*#Uj" mY#kjɕCd#Av G^\n^έJ#V1_!ql_GiuNV>D1=$ /*yU(guߐ4!؊[hc>`aCg6lNT߿ 9{6JfV)noڂ ?c_;֎C <TZ? 0}ĉ'\6LE9 ~DX8gY808%_?-rt*ϋĿFƖgLC0ϰa_’aUѪ Mio{o6T^v?z[(_(jʔ:氿#7KǀJ Q-x$VW9RkrFv<֚*e/Z(\) 6pƞ8lqcD-B[Rt{@k;&0]ӡ^-Ҥ馶"rԏA~~Schx5-74;7T?GUqN[a;SwSZ)D3.7$r}ds ʢB>#sFVyR!q\_nyEJE޻F J2yb 2„VRo_۰܆"_xŋAG{֛^1u^¡u!;Gĉ#[l1FװxvbK(ԔbŜ(l#ku i'6Vf9IȬY1p4ENe""{ޞ̋>ƉFΙZ.U&i.K>RT&6o񼚛$"1}^Yv5VP/0FcܖݺZdJ*{7e|  i9ɦ-Iq`6\:Ɓ:e"/PyV_ɓkV&F7@q'}kXEq}zڇ_A޳.n`Jtigm?9^ WwD}(_3$8LYa1z,!OҶYMgj;mz$̄KҌvj4CuڻkHezڧÙ]TJfFԺ1I;Z,͛&r۫ЄcSx_ilXE Ⱥ],g;h&]q۶={Gm2%vSi"G9ob<Ò$}O~ur:dn^#t.ۇ yTQoMʗ!o˨ߓJnJ!wץWBк:N_cZ뙮0bΕ[I[yimKN6j^wS#b4kǞi6.?"C'Vߣ##(h?Y(uO}Mۉ⫣O?!2(>g[=L8#Z$t!Ar'gTA$Oڛ۰P3|'zf9aRwErin*+?@YǗRatBdͰ:@ҒK1@ͯb(H=l2fu9.J&;=?p#Ӆ2PX2ٕkRyMܝU a-YYbKl˯VyJSJO-OOI ?cJ?/%I \hbDm;_r"KiZa! ."hk"#r,3rۦOtM\99g1}VK7U%x2 j̩oY0R) 3õ /Ńhw3}{NWljuntr(mN6e Co2jrqLM8lN$K ;?s)sC#~&(JƸοSu8Pa4aޭ^$djŌU?D;\YtL _ \kYġ$@ qZ2xU@,1T l2]>\߷$98jt+]Zg!b._ou=Ky R١49{ Ox4Y :UD{Os=W[HURl |=Kօ3U9w!Qg wE]{rQtI0%?M#2 M'~;»sdžEg*H1>}WYPB o#K}!Ü4d'_E8$`wO| h/%.9\4̸E*4h }:S2q[=N֍052XiqbPOIαWð4V!׈=(!33~mȧ@Qou_;9yL:;lުEԥ{d9E,rǙ|]kU=O{G PöG+mIJD~L`JA9!qUW!;ic,<ߔXD f`8-phCjVoW$aJ*u aJ~znfM;&VJ<}+xIKr| '\!#2}?p0Wie;h'ŏ$ ;o Pf3uD|glc _r:Bf'+$Y ctݜѽif$BpvgZc*o"QFnat NƾNE/;fJ D@3LmȤVC3njWbLoz(I9mmXYYm>%#@tɪ~ɡMd:e~J4HLR1QC!SeǥIbOq=uW5cAgǥf.k`'>b}YoI&vO hb}ybOWԇ7MO Ϫr7mʸ-9Q$%T)E^uN^ * E5^)nCܲ^^6Ycb-0 5/v,gm2m>y_\X8jy`{<\mD 3 AYfCi^{eڇ#w/zfNl`_PI$60K~ΌR D9` v"ni\E#O&X`d$l1&_#J 21$ wS\xg U-9b3N e{Ҙ{fK )-L,ǰ7Xd -C&vy6H:(>-lȠ޿E4ߑszpo3e@=]h=]t C̙l yn[|:jgL׌m>U{hEiME$: ],XRϫ .H.l3^6#tqx8#E@W]ZsS|z&IF'W$6]ut\Pzƕ&Iu>MNp9GLޞa7lV&jkɱHT,ɐ8fy]6\D{*[eEeF7?idHvQߣMt3ilYʘӧѮX>b-5BP6 墰F.7A}k}LE)qjU:& aȦJ;q6s5<b:%o,%ndfP7#P/kZщJ"z2pKtbzi9^wq7w8i9_#?lJAHH'Z:YUϦUs}BR))Z${rZCQ^dv8wfLR[FBVHL A/g͉^w,\|TaKy|| M>_7}C{LP8%噐 {v^}\0][_ژY-~XJ{MZGf܌1Iy#`{GN 8ެP5?QkĢmǂ3y[nz$R8~mePnIaE(Lr)8k7ї]{}I=,1g 0be?wa&v24[.S96?]*96N9Tfi4dr\pl*`DTsL2̴S9RM^i<uׂYF =-jeJ~b+ӷUua V-_D,<ᶦp-\sUokbeu8W+a;B}?0&i5"0ʋG|xOϭUkUXwޢ{ػMٓLLj|cC2Ka-t6ޅi#ć9oVNYGyep Ҥ$UA"Gk- зcT'Y-g=P󋌓.#BxbeeQpNJ MM4=~B[j:esaޣBٜY`Mx8ץIwrnT`Qe•kfr]r$X'<KVõ ;Z#Ҫ6L qeћA5KGoFv6~PDŨͺjl|%)HN}?{wVoU>Tvc*qsg)6u²UoV[5Oil`h)0#kɸ !$z~9@ܼ9h)@c̮zk̈́jToP|9ZB8@a[) E#Å5c/9O66`IY;ՙ$D-%ELn,Ĺ-;/}v_Mxg)t|Rv*هvh<'(Ccg*ٖAPJ+sNwq&K/ G1trgN9]UQOf҆ i70M4:xn|i[$5d!55] fcGjM grx@*ӰRd5#VM9 &1b=U*~WDK(6^crը2wܲ!WnI ٴ0t6ZhF7 S~&A&y4#+}l֧. Ί};?a,0S\Dp4%]:)f۲TM , cP.G"XZUX)V6"[OEK%[$'~?9G)|%%;6?]-%︭A'tǰ(DD&:̯*L^X]M}8'Q?(OvX:oh_YgNlAv^9۔^_3Lj(bG RvΜ TUp2Fؑp D}h1+0KeoYC"b,w݀ ]首૖ m;upStSy`JoJ&`$v%B\ j7AR̒H@'z{\eSNH;zɃN@v-KZ{ѤB37]('N8[dN<-8'E`-Jc9EK3a߾aMgcZ4Kf& Kҍ)p+ k]у ܯϸf݈  L럣߈Px":~Nc!UUv/w'H̘ˑcxc>@1Z' t#4"EZ" cgTZ'~r)oeCkZ/\f {U 8M?$.(E9c3w k{{ȜI?f3񜄣?q{;yb[T76>mYεei(`3Veu%5LbXͦm;jK$"9-i,8m(G-]ﴃg8;Lx[;B2wp]͒=|:PN-;55RyC[Y?w<ь| 1Aȩo[ܱ7/D@\eiУ 7Ef6!$77N-E@}y q~g$.cdW9,_\!<]ָ:v^Akp2LjO51H#;HBCJ#;vvd]F0x^z @LWsN8Foaaw;W߹g9>$ N,ǧ|1us7¡9Br,<m'‘!pEx?wCM=_Diڄ)J#+S!Yx9nY+UhG[#,8jD5 cYQ^16实gQpƺi:K`LYp^yG {i9z4A|||9YT4ڐI57y%Y sǷ)9LM6|Ycm6k֧ңcIϮ-`FUxBCנ\1KrOnɊ ^5$!Hjl`qA :*׷F|evש}봰S۷Un~9Ȫd 68fj ;7QߍN(qTE8'穪U5:{4ņo<ϝ|p-ERG2Y~V xНxJSbnn;c`- ldxоAܿs0KeqOSiO s2MH{.,+6}0)%k@>!hVҞ7Osq(ݣwf<cYcCJ&oEDuT17muo-}}b[vDt[,6US7)ؽkP) C^p`=5N9/W(ld}T=8=7ՃqI>4V?p_ nu{®;άz#K ]T@0@R K (ʐ&w\.rW-*A?Nj6֪JKQ}}]4FF:c+B7Ӳ_鯫&7j"5ވȦGU͗Dysë\9%d`;IbO2]c !*3lp+5c~ٯMǩ){kk.N-VA:hէ&SAPL:Cv(vlW%~'h}.#Sx0!|]@F}*C4q_?|[{i9EY9_ç]6d#R?QyɵϽաNj`i%<fqC:<~A?6iGC0. N^ rOax覉G>ouZ(0*-XGk5cbr RIyb7 R.Z1+=Lx!ԗƪ( d\;>ˏҀIq3Z(5`w,OC*iu#Phg?oP+|ˀ[;Ӊ_Ҩk i|ADTX~_]E(Qift.SLwf~gz&!Mw1g!GM{N/k\P@Ė@16m\秆zFjӸVYkBb}oGgeU:ftRo{Y,G;1v*fs ` ѓ)tv̅UfUSd"L1hFɾU<07\fdRs[bf9O>HoC3lFtyk7 4a4,bƆ\Y!Vdq#wQ/n 's.ص47v!rc)CT?\ZKĝi=Ŀf 29_ε$*,UD~=@U?,(o0h/ pPH4]1ԧL井WW7 `[VB^AkO&O] RgT3 gn|^P]C+w͜[:ڤ["N= 6[E~a jVX!\etGD1)u\7o7v6\"XC$O8Wwi)niTp&nutC]Rk:cȽb'ذ41.&z<_YtU79bCjs(Y,C%c&v S9_x ([Oik4B ^7ɟU1eS?\S`W9mڧ؇Y)ښxF=_MH !♬ 3[a[\F!5fvo>4wuGGG`VQ(=VAtNiז{ꤤ5{Wi:Qk%25חH4-C$od\jHl Nl&gl0/;2ySכ7#f\Ͷ 97*;-U~ BCؐQ>6/za'+FJ ض %tc3'֝1%~c'썣 ΢8nRj}{r]`Ё]@fYp&cy2,pmE80`˙rçw1_HAX$Rj;'. (|J^]}4J)snJ' ].o {&[80SdS 6W&eHE{[MRQh:i`IY+F8DoXch$!4yU"TnUQ }|nr@Gx'Ύe<Оߨl4YJxw cqu~88i(D@@73*A$7lOP8"n iݱPthIwȈ'¨< J0>`$ĝ7.%i%ԭ F'~KsHȋ3ehL)jU8qb":̽h5'+ pHkcnSZèEW rW\-|]AQ-v|}tr6' <:OUur@u`ڋr; aˎGfL9'KxoMcsU WWhS~m:O`O->C,~V?2DI*5 bf e9 V@'f!o;??b lpc'OJb(?m3dT.gSزMë=Y)n m|K-$ī[l,vTlZ p3>wQwsZ^muGZ҈\6i#-\vL0P+Q騰]& L97R5;^lY{KS& <R–+{ܝLj:eZ~Ukr+K;G'Wv\Jz.Sm B䷎kռIzַpUjV~V2ȜvN[c,-ʳFI[i %/lC޿]ߴFd6Cݯ~fG:Pr,f5l-[m2Vмz[ {Wc:$W-DRV+ʖ%LHs0ІWP?6ELJqfz*kT܁*b]v/GPuN8 Yfۚp/b]C /n?@6< kYr^{9|z'[QAsݡb"Drڲ _Oqߊ#cKΈ`YE2,L8M8z(r/iN HM|o)-v}&ƿ qFEVGIiHIփ$,C="OjqeKnd}hso["۶1g!196+mqɺQ R$18Lf(`BJha)&V:tzBvFf% Gj+_ĒPL]ξ)鼫OPLNhkؽL¡U-*V\YYJr)ݿ:=l2uS{n'7*3|ڮ] ,}ZUYє⪃7;;eAj4ws #yW@K,^=AC,5>DzY5Oo{ĭnwRn'0'/JK"74}_j aftiLYi X k׾h; {D=Kn:϶}9dU{`aeno.ՁͩGީTzsigl׼N} hLNbhK^|]Aj(LXpJ~ M<*f.N+X=y3F{{Vujuw=gm<_b0,ddaCgsX/-Mۛ,iUk|;':"?I@3b4xg"/!4ViXW[8jf ?۶H-ra[^v4[߹ lJs/LZ 5?Ci[j#(/tZFj]g$oݮUn\«elhiDfN:KKKuohfJ|q6 ` 5ح&Pm=I!?_Cΰf2Du.o.gޖt4P,~ax>xօ,qdS 9`dJULaHYޭ>OߜJ@Ln{Ci[šm?Z6rt$W{o;\VKl%@&T=ZDP\| 9~# VX§mD6H 2LKeV65wR:WDaZcHJt6Xo{wSM؛Px]H8x,5:-9cySH $ߘM-f{98h-)о!u͈Ĵ*HI&X%=kMْŽ/X8yDtA i 7ɪl>3Li?".,nt0t_PIάuI ax\P1TF3WDG`c} 5~sךo <3*Nv rVa:i_vFJѓKIDn%Sg7],vG _&:ٜVde) #2)MQl2m6D;ѫ!?~H \>iGAe o2YIKN-{9͈H6wqp'8)rօ%P'A}y KdԀ2)Vp qdRbYsC%fdŊ䞱ᵬ2NpȞcjMxض\z9sE.Y>֙:n9:#l?Gבf68H~o JJ2oQe@:Nh.Zw癛sBcWу%T8+/v.+JĻ{Cb0+.r[B -[Iz#FiJmk|K;okx m9>uho!SpҦ$)9)/D EV#L'xRQH0Rt)YJ)\+Zݨ'hA>4 .@nX=bDh[,Jqypsr@ C3ҫ$,a90b}r}<+46`$L5U0p#q ut MGv/QPF8;(QUaהaɶ1uų\kNKbaQ\+mrm3~'^#['/(~_gMo9q7yS֭ױ10ztuoK n#k%M\u/̧5 LϑڶMRhT /[}ᔃԎC+SБ I98*a8h n&h E dy4&YyFFf$5-zn t{B2[$s5(0L&q*mZ7L_]9;W~?BAfM۠5c;ԭ/W-  7{Rـ; 5f:f2*,út9L3*`;= tWPd_0f&{,WoV٦Rb=DX3LK.]O&m4 N ,[ᎍn%< %!L\ {`UC|Q^a;\7j+oƶcͱDrZ4^BeGk" Ϭ=<Pr6PGkR:kvW+6tp#gԠٽH|w"V?1>@)pM_`tIq-]pփ_%(Q0IΞE|ﭐUafVv6xDZF 6f=o҂|a3-RylU}ϢOqÐ%:E>B({NNj^6SPC/3 cV#"%+W'-8{}{Yб;dq@N.> eURIQA+o#`((py3FGxƤǬ2JCKvaFϜMWRXL/$ˮ0)aP('vZsC|(sq' 셟{$\o(9xE %ݗmMxJm0S5r,߮9oL GEXq&[4o)' g#s#f;B+Z .z)u쀄3H$5UE˫4.zbh'"r "YH=]d g]<Iu\%C/F?3ƻj.Ċ4Ox,fuZetvO;!0f\0m qЏ7U>ߠ{!׭wTΊ^2|z$S\I |,kH$x}5l#E9ՒxCWZң *vR8h M-5PQ=s}o6{qztLog]6VPJ<aaF))£ÔQo< D.ʵ2-, P.~yΖ',wq_Hr歱(isy0a\U`𝿝=v;$E~Re4I(Mw GyoZ+: }~fTZ8n .P|5H|tg)01Q9n^vn[Wnڔ K,q/ls;+ua:>;!@Ne2"0YKȂ_T]>-}XM툸1^;¼R; K7SS[e:Akm%jGf5ivZ@ ]%_6N ,Bq)j6aƟY\^=70PKr£*+]i-3".NF 6<;nocM~llNmqe2;l38,rbp]ttj?-~B7C؟s9Nx[(CuX8Tx1zve 8Y H6&M(dG$ΤmWzG,;pp= %l{˻,0Ky393c(d MqNS/mgLZ[Y ʱЙ+"6& (rJ I3dpέ ta8:-I0E\ݦpWq{sa gΘ"&/i$VԜ=+ ^RbwḵJcx3|lSs.рs1 WtUQ:)ᾱ-g6iJ3dDV\`h$p 5q^ l㢟(e%l+oa-qTMѼ={n;AϠ<cl17 [ՇǑB^C/jEkWy0L)6#$O d }iBmʝb<~nJ `I=RwTr9T9"ɡ{#/zwq;4opb1T0WcԻcȷB$7ʺv<+ j&#l;5TG\rjy;m^D>MDZ0-tM%RnPS8tҊƏU%Uڠx~u+ urg*z\gzPϦLaz`+nC endstream endobj 59 0 obj << /Length1 1682 /Length2 4144 /Length3 0 /Length 5185 /Filter /FlateDecode >> stream xڍu8JBGF;+;[YYswr!JHYl2#"\dh~~=ޯȒ0)edaP)(T$"b&cP (" )eED46L34} 0y%i%(BJ6}<Eh D+# `ED#8NvEaip `GQdSq% JK]' H( PXOjR M0;=D@`Ds("@3PƆ? 0)p?wqp%qh ƠRd/2;p O{`t8a i #!h$EBcv8BvڬCjXLԧ&{C~ BhyBX964EP rP^WN ojW 8FvF~@$ )(߿F  @d ƁDQ?0mD` |~?]mx11D켖Oʿx/WHJrҲ(+5(@/?Bш`m3 rP >]D_R0]GǢ1?-hL!.OMP?DS՞#isn$B!Md-:CI BN!цB.up!s 9% WcƖ76 ze,+_,}[nq'd@ZNH6T|uŞ0(/.Ȥ`wmS=UcT5<ٳfRpMJ^!{ OK}pU>#IT2&\iXiAjԡ7<?R]]4d/I~k=Wwwس.iQοΙu=z=+ i[*;3XrI=oJ4 `&{yĕPPWWs*ΰ(ٮLs5QuFӏw/LQf)QyĘDcQ=nW%pA=9!gDtuE"س9[o~e5 OD I*W߄Zo]x6ђx'.Y-ӟjuK\鱶Op;XMxqb\ԭHQWdeqiLU4"ߧS)1{*!|$P発 Zz>OڔWu۵CU\f|[k}l1֖B9QLo{rXE1 uݨ'h|:߯KYԒtć _~`._YpmAQduV^ʷϭcυ7' oyX[P*N6𻭡qNnǶLyZKqpCϋ.kr\ -w#*h8D3eȖf$0Ǿe0W>o;(%wږl29#j՗ NUh:-w8Wڑ&ˏ1+U<>v `f'BMm-Y> jo3ݸ3SЧ2tVz{t=ȵi*O`h4m(ոPJ+BꆛQ\-RAU|Ymö鰤]PUh@C0Lġc^t{zM 7tMä٧>  m_;E)96;K>_TOiw6ڐHKNBT0e|@ω晭,QSjBn=PNk;nO7*_o:$;ֽiH,-aq9!:zP4UKn͓I}b6+__9$ғ@-  %.Y"ލ,TxvE+](9my/$а~ tx!^ztEA3ϔ>(6PX.oNrv^Q™5OY7}R7$,I۱z<33:c6j9xGcS$ Yсt+3;;1M`.E!tVDZƻR}NFhj~d*x`؜cBuYzȧ g(Е镮ӛ:WΥheMfV_6t7Us+Yo'vTh*YK/Irzz*B8! 1*yTx@T2U6?\R.\vW~l1!~!!pY9jxzYWMpБސ/lf_eD8&_|h&JCLTG9RD4\52 .Քrg˗!/7]Xm-xi^إAlBy:#˲B LuIC.Ucy,E?w>`]#x8$yhAm!s# mS2YmJУ(n=g"?')i.QA )s9jy?Vg܈bKl6.5קЧ\%#@^/M(oOY 7؎ Z^e ipLba u=ݛտ9ۼ'65-8pNkre Vs-)K!2ax%[ DZK:۲ij|ܟ;<! K3'͊ r *ܫ~Z^]gD0X{ӧh-q[(6m+e_dWŚP^`O:Ⱦa YQ JY4 %E0d_k(P/[v$+x4B͐M e\7mot;uЦ,z \؛i5Х1lp'x]XlzZ\})ČpC:JtHV(' q3+G٪kmr73`vj/Q,ٖԇj?\NIfYHrQ"('M}]b3v|-c[{!j.{9`+71N]d4Lrs\}pjrỰp2@=rLwMOuJb7kWZ}Z/+w w皛0eCFX>f@'nïV6%%wc}4Oc.xP5&‡{6fR-V$5&{:ښT >OXIv0e% F!ϳZBz> -ɳs4a#PxpstX8-2o,qIT(}3IaYث7 ןeUL£s,YuM2"U>3ds"nb&]=Lks6_ӽmf5,wnٓO7O2=rgںU]ityO+Fmt|;{JVm\r\4õ_7Gno9kEgڳb^XN^,})Ҏ<{~2TMMF6JM. :,/3nӕZU`a(2[I^농!XKÚЌw"Ha WLkmFև xq=@*Wآ:Cè V}^mDZMr};"ȴ$aέZGb`Jݏx x\g?Ţw1&A>0}l,R3 endstream endobj 63 0 obj << /Producer (pdfTeX-1.40.14) /Creator (TeX) /CreationDate (D:20191206131600+01'00') /ModDate (D:20191206131600+01'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.1415926-2.5-1.40.14 (TeX Live 2013) kpathsea version 6.1.1) >> endobj 8 0 obj << /Type /ObjStm /N 44 /First 330 /Length 1745 /Filter /FlateDecode >> stream xY[o~ׯpK (KKs1@KFJ~ %Mt@7;3\RJdB ) JLz!0 PJ %uOY<&(X\O *gBj6Bq[ ygB@K- [Jfoޱ0P&`fB]a =k0A Lc.j$ 1W p2l\υd* VϠ ?闛RЛbT`:iIlzP6wd,U?6}@~/zRcȣO?EG)?t2<$ .TzSO'e+ᑠ 5vv~s蹋s_-w={.l#Vɇr<ȷ\GZއײeAj&?xns-Us3?GyiW3cy?efcCX.pϡ7H2c|_1`yXU1(džĶ0_zv"A5+YRyì~.8%ViX`+8"xzo8XR23P˘rHcL5gֲ8w\$8jl[ԣ7k9NbQJ[՝8Rr]q^K3UR7K[gujuj’Rh-8#tp]J1nWcHsTC%73[\rt!d*0DC7ۈI2sk;42ql`[t4SK+̰&3h~^|nWg2Dыvwe3vZƫ#o_?{gRa`\yIqjHYɟ'ao[\ɃiYp\ =<-`o2"I[^EF=:O2u\5og~=ڧ =7tB蘒KPI#>ҘiBSNJCګ,mJ3nˏ Q""s,? xLo En\">TK-&M*4rBMu{YO=4yމ{0^_4D&,r`:Eo]П{w.wiscoFo@ou}E y=[ewJO^;׾upIoRgsy? ^Q_z?ר hUVa E7c3.+A9.?c0llisj<,3k8 ʭ4+T5}T߈FuY<:՗+T`EƴMWNWJ#\4OiV6m>ͦm9'8б5u @ z/Mpc0K#](~zsx}J;CO~BU&Q^7<-RHKx`ܮ62V[ϵX ਪ]VG/TW iöڲ:_xBl6[len{?ovږF065ʭݍoۺqdDmY Кqy.K_a#cN^6lxt]c߯xI4kZ$=ɝ=mcA2ԅ}Q endstream endobj 64 0 obj << /Type /XRef /Index [0 65] /Size 65 /W [1 3 1] /Root 62 0 R /Info 63 0 R /ID [ ] /Length 183 /Filter /FlateDecode >> stream xʹma\^CHD h(NiQ n 4<x%Bh}/r燩Ho##"Edz~Ȋ 9ŧ DpDEΙE|"wD Q'{gSħD[t_#w>qz ˯א^\& endstream endobj startxref 81442 %%EOF openxlsx/inst/doc/formatting.pdf0000644000176200001440000031017413572443201016530 0ustar liggesusers%PDF-1.5 % 3 0 obj << /Length 1141 /Filter /FlateDecode >> stream xڵW[s6~ϯ`>3 .d3Mv:L'q'md@$jW nFsst$ |yޭ.`fvApgz8J?&,7__Bh`GA6 p K(xH[vJZ;L`ԝO\4f^ <;G(PFD笄cR9#Pܰ\DbTNpZiv<É)Dʩ?NNd°IJn0YN#Bh 9/Ld[\h'ȡ|aͯuƨ|Œp%2cGNWSI./V |HRX"Ή|VFQ4#R'O'pL2嗏gv3|?`97U'$+buQ^=ۇn<>h]aה*f6I?޸nUÛhkE 9vs}}th?YJۄfצ(Qavdn {bTe> stream xڵT=o0+gT4mq蒯E2mڢmJLKt|N:E}HHWU2@s @U~Y)OCnr i>j_'%&"ɺǝR'zO,Z3D "e ބdr__xcl⿲Ԗ~6;g}iq }3~E6>T<"?lP{8i,:MATg06}gIwO'h0 g^2hsvH}lUg7k:6fӐnf)|* "ӳ_ ՇAS@ѩڃ1vӎ۽%S0 A* D6&/8ʥ[pR%[@7}<ȤSQ`ȼ1 Mn endstream endobj 12 0 obj << /Type /XObject /Subtype /Image /Width 516 /Height 673 /BitsPerComponent 8 /ColorSpace /DeviceRGB /SMask 17 0 R /Length 18707 /Filter /FlateDecode >> stream xypWوٍ&6v#fc6bG1ឝ3qOOnmlh06cq2m04sZ !  @teVeefҕY|_f+'"B!Y_>0uBh(@p+~>ˇ&.#l_G[C!4,xFk_EN|1xmfʿηj# <~?+>ȹ+>w#g*\(|ZS{Vxn>@Fޥ~șHOO瞨dPD HtQ{G+V5m°Wom<ڒYeG(ZB@Fޥ~'Cʯd2LPa@mEaɂ51l. Y/@ 87W߼o'F.LmO:0qԨ`寶լ`0\Qf~ 5kGmY;}tQ k7 >-:-+'Cý Y+ d90oMV( pޯ>=tw7577ih8QQ '{evxԨUڞCwhł}mW5,ύRX20j7=ww ±gyղaAlPwxE C"uւЇe/ZO|fssѣG32, ؗ¸5,IېW{o,ػ[^q ,; = s2|`a:% ߓBm]۔r@e5Ӿv .|G ϩ+<3iX} [G# 䫸A;E(, Cۣn ݙ'J;Gzhe<>Gg }i61,=DB67[/x!wOR- kT E/'-bo<@;<,Z`BF, \5 穿R~,$;';5sV翱Ⱥ]_`Ckٳ|'~ȵLןycAcy[ǂ*3#b/Դ%q5˫/c?"vXL (u0"pȢ Pˏw0/w3bX𦒥p}eηcA2G ^oj <,X̞`oie6R(ӊ;{D- {d]ޢ@T.Zz{(]݆MM_|źu~VQ\줁a ̽EX8 \H8)Z@G6#Jbu&_ 8G ȜD>"՗}`A"Loy#Sw@g Dޤ>Һ%:%IC]x(4{8 [~?Wp׮u999&MM_OnWhTih4HjsIΦQaW硏H8d9`SǺ@c~K$G -,Ht( /{Ұ.!@*DB'S/SpJPC $ 4(,cፇB{߿~Yf͞tŻWmoJYKكQR](=?rt@ h)o,P ?>m`AbLYX&xy@-5 `OJא|\GwK{9eJ]pH{B9EG_9rSAVVքSvߜu}wͻx( i]XP3N$E,XD#R8H' r~x:'2.'Iu@@{hKGsW@Ր '^3nذjժ'LqbnqW8pbT)@  Wi !{g*2^#oJC f<|[l8sF|GWhJԹ ʻO *C;`wDZ-1P9!_M%k>*w׾wyM^:[XX=i\$,,@,_Ϡ;_I/$[lRg6燶l)./wֹoZUdScG?Y=mWl},pϽfY'>|ߥ>B|uX?||褺* ] M.ΗZQֵ5Tޮmf>xpKGM֓xO{`,\q[ah݁_tdwl|Z)u%X `A?Yp:Fp[]w,";u{}[ozWL_o7?&lӹ% _v߹*5l|3I~Y~uX?|t=hf (O}'l?䩙_0c}ֳ?g+kw<%228%쫟K wS_\?S[8t8È ,Y[_6m;?ڲ@u!V_ukvc"BY,-"Ov˩iG>ի;]Od~ƽ9;V=%(p[h:VS; *Z[cLq Igai_rW(ptǣ!O8-y߽RG~2˟M);qZ; ?\S皾>7C,?jmiX'xΏ.7Oivͣ]-~Vo_=|γJ!Y |3/,u~r) B(,,xyE(+~AP` X0,܈] Ɂuz{~gX `aw,8;bY .X `0 e3aӾ8OsKGЖ_O83XQv:EJ<,];K+]NPI |ʂ5;f76m">~7|je烱Z3XvU=cҵ(Cc &F,G$@߆,' WevcT XAջ/u~Ŝ^{ئX>Ȑ7m}qX`Eϵ;_Y`ySݘk b8+a.ޔaMPJ ^3 To_?Q[Gd`aקeqo28~`I 8lu' >V:zL,̈x` z"ꈾמ S֒urR-`v+ j`FC$9Ǣ &coē)'ףġl 9X.HQu/eW^R}r NC_y DQ4j/dX?X`G.uA2 !IǮySѽevю7 *- d(Omy Xkd)R}X`uB_@Ir;<>Y&L<YpIK_VLbTۓmh-l2v"a?j(X F0 D5 L/xbAw}d3?42y7twSl:XN,6*90 mXXY{ՙ'M`ƿWg~ʪ*;Ɩ}?=jx؏/>?pՋwW=hw!csV /i۟X-YXPPǿ0[s馟kZ$egDk2gtf2휍 sƬhݫk^'. 3һt?[qᛳ^ser;?zwzBe!l#" 0;:Cz~Hע Gʴܲ@uYetkvcL1ƸnԻ,lX~=yfm۳'pW)^?Qqy c<4w 0xn’~;cqX$vޓs~zRGH :jzHskcN G>#e.|kRn3Xcҫu[[[[ZZ޽tΝ[o߬}M]$ySWb4,㠰@󷶵z]c,]NݦDo5`qz -Zo6`qڳ@۷`G,XX):^xfku|g,ޭ2p1Ȃ;NjMZҦwXbk$c=@@ .U\4" -O>4ɝ*  Ec}bAS)@PKC ,"jP*X1KbUbw$>,cU߸qQKQlDJc<,P~EFnX&Ȉ +55 QfƎ1/,~I5Pcʂsj+*\%NOT1.,~+ ),>w |g˿QKǾVoA1Aa._ @pƂDŽFKUT 08(,jooBpJ ;'|wUjɿMU RfSϟ7 Pz`揤ڴC-N B(YpZpPgaBd A]sǍ 횳9 ڮ9Yvydϱ ڮ9!`B!XB,-=:>q&>q5l8(&1ђyNd<!,@! BB1׌f52l5 X}%kNkuUm{r9kN]MCmלKMmA]s"*hX `,X `, ,M?1,eqe.SO+ ,v2b\Y`9˔˹] WX2e`PPPPPPX `,X `,X0x,/|/ `, Y ޲kӶ3(BgP\k Pzԩ+_cB,@̂%Fcĺfk61b]kb*ᷖ\dkM]B^T ?΄XB ,! X\31׌fQ暥\3X `,X `,8 `,kƺfkƺfkQPPPPPX `,X `,X `, !`,@!XO lX׌u6A#p]3.d!,ؽ޽?쐖M'!,qAvK!`A2 FSv | ڮ9vٜdmל ۃh"X `,X `,X0RY\31׌f5c]3X `,X `,6k\(s͘k\3QPPPPG `,X `,X F* k\31׌f#|B!  B R[!P!`,@!X B,`s͘k\(sX3( xyEb<"@1^A/Ϡy ,X `,p]38U/-!zYGo1y:b~qȫ^[C^buC]@]@]@]@]@] `,X `,X `pf5cs͘k\J70Ɓ7ZVW9ۇ1,r>q`A]M}KMma "*X `,X `, ,M?1,<ܔM?1,<ܔM?1,<ܔM?1 ҩ.HmcX `,X `,X `s͘k\31׌fgV#1۞u쭱@0ܽ1NW:|t=E>PB-loG2EOp`q0X`H ԭ#ݻc1ưc1,c 0 :kx"`]? `Ajבul -/) t5q c X Mh_""b-\^FEߴKv~G/n횳/ ڮ9} 'Y( % nmyYĂqcegl2XvfAք3 s,XvfAhrtcՑʂwVX `AX}4cA۩t\\TȖqIX 2!o騌\;RBc$ɂ;*E[,Y`y) $ ,5%axa廦$/,|ה}Ob/g]~旑H[k˂Z\[&BX `,X `,Xr= k\31lhXP49FK̂`ٙwZ.,iYp-XvfAVe `,X `,>ae|rCXnJ>MI'1^X`y) $ ,7%axa$/,<ܔ}C]@]@]@]@]@] `,X `,X `s͘k\31׌f΂-̂5Ǚk3 g9,\sYX `,X `wm8w' /,Ps @‚5 /,Ps}g_-\[غ`-X `,X `,(sRkfdA0ٲf5csxgPYgPزgP A3(`,X `,uš,vpڪ]SǬ(6|v2)S3uW*1˗O_aAssJZl.3uNݻ;ڹꃩc$ wNu8뼰SPӌL>;ky= `,AaASS%KL^l,$)3e{Y}ژ); Tm{2fI񃔳-Y<~cMYz%=$╫|7yJ2,F~hN%^#]X||/,Mg+Qw+g~|o'/@m9Iؾ[bi([°2ޔ}7ǎ9SLq3PX`WjϖC777C]ૺv{{}p1i‚[ X ‚VT.TYXX5R'/KPƨar ef/\%l(c0Xh1_z_aŷ5?9j/aCYZc鋇ɇ@`/v , N;tR~9,,5gxa&8|yځj?OUV7oY2jշ0Ĩaç/ O7gM'1^X`y)?$ ,7gP.85WTd='L!F t\%l(c0KwY\Zp*CY/]%l(c0KyLX `,X `,%B!gAy 5R7Z\%l(c0KVWKPƨa-]%l(c0K4KPƨaj/aCY*r^†2F X `,ag S~IXn>M'1^X`y)?$ ,7gxa/,<ܔ}C]@]@]@]@]@] `,X `,X `!P5s]gP A3(<"AzǰX x,XfOb'kEY̊kƺfX `,X `,5C!s\Y3(xϠQA`,X `,uX׌uX׌uX׌X `,X `,X `,qBh;F!,p]!P: B}] B!`,@!XʂQ]i͡94'= A]sǍ 횳9 ڮ9Yvydϱ ڮ9ѰX `,XxsO+ ?1,B ĸ )/O+ ?1,B PPPPPPX `,X `,X `Bsh͡9%}c`CsĹCs`,94Мbe)?'pCshΠucmh͡9T1,C, `4XfL{94XfcB(Ђ!`,@bCocy:Csh͡..@!X B!5c ͡94fq`Aku};-48ۇ1,l8 r>>"X `,X `,re)?'ƕb\Y`y)?'ƕb\Y`y)?'ƕb\Y`y)?'X `,X `,es͘k\31,m暥̂na scXpa * ,l8a z˿pc`,X `,,7gĸpS~O+ ,7gĸpS~O+ ,7gĸpS~O+ ,7gPPPPPPX `,X `,X\31׌f5cϠ:k&k5D5'vђA]sN X `,X x/xXb\YqeR'ƕ^HWx!b\YqeR'X `,X `,X `,`Yj0e( S#eYXҒEW뮷4 77JnccSCC; n߾YZ;domkse">"X ` LpB56B(,ȑ;c5 ;tg}\,Чjn7C7Jkۍ `tI`33kkAsEV47 77MVCͫY\f9=bd/M72ջַ~# [tNBCłh|cm@ @pyܤ© ZٓXW^[L+$n 'siʔ!{ g6-7эm8 BhXgofV"%9)+8E()^l豗90%e\ƇR^1b^gي-ou'!-a t=!B 11u Ij]l;Gr2;tFvpe{rp  :7#Gv UԊZLEsVv#:S+uǑ-FXeԽxEwH =Ckգ(r LMs8L!4,U,>#&ɮCks}~Տf3tC]%N,0ة;moE6c٣oj~L aBh8Y磵g!Qi ;^S^* F@pA9FL3Y9gI "2 B,yދ B(]Y`BXB`#Јb#,G&1i0  n]B N_pj/,8m^bN+ Z;0N'ީ8G n74a1N3{gAՍka1NkI / BDzI Tp:cqX$v MwZ¡n/ w77i\?s#EzI z[;߂~?_$vE‚H8%o^|kp".һH^Xp/rOD p:"uv9%?~?_$sEb]$N~vs\?~p\?A endstream endobj 17 0 obj << /Type /XObject /Subtype /Image /Width 516 /Height 673 /BitsPerComponent 8 /ColorSpace /DeviceGray /Length 696 /Filter /FlateDecode >> stream x O3D.3D endstream endobj 20 0 obj << /Length 931 /Filter /FlateDecode >> stream xX[o0~﯈ ) JL}`Ibsɿ!E!!>1c|r9ꌽvm{zv^Bgk[@Q{Akt[8KQ:o" ױr&yݶ& Y$G|o݁ ŞO־_N5sbs"bV:n1WE3qHR1C'Ɩ$J!+n*(Rf7)9v•% 93FYѯz3Xqji_f@ ^' !ʷm&8N|E1Jl2DQ@,Q_0I2 < Q^tkQU=W1 L?8NQ|0*o! hI5)7P#}I#lBL̃EJ4 i:mAQS qa*iU)6*+BqCd+ Z-H4WqNX!n1l2 ؁U: D;BUIEyIХd) Ñ-I"h/_i4,PFuᙕW^H 8qJ͑< ojVv R-O?Foӱ|*Q ě|F08.0&.GK;bq.yc]%g67g@wvfq~Ky΍R8n9,4{;7zZ@7+ΗŞe@mtU> [~'8(`i*7sퟸ5 eFԲ7\3kJ- B}FZg;=EXFHB endstream endobj 23 0 obj << /Length 1058 /Filter /FlateDecode >> stream xWm8/ &j?]ն[5TE; *;/IHnwcy`\ka WGݱcy V4 }wcEwi7_ȩGI .15Ɋdd\M*wR;k%\ę#Ñp%k^Oy99@B (O|Н\M5:T'sqmRyvxu'Ha׭} u/:ș(]{a_:1rua犧JS7Z`ͦqnxgԦkiX_!ƉUp@6A֌ibNDLoSJU\Dؒ.N5<{_c|g@>dL6v;tDk]IcRQ׻]3%4Ku ly;nf/%oxu=- ~EK+3;Eoic\tXkE%| ]oR1zp%vvLd4 0]=x_+QV@R ^}Y|x 5 ƽP"EQ qf]ûSِqO%ׯn _3pIa*M-4r$-J_emcmm"7dțO4A x{9ݚmz`C4sϫ5n2llG?U,?d^(;Zz,cA{|=;> stream xڵX[8~_2}H &ͨY)UW}jn0 6M_62 < c8;]Y).ƿA1Fpto*L2x)?Fa^@7;7ݡ$'G#(Mue`RcD]8ࣛ@@7,zOtz iCM&<&&]90պ޿" =>G1.P& \MAE1A#gE!JgP~#)* 8d5}"3هS 晃\{reaXxh Sㆮ?_5Y-LRP@'w-|Y (x&h%W-K'<.gx0& amnCt e*b-P"Si^\WD2e\ß1$Vd:XCz?H0ie U>];aZƜ{dѢJ 9EMQܝVBmUB:YjW2wRf:#;xwfQ ,r§* !Jqfĝt|3?@L/?ryA!)<I^z ?M`|~W1t=܄t.Xb-J5)_<ޑ50M`+KY&)2?4vY,]f` }^&\D 2P*W^tz3R5YzIh_oKffdAїCmS´*hizi#xUҁd2 p_{T&T"FVFr⠫b_@Ԟ=>/8w 1JY*~,Ea r ]D*JQiYb)^]KIpaQ!j[2~[T_>>$F bjf &=Ms(ۥxB0߻a>ؙZ{۰?LԆv045},~Ys?Sxb endstream endobj 29 0 obj << /Length 408 /Filter /FlateDecode >> stream xڍT]O0}W4Wڲ:F‹ `>VBYV{-,.s6E` xw;)qFA XRlۂd0>{Kz dTF[TEƤr-FKCjUe.IKܷ  B@qI`MH@q $*ф3$-Ӂڴ*rbC\=H1i_xZW53ПbzJ 5)mYŝ_# endstream endobj 32 0 obj << /Length 895 /Filter /FlateDecode >> stream xWN0) `m;qӰb0iBLE 1.Ɓ6bWi6*?|;coýgp >tBA'Bg8D))a/ab9û <tߋHhAHC{L'i ,8W/#9&mgSmO '4θ KCzVx>E2x(UU,O&Dhw [x*\Wn=wT`ctk:H=򧑚W5-G>J축8I4@F:CRw-ORq9R) &Td > \J02>Y$Nӗ +2nXoՄn8kmَi;)".`1Nk/1lrNV7ڜMY'D#R5TNi]{,m/oun>>ܫ"^g `k_a8~רP̶_˹Ftnf^:SO&V8MXe YO3>W5r.[ Z/w)]8Fhf+!!5T9il{hj+?!P#oSTC@PHD֊_*U endstream endobj 36 0 obj << /Length 297 /Filter /FlateDecode >> stream x}QK0~_ i6v'8 m5Ѥ"6m7V>|w]`<½}`N1b &$1" ,2 S,3ܦ }p\zFΝuڲ=2]IK3FƬ"Jl4w8}j( (vjejb[F`?W˫vG(mz's}ۥ:>aƎJ~8oPq&OG!=8ED ᨯjd endstream endobj 43 0 obj << /Length1 1768 /Length2 10122 /Length3 0 /Length 11242 /Filter /FlateDecode >> stream xڍP. \A w \=wdwUT}=OoGC"ano:p %u98\(44g[(4`G'=T_ )G0Y& t~&*C ..  ;;]?D{GA4bPf(CN(4RK+8yЃ|$ P:[# {_.腭XvN 7@vt~ Pځ*iqSaotl5;4o?JowYva P`*:;3PD==HP+>'#ى bFnYj.eog:;O=_͵ڻA, Pse8iA!\qE(,vvv>^b@/{s` 8;}o0f`Kbşwq~3z0s{??Z&%#W+%%^,<\N~>TT+;._g )yX ϙ >bxN/ߐ|K_yp؜̟;\ G{n+| [W| qcIf`rPx%0Ѕ!C)@XV38~X޾D7,Ulah޹cܸȹW+1:]z_p8<{FM;8rt}|I/ض8;OMwӓyNlgN/;aؘͩ9BSPTst 8aE)hz[d# KRaNzrE[%c$`,}+:Ns%x޼<j- }1+S#AϒfbåʰU|@`^bxqCE*hD{= IOG!o+c9>pv+ΕO 'k)P֊;e$Q[w^Y 2%k=UٞzOpG1NIx.P?)J_5bj# $Feic\XK=^[Z"vLsqo6A20cR̹`.fu^jYۈ#( {Nej8 dټ2Qdd-d2~O02!-+"4aQ7%Z}^$e3M!&q޴ڥBoG?s8V&\|/9^_WՇۍEK^Lm"+UJD#6T&^`gV8gY"MW* k蒶z,i#l\¦5imIHkYDI~gIV^^F`7p,)qPZ}>dc2 pi{>˕֭PzcZ.9ETIG@|zm8>Ob魃{g_OA>UԲ[r+/nU x7+v7\Z|uj{x,n/Ou6= @y8;b]KdϳR.Z9d[?%&^P2qWS/N6%PuZd+_ (ǜY%VE􈺫,;l8(my0 B' Lv( Dx lpg(Uғ']+^7/ LrO0*T $c\ҁb/EE@ه8 m޷ _~&C2j !'x #wCv jEk{F[X p6`ڮ*US_Xif]+|.Jo[FEF|M^>Hʋ #jy:(&*Qe(M]4m.{Dzyg2b%%H@2W8YyuJ)<.KTTy1&CP敆-3-. yx60+njBSʔu1{ͩPt={ KH6.9|O 1U?)=x^Q9 P+~P"WZ4 y 8d!PWxˉT Wъ`?:eF4zDi6ѭV9B[dgmR„k[c~]YRr ÿT@4D(u%َ WTclB9>R8f+Cͅ6Pd-Kt6 4l%HC(]WgKqW+LğIu?Ewv臍X!֯ Uc'2{Ux%瓯 KC7̚bIkx({V3BI!yH3 寀V rm*N+ѿ0h$~P1fn)&-PAYN[܊\{"a HÈX,kh4{I-Yx6%6)@JBm Z2WmDCX-*Zn4\'s!{un;TOp]CǡX4N+i)#3 gSWHֆ\lx`om7'66I W-ًm¦G,bGFB,r>ԯhkhsu6.tniXI0?k8_63.HJz5EhN¾\O46 樛>5c Se777= g.[p|\DAN}\6b+ϛ' f*3Gs"zMj2z5>1 = Î45g\DXˑ9>? 2mL?f: ]C%HLڎPeM&R@6*iӑ7PӜ;9Ԅ)6yo'Kv e]/tPmPh ,kCy}P)?}2:lZ!}$gȦeUJ(=zX@ )5TWN"9$֙[?-Qfn+<@{ _q- 0F38,|l!0ds~^~stLSBl~ּareH'գ$8.愺8žԩO`rܕcE aN3uk/ }=ӔI¨OZQ3D zh((-yZg}"%}09;/wʎK/+N۝ڹnD˳C"b2T*>ȱh*[#>yCu[nVmfKO[y`e<ζ.Oi #>k@aj\G=ް2X+Ce,P *^aTwި>}VĊ$1ɓ(F"毀qC7'ze%ߕ$EfAR ]P` 8K _Tt13bL["}y4^TK* kNtRV[vRfپlLp/ye;;.̫ԔK}/hC}tu/k:GL4~f:%ZYgW9*Cs#8[Ѻ5Fnhdք/.g5vj B1fg/$ J̯K֐ZK3z 2_øRWt 7MZe>I72(Hz}h"c~(#ME@?|Sʎ |M8]YdwGvYbf|:f }\wĿJE~=b%Z>"ڭ2+i0&xn-ɋ2!5{ X'3| ѶTe-]tT솓e7BNAA97_mO}ad.OS]+̘ R݋BWI;ȹM%b|uLԩ6%{"j(^β3 0$dQoKJ: "Kd~b-]ZovVuF9+oba1<*ђV'X-M%yvP%^^F7TluMv3mmՊpx!dcSUhF "heʢslD{$ (3 iE|@̑g]mu{ݡ z.K_c}ą{bFF9uxV Àn4 7xŶ\? V<{1  w' y?$VLw6t`M`XxdaE"c6+HT`;F"YBX*Q/xE։pZ`F~\kCV.pḆ:D~4.31U]slX~w4WZ͗0J$*r"LV`6\h3} J;ԓ@UX6z.uUWxq=yBu"R'| *kzt 2T69WkKy\ Gu\Z 5UnJcxyn5KμRG C'k%_i&oAs9*$\$]nE*#G|,ܔPQD$ kxB1Ҋ3C_ƅr[(t{IdV[8q\P"gNyǮ2Qhɯ*i6|!dBA*`~ȬQu'r3&ifzmV>W;zdQ)s iF#y_b2(*5| nⷽ GaK4vkF)^J_꼝pX[®xES~lTbҏ;{\#`mdEPZs48A:Lh~K%VLnRxK.ʚb p)v=l#[hReX"Yo$`A?tÊ}a:ĚoFę7(J?5fm/NNV;Mƃ޼>UBԵ2V3!'.Ib!vNFVb>ƧV#2q#:24ƢtKיC#ѵWŰuȉ/)RD >':syđ,˘w:^u'g2}}'J+#:\΄|uiFoXY":l(O5tu'7ǯ綏\3j g;\9  :kcX:w|}MJ_P35}il11/)jTV-4vq:OEKc{sTAzDkKx#'<@4S5$AH\K?kԿV7ay$2!'j(37dJcĂ,;SVqepT]wWr8:_Z&NA! _gYʝ&wլoQgpw>H|:;A~Uf͉٘.R$)iVM z;vT(5mk6m*bR0C3xqJD8SbFB[UcS;.dÞҷJP jzhmkm }dgP"sPvqӚBWa-:/̩]P582hEHcܨil^+sPփŞN=VU @q9Z9k=G<ハJiso;nMvfcg#/D#OrNl%hWgh޴Qv |2VSKTuOb8m Wrn`>с ]>]ir"‘f-(V6Ht'L;B4 5)S^|{Mx94u}S11]j׏c5N j=5nӗ)#i%W@xEk ȏ$ [2lpRl$~oNoI`0|?e9I71&L<2uu)9lt,`բdNmꅼ|^վq""'ĕU'*.v~pd) "OrY>i7,uLjQQkkmeG՛PѢ.8c؆x5VF\+@'ҘoL[xyASˮ ]w͎!I۳Qw-|BM#9mń5 ~sU$hfpCog-a:BjYSצpq@`|Gw[m_7qI {@Y;4ϲw DU|rGULݼXx/,siT+K,Pau=Z,wM+M) a2φ[xnX]AOx7oM`z.nrdߌߘ $eglCȻu+},cEiT ^]kE$O)%<o: LM`!)8\_ۄZ)+3Yp%_8q+BJ ptuw j}[xXݜ)#?ٙl䑴84#BU?`,P7i%2UĔ9I"PyD>Buc(y5XŽe}~(¢-88sL'OYhQmM`: ^?ЯPp`S|j҆"?z;m/ fvA啅n9&x?V7U#"CNfU]wK'q߀zC\[e'x$A\w-JI<&-.,J/C rdD~H&~؟ȓ!M0%zɡdRG6_g|^]5iٻ4U5p]ܝm2b+: FKBfۤ8*F|//T A$vF?%¡7uV(yUrkM\ IP{3>]ͫn"Nm4r[QLB%4v1=8Cg= 3 G مkOCkF|dIh/>GѳV[j1(S_OOng>nqp1{FTR_Vb/?uNA"O2;~.M aXl=xC}Uul^G959م pP8hb4m^%YJ+=]$fXi{[ԙGx FKcLs!X أ'n]ϥr+Q:oIx *$B1)CGh]/cֻ3g2?vΗ_Ur_?0ggM[ #=rwJM;1WW-/\UʲUwvh`bpS,iO*ȊW=4ԋ%X5 () D[י"YE15˕L84Llh˗ @J̚*{_ꢍ|CRfxwg̸ '|tsE$)D_;RKtdثrps"zciVTDǂ+qww-r1;n)9S?P y?|Y%uè7ˠ} bJV ip8A bf^*4@# +TDҚq?gP^ZP6_FQ՜ہR۟XcXS;/c<3rԟ>^_*W=eRMb}6:}p:ZJ~5 CߡJ@K2z RibC9dz)>$$$+"D<ދR m cPfA t}Lb旞^aK{77ܷ=~geWz1=zu~sѣlc3:5"T 7 Px{5͔\]^|g2=3fy biZv Ҥ)-TW@apFxJWNOSsF( VfNS9nJ~Mia0Rah[KYG,cdžV;vfp@s6Ȝ&TN 5'\݂ $Zf@?1r'7۠pӚE*x0T_na Fa2N+4 %;.96rBM\N9y-@ыײ{G"] }t5CP /pBiF?'HL@4a*ra a15|b/(ܕ\֯um!&\%1,"u`<7U>S0c.HlH8t*guRQ~l~ZMUB3z$~ h/384%ʒ0H;GzApV $xifU,AnBDsFE&wWɔ W䀼aL0[ ='cqDs dzȆ3\U}Muő#I}%6~d^U,,1l`(8o{A{ >X32eEꈳԽ Urqq75`PɆ8rUjC@g\GQdnnҘ$G T=.(n{'ܯz.puĐ″2zAq@3;$\1BW endstream endobj 45 0 obj << /Length1 1932 /Length2 14883 /Length3 0 /Length 16076 /Filter /FlateDecode >> stream xڍTk 4vݹ&s&wug۞lLmd6&{o]Uu QT258330qD03XX((T@NVpj@G DN2QCw;9[ C[n  :Qڹ;̝ޟ'ژOw5dlh3t2ZhlhP5'57#+#? dt:MZbGP19%W5ur5tV c㻇 8@YJ`X/:ߵ0307lhllkmgh1qY'7':V. +Cw?37 ) Md"aޫ,fc"bkm qr#?Quo` 11 (%ɻ ݌nSd |h898=_ 0;f ;L `_ebkceTRWu¶nOz6&= ;!a[Sh;E1pxXP24}A}F_moBVV AVZپ/5Ur@J9rMAN dTuql ?3|?(S|ߧ}Rca:8ý<7hll]pt(/`/d0**E\(F(Eb4d0 ߓfM ?@Co/w̭SoͿ{l_TQ3{p{;۾O2f~O_St'wsS{_2F'sjN_s|_7WtxD:~:mml l.$Rmy5ϘxLWrҏrٚLc1.8 :HYq!3Uf4edp~taKxh KduR#P(0ͪTqH)[>~hO9G< ʌ-RP%U{K9Uxl8ǧq||Dɦ@_L"1 ΅YS%@ndo(΄yIBu7#N{Y]|=f5!a>ڛ_$Lݚ(*Lcnolږn_Grġ`M`J9V6%A86W'5Y%\?q^:<H.֘Ƣ.!Rӟ}!8kFL˩ @&l&0fZuR(r ( 2ad ogZTyvS-i@ڈCœDqz~YGSW`$+e+0J׉'wWSyoE #|˘ &~INDk,'8%ѯ a]1) fUXN#_&.L}FqMyhK{Q| ]ΝJ;ǭug݉Yȹ,NhA-_y5TZp&1'['M#!+lSꆋ#.V5vF֚,x}qGM$`Ռ>}lpL{LtsA10Mg^1Ĕ0,6y6w 0{jtSsm\A$%I)sF$ K[/apJiӢ)R)Tf"Hv_ 6e):!S3˓s._ ~)R:gk䵢/JOEIAe]E\1"bjy[}J ⅶ};ӨeӬ\\ZQ\TD+򉅧CĂPz,lhVs,u \Fo E#꽟t "jaÆuz?T.w&r!*m2cah-j;?iҴzw. F/i*>M^F^v#KFR5k) N K-o9Q%gti|c>aP{I5jӤ{I;ho!-C,Ww3: QSFqsB2h/|]U\~04s͵<Tr_tk3$+MOi|#Fq 0$""0 ZMDL6[Fvf&:ws锲v?> [#x7p=1 %7њN zo {Tfܰu@}yqګnO‰;_` cL y_VS]Y28CZ壕qE,qr QsxbYئvX郢dpOhU  MtRV烇mIrm䉴_\ev,ۅ_!›̨oJ0萜v8$[sr`P=YWрq]-zK"d~6qV].W\ql<=߷7EAVr.Aى(:PނF+9f!TAe<ò& fC2͚ͩ w]'!yHzYDH ,=U3F,(_%2¡58#Jvq!xy4]!MWQ<ʸTz{Nk-bQ3ILa*.LTjB⍵ǷYswaN- ELy0(v+2?(óK'˭%YQ_6e>ýhzW6 %+ZgٜTޔ@aIF5N=́KifPUN^Sb]϶#i+Ți(;5%Jx>F~,Êd_ߜƃ{Ȉ lCut p[K;`_ZΝLZL&=\Vs1H7hlە2TL}?tD U-c6"qw徜2&wL0{]hͺ~2Z4K*QZyHe*8 =G|ܢ7Ha1>H"$/O]Htߝrh@ܒW9V DŽj_+%ö/<%t(bT,u!Nyl GSɫ4Til tXcH?`ϲv%bM'_,[0߾P Z3Q+-[?|NCм]>nkpfחH~h\f.95s&L(i99SI?ܣL{9TI=)sVSMMTs_^U 1wLԊ Bu@* }Y,NaYp$\!LVc.5|[?crm`xV4]=b0}L,+Ԑ!t%km1Rs8,;?|߻ \ :m&1:'RVuDZ!/b;ziʡ2伳Îem.z:_?c.(lӮv|ʲ%Z.V6i6n 3ۋRV1SLJ!76 ݞXoyWeu} > \If}h+Lz)򅗑Eq%Mi~i4oZ" d!$]SW?#uhUA["R, I#ЖhmA[AM'pSSDJS%6Q|v(6Iuwz+m&!@G{Y!ZRjq<Q#HI-=HnI߂W`O#m1ogKMTz+M{X!|{@eHhCj~|7d8l,DJN3!˽\!)CN۸ziɷaNTkG}6z]v5-9z}f7Ow P҈o DV]@ᐂ!N :\Va7l&9x)rAfXl'rjaƓ+MՅz(Yakf9 |aF-"}`< ]J/eO+G>eBn?Ӳy! ex>>ܸz楄g z,J+,Gd/QBU]Uc.=t2^W IjpJA kVVi(jaɷ +s<v׃Ge.eԛw@R}"DBU<`G&aȓ8TjzPb;H0{[9.*+FW IPa=aO͌,MI*~Ʈ&>L_'>&&mHN69% R6io ʠIt! !a\cԛCYOQF@R{ۂu2Js2íTPݘQ0"Xt|n7H;< _CSx/>|Sa{~gغlDgJLNa&Ё| 3;:7AЀzBcTfnݛ!@eKClJ~6ԝy/rCg^#{)CP/~:U+ˋm\?)>&LkT`yИc}_[uEg r@j?|臹gU'ǥdǎ3L`vTGىlEˁn^O8?].tJygM]&RogkBJ}#>㱟 :<(qCM؎//]z{Xڤl"?PUי@dj.XOv757Bɲ`rRϐ<9ĜTmSRdT;AOS)ů8LQ2A^Y/3RUfC$">gG9Q9gYBF/3xWy*scR3nz dV|[itr%L&CCе +xMFR:n!6}80ΘVh=%6N5e(b;r 4-/N|mtlL,`%>v106XA3V/?1@|ʔ2pަA],']Z.rh\t*xZkzi}x=z& VLb]t 9ǪF''2Nq=~mjt`_Pc@9b[٩ b%Lyu]2'(U˷7kx{~pDQSLRk3U _ V,+.+iiWRaTBz¥BxPt<%m!kMUX&CW~# "n5udPײ&,a!a&Q0|GzmVXW}|o~{qG(T@5G5_t͘*@~ҌC%~nxu椉:X^uEF yE1`(Jlq:0PZDԟF1*kX@U7WcsaFS>[<%OZp!A2Nf<᭗pt(Q_U+ $T@dX?\g?-iB@)mYt{݈um]TTHImHuT,] rRZ4?f @MdN"n$:zr^(~ͅحoVlW/o17K!_(0#>_!D+ǨKxA=:8H5]ldɸ'%( ͹פ jV81©1?޴v(i"Ʒƀ[Ǎ{2*ض' 4Y.E0-Ta1=3=ɵlLTMAg}Fe,XQ(`껎[ ՒD9`6^SU, Xs.ci܄RM-Psr0}V 7s6^u:XtpJl؎}brT2?[C?}{;f>ӫ=E+ʨ`$v8}Ӳ3)ކhp ,>1 ű}HgsIƝ,6ӭǴOdִC(`_[Ⱦ W0^cSdcSMɱ8>3mr:u{O"J:cڛS&6<#&ί>ruVX*I犇_`+]*T:Yz X4[,/ Yմd'eJ՛ `O A|ÔKL87kÕeWD9}4bW՟_RG~)n Sp(JЮ5R: ]vJ_O~a4k\} -YjLY%OWqc"bJ\ 􅸐Sbi=Gmߡ[ VъLń1qS: DՔ-jP¾dݼvR4Ӳ]l"Un،-(tg[a#oyX2Ǣ Ira%!Ċ50pcSE ˁmR FFb VQtJjNe|gՅ4\ͅa-Z;ыџVt;dR.?~Ձ?7*`3REPDzTSLhL/bZp ~AON!o`}b/ :, uG^sq{ukf/b&[tnxq5 `􏈋7Šs3qPřdrrs,ʩ /3;pl/ܰ2t`C r!KH@Qn#K3)Y)$"OIp*ךˉ˗UkYߥ>n@& ikC.'FVS['kZ# $۞/'&Ђ~i%4nէ$UEQw'xUhZCK(Pw>WI^/6Eϓ*(*i[C'nr؝ g0!0 QIKh K(s_[6 T~Fqx S6_3oϣ/)A4_$0  yt~X ҿRbRxD&g|}vƬJ2;ndik*<]|k1pµ5g>(ANBp qK9z剡Y[' mS^U.J5(/k5Myߊ j7d?ޥRaLM=W1&" OFN?O퓝?3FO>ר)l#PthA4e11n*pGLgXmA)o8u7s8Wo[%&Rṳ̈̀11kkfR 82p?#<2e5G/кLC=ӈxb csM[ 鲪_;JV+ ̻hMrQH,wbm? 6sktd ?~e3c?%&1xQu@yYx#7,Θq#1]34n ߆%< aI^Q&vX$֊82Pv2IH,($@ R^ ݺ+% =cM}I(Cc$I,gF sD,~b6{_cXcSwZrHTAcFڑǒ+b$76an["%ur4|\zG:+-K)6:y1lZB`׾e+3sFXw*ޘ,v g4w~mJm->׉nl89/O?/0Q~F`iD, |v@&f6 s{4|pOQ16ѦTc4EKA6'IQk&u#RmKIT[M+A92[g}-!rE 0>#o!'uI˪d&TcZf VC57m)NP=a6n4LƨMfJ&F逦_F+&{&4`tV{Nk>?ew "FHB-E|΢)g9-TT;5."?[mh$5*r P6EڦḱB9d42!AO][LqRk[pdewK3)Ϝ=F9R ϐ \rb*$f$m69!adq򋻎%&ff[[`6 Ur|ucvwI D" ψ:,$īs&u o:P!VPv}&Ňb.51هܞʚu3_R q ǩInb0A>EZ)*칯s<8yN;E9&(stXG)wq#4)IDf)XHƷiI^1J&A^-v،w7$j -z\Fblt!r?UHly*,`,Eh>O{L_]B;#NUo?Ψ=O 1 &I9vm #:fEpm;Ϊ.fD YUkI n2T&GZ_ӗk?lƒl)| ޻4; H`)k%2ڪպ8Z_25ڵ_|Mg-4~Is +S[L<ޝ2u!)U *5I'xtvL{^;۷${sR=9{(zjY@Q+<|+$kz+_<4?s:s JMiOSq1*hA|ItQ\4vfOܠ.2F_ub膾m:p]3o2f J jS߈r)E3DOv6H8b}2'!/C:j@Ua y%~ ׈wyܰm;5F+6=8-@ )m$9.ֿ*4S>-hjoJz" k.? vu\wͫX}S/aƎt&>B ,Ct9)hZQKVnP%x2hzf3\XV-_Py!]`7t1ԲWg,f(vDǠmeբ$ t  S^KtrҠ@S)wRUßh#l\7Zb`=hS\d6>-nw?Wn\QΙzެDZWۜt RmWYͦ&5weV~L,YΛ~WJtJBTFvv:Yݴ= L(W*¹4\NȚ&ׂu6Mcj+y%\$M(+]vzdX(Il)L׭)VlO sP6S0B.^ }]hCR5h:)E\QӾc6p2Xʆ*&-Y5^ڻ!kjT0{,2Ǽ V" T*5[ba7 UT}?Љ9U~lwձ26WCe }fzQm1$)DEWd=nRb=8$llJl޴:_ !$ip l 곊;^Ȁ::FP|dhWL a^^RPl[qVŸ/޻&4>odͤ떱M$9ѵي2hxq'vEÍVyrT?nv28㠢)}vc~yΐlKBěӞ{9ۡ%oDB~Pw&/!}$E^eGg-Bdɰi;.◟:UNy),.:ԙ~3䬊/P]:>ͬ62kS_鲊 m(W6&ÍcbΫ~v})IyE5 97 Ձ5I ,F"ѺuvCfQ3Lu=ZK;|0Ғ[Gǃ}_Vֱܩv. _bWx Vo=gؤ)-BJ(n;?{i ݗvOeLĿP8 M]\)qYOZ ّm Z`3MSzؔTxHag>zջWtl2&||C()XSbbJ8o N֌p~`u5,z3HPvj}굓ET r@)rn Åi緢Kv-)ٽ ɀf#5<8RORQm VW 摑O#w]Bh}RlufOJ-&ĩ[;93*UKVXX`#]Kx5%pZ _JTw1=[rBw$'$U&"q<𖷗kmNNXf-[F9eJąT"@d'¸l쎱CQghqBk2F5rI&"D"D^7k6JgvWUM;f뤳nr\7ycu5k|o 4f5qU,Mh翰>kPsA 4ٿ«,UQfT+Ø!w' ʳ_`hNu7F_8]TnݗsD]UZ0YwVkAnȉv,G1fKHԬ,&H"> stream xڍT-whBp@# 4n5X< nCpKGf̽굺{ϮU=č( Q<@ 7*=6W^u;AEݞcn<'@@NPa Pt\Qe`d ` -!s7[-'K0$Dmܜ99===9]96lO-@ z TA:@hۂ]k9YyCAq}pXZ 5gO6_g[B`斖No0` v^*sy!VN`sg;7?˞% vm)AdA7WCACf; sq=>Y:s4V|@?uPh<D]s i"dKgf]vDNa~J%t%D{Ҁޚ4Asr u+ćC~ rvm? {NE<wAL[>yѰmj~%)h|Yb$7v,^X3WӸOT G1+ܮ]$t$W {DEkѴYŽ d.=&mU[ 0 1'<»W%ԉH9׼Z,\Oj|hB?s}Y5m R | 61DF+#+Bfr-6uA+߼f}"DcΪkD?/){ 9. .|c̝P놰*nbD#SX^eBv(G4C2{xX>2^GVD/Q߆$LgzrI,:A}6VKVtuC[ޭcBueV7ۧ_^?}͜l"n&c6g*FmgSD^SYd% 4o= Yp}}3iXI#57 e&ig#͙wV+S6%ޟN&M7!] fqSX?+"zU w|8y;a3-۾O2cp/ k[IKNI6t}=RTڵxz\}(Hm:b'1|Wɧ͚:7x RYR&XF 6Pi Ԃ!/օ9Y\`7f2i,v.'/]vhxK}pk!X6OYA|ؒ$ύY>%tD,c\Ap|G:UJgl AKdT5F߾-R)U^xu{LLD$Ì*$( (Ҙ\Z(pKع2 s2E+]BFiD O fͯf{_ "P&Թӫx$ک,ʋjl!bKî,}IPT5gKx+`w$ݮ399 OĘs2iuftՕ*k~ }U}6p$O!A4'Уd'n-ѕ2ZAn,8p/xNԎJC:+Y9;^I`?5]9!O -V~id̡LnSscȖt=R.)5~W#lo($!H[MPU㆘ ر(WPC{ʚ%7A"l9Xّ"NxaU@cO/;5(=PsBa?5fb[V6.LҲFz_I#gZ%j6W_w`='znVubIKylTd;́H'oRI{؝&g'xA;T:]F6b|]p={i6*/"2boW9s*)P }iANl6PMTBt/-7VNK5nP2R@ QۭjXT&O.S!hb!i e̽ONG'^mDr)V3έxH; *Ȉ:NEQ84>b}t:0Mi# F=W_OW368cpp8/OD,RHjXws&\D"Y;zMzU$f.B#|%o~>%stQQ]ɲ y뚊O0+eif&~" g׀SgX5:PIRU2=?ƺ]0j͇4_`1+jrHݱ>794"vg5R?+Qʹ4>FT]'zM+vIsBUƩ@Df#T&]H%)&X_x껹l) XM\iL54D ?R bxd|$al4m}s*'A*4p%:/oFXNI s 6μg1]㗥=ڰUmͲVC2feGxGf&~!!6(="o5WU39@tKq#K|MGeÌWK+Ʌ72+#x!aBq&8%!y`rO)o5>8wa4ZI2kw?J,::KyN-( e{]y3W|x.7tdDS =|RfDڻ_Dt;`ӌ9>'f2Ɩ{Aѭ! ,#wU*dyG$ V]ԏlEچHs!'إϋMbynSl=K_KCYq6xBu|)أ?N$F,elEZ#nOmp^Mhlb7&[F(¿ӯ?yk{a9SL i֑gRSs\k`tR~;~{yJ n8P%v$ h~v %"&M1$GN &">QO*VZ4tfDi'_@`WHb^fbbfo ^,7pZ]my( 5k^\uM-ikQzUXа8jCrФ\FNӻk QW0 Ratocf Y3Ѭo=\5m!wuLa"G禱$}$k_j.|xD%{+j5VvT0k<{V4RyL}]0[x!!/%洯L-|xZ6ٽ e>!۞[*K|<ؤneDgR'r?Nj%+BZJzd*m7cvqM:ESnNLeT_pYg7j!ͦK~DiUշd[=Tх~TJ|S86~L"yJ"~a= ':%29l,㕦 Ŋ.ܳ DZsDaq}֕jF|6~!µm L.4si!wWjÈ={_FR8lW3E^8IaTՓOU~5K0%Bްq_ M E]:G9KN* -e⥇w㬠fEK=P ^gH&T[>>@ 3lCú|;z᷌-FX|]h 7E{+ٯ)mǀ+]j}2L}&GɹwU N-okQZ HRqqPBnJ0'Զe^,)LY0#L#l.?cYj]٧rƿx'w+dߦs3 k t \fV`1^4Fc+tQ'HPSr$\șdEW`=+&&:;[R؅ľi'A6߉=B7zOf2 H`&V5Q1T|jqaCI 5@,! EVMryiV~6iJF`r)aA >Q,P\ CHKQ,Yr kE>c$0M?G:Ws*3T@7\Oh͜|<)¬~Y(^r8RɏC7PH#8*gܢ/hЯRh9n^GnPc%)ڴ]Ӷ( Nf+nEZc\cVQr]6} xg`.@MLL6,lCpR\oyL5%' jZHYEG/$,O:/kI/aYh=HG Zi}pˏ϶®Y0|,1]:g3%ǧf-p->&ᜡB5dQuU]I8?Lٟ>eD2< 㧈af_ܴWұhvkN"a˼d'~~fRX(c4sc#*c,Î ^3Z.W*\S5,ٜUЊ#g,ͤ3]r#vMF^E%\Qz$){@YN-c#۳_H-Hux$rF1iH{^'=13-# 20peb;QPu׬/srXf!Dl,Itb?۝\+(> stream xڍP[-L,Hcݵi݂{pHpw'KpBNH3UUWuk9O}MG*i nlBiU-N~7 یFrqàB t{X=ũ %w'/W E c`P+4lk̿ @F ?˟t# T-@O+-!m r "vnnNB잞ll0[1F'rx ,A1cC؁]kl<-]@' A]2ܡ mEW_,rg.IaNPo0`r*ln^n,K@K+) z sK&\.`'7W6W07EeNYj- stA\~O>7_u<07 kw'v]((wȓ - / 9@^@;u@O }`N'  psq'' tXlP?A6滀OpI^0(?eWR0e}RR0//+77 w?V K{"ߪ`{bI ?7>}q?OW)7$q3ܖ`OJvw{ Ul@7T$Ut|I-v{5n@$.< +]`Ӽק^q鿗aֿ王`b$/.^^/ӀZ(=l`.h;'`mFO*dC>:9Ny@._<=qs@^ " (f_qS#ғuoRtnO?wť'J*M+>]YKU (mo4͓fі $Nƪ#O/Y]F!瀼W`ŧ=/+fY_ƙX,S#2xa_^M=R*%1.5⊿]Yr!%1"&Gy+ђoYI,N WRA4ڲ^Pr/V+/PDؗ d/^mdQ\6 sI8A`r6YۃنY5\ #8~:ZNH/MiU.ceXZY `5ptf;# ;n~8xsm >)7FX_' ?Is-*6)H7| т 00U"$l}΃ >YP,"T덴9)t%Y@Ǚb6Kl#Hu$æ Lǒ G*cR\3Mݗ{:r5S/opgb']=RԙU?RxH]c)} 5IZ'q,+ ĹIiX^HlY;@%Tr $%Rp("1xz?ыw0uwc*N\ΐD 2r\a{fS ?7p$Yi7S^]\~n'U#T)YJGZVI /uMkf9ގ6 j-c/&-Kv^aa'ym QOڍ00<, byVq2_n۳{E6_Gv)kjӟ(%8a%/? Ͷگ֦^{A+ֆ##; XVvݽÆ| b+S!?' qEλP/xrO?T8͡<&Q^&?=vzW¯Fu,)ZiYzLE}ߍaojc&¥Hhבqw7#/U/Vteb62*b&WNzg5rk\(_l L|!yߩ&Nyc|Q/jvm'?keS+XؕTR.P~ދHUBHЮ>7J 蓞ZhQeϟ7qlWw{2% e<A3u=T O'kc =BS1V]wwךq#U볭Ѭ3:{kv1S, p 7^yC_=}Yq+*׽`RT v>oQu#i->OJWEdhZn_8;F "z5p:1Li+EEuaώldd7RA8UQ3H+de&n99}[иPJ\D e> ,zcRMkd)NK`)" o ;9En: 3ZT" Tv{ #M(3!R:)o0p{6)JISDJw=+\y'Kb\{nHAgڋ tJg*= ]?37)aP ?JٯMc+&ȃMEցX>Kޒ<Ⲯ]ks@jjk̠Njqg~6im{rons#(~Tw4 M]^GƚɄfgsR^gmX8֪ >?S;!1nfCl"w%گ8[aܪx#6 -&{ )tZ 'e+=72o[g3_U*Jy6qa9Fߺ,޻)1#12 !Q͕ܽX KKt Z|/42 UAl`! MEahf`gBvjOmWUoHHJ i#3"<2d{&J4kx.ʊj:Z[6ʁ˸0mx_6/T5WϗçcR9= XOG7)EJYLOu2.ĤCqeC=7CD?!?nsx ,_yloQ`JNeӄ6~ E|h$"Ƞ&03.s²İ$]\H{}[tb3`荬:r[3E6q fv޳; 694l^2X{2k#*eZ,wz118.Բf[0dcDDT]u/>ǔ~9haK8a]^:R¯PՋG aXj|HIzD c'TS|zI+^e @5 p'I݈s-H4$㰟NY ^;^گ='v_,56RļLn*,[8M}k5p1UFH5K])y\ezgiZ}؇>zɾ!0Ƃ )H ر{M5[r񂮮e֢[yRP(ݕPv L|+Biݛ&o $ɺ,@FQy2/7/H 2^l}8 OyX48kzڔ9픫r[AàZOH1XX}mP}-s\=n70+N9M5= V̖|aÜaxv+ Ẩ 2їNKej Ķ|)ZĀ}$4U+|0ɣX9bW@,"5)Qڋ_C;+DKB?dSv~ ߫Wk(YLf7ɚk跷9m^vsL )uWs9x)t U0kb3V,tl`㚗Ivx3xbp;=ِ:'5[XQwsBbJ2#**?\(.T0,Gond~ S^Y( dv:=%6Nr耴3M,6k3Y-R*CW 遫n.DO޸λ_*P,/Tvq8\N8̋|2i]p1 w.:@(ySke*b Uk;c4Yrvo5U dcзZԪQ%8؃psj?5.g{gNA\9%[B̮ÖBt-kABe9UGFB@| ѻ75K/c0'uraa?x -9XGߗ3ܙ?2!fUri6V@6TȺY`c xj7~&6B ̗U3o%??WMf#"O롦SCbg`)$R a= 1)RH댐 .!gP2xGknZ ehNC3Ɔa]i,2e|_:("WiDr^Z2=Eܧ[xa 8m+ة]"<^ҁ(uMsC;7<f`*MT8V./2eM _E쪨ZpZd4Xw"JD LN&Ԕ{hcLrrݷsswHUG>{_?ҏk;y|H獧7gC7Q=f@}C+y$㜖lE#'+/7ȵ[=LBtoQeP} W~[*/ս#ָ0QsB (`<(4p%\DE~ _{* ӂߊ\%\im9BEJúF0uݢEV2U_a8 IP/uJ7~Xtz[%2UN jI2 Ui! `3wsۗ:,55fd 9Zpiu;z𣦅0^tBk90e`G2lVиI zѺ:1i bkd b>hl3QHQ*/HGr g]l D<0^u%f.n=>UA'_MgLd 6`*R7l}YȮwԵVr>IEhI.I E^ۼb},F t'TOx.5V*J\ G ~%ݤ쌎W2U!LWF1Cz=U0nGY*T1&zN':GÎu.n!χV+Ü BCWg(5"vzxӹ\BE^523,ȜvJF,XKTpBu5[+)HȣƋpCt57k%Tz$X`_s6rh\*I}Ƥ7EĸsC@.m(\(T%r{}QoĒTlAv Uyow`g|yN[0Y=ۘCtUL6zIˢ[>fA}8. wuєᵹ@x-"KsD<]-;uoaGMqb> stream xڌP |pwwwwwwwww'hpwww $H g-yսw(IUE-̀RNL,|qE V ; %=rJ-# qW;H&a"*:9<V.>Vn> N| SO "@@)jce߯sZ+//7Q#@hnjPw2 kwwg>ff///&S7&'W+!Z5@ tZ*d4&JN^@H`octtx8Z]uY3?d4rdPR`rvg:ZE4wsٛz؛n U*>7sWgw7&7jd ͒N@Gw7q9:y9Y8ZXU3PVH[ftp.5_4|+YjsvrXXAL=wW`ߟE swwh :Wo> hX,4aN>1?%s1r.^.@zT'hO 查o%'44߀y67yz6>0@ E'~8_6?pZYwSІ:ZH7)ocyq8uYYXrv; tX߈N'4_l?VЎZm3;.`בrqEq~#no`x̒"no `2;hz#P<O7S@E$yjNSy1NQ[:AX__(m#'d9u ,rAQ)|nޚ\;R=nG|ȘoRBYlV Ɲu~pD.!2[UߝZ 6A< u0d\]lNnEt!q*t WeّMt+é˼y(<^vZV5C 9Af(Aqt9 L&;kg;ˎh%|Jaa+Øde7Ԥw#iٳzCz>ޓ QM!T)g`yTmGݫd=*?SvFLl_6gdHvǽ;:Wqu볰f.\0k-X$DjN ϙ+D>O5ʯ@:mSioLw\:H|!*h \͙bOʼn--ğ/3Mo?9=ֱ혮Ν'RΉd1XuKUj_9L*Ǖ%nHf^}ŏSә?rb;mAP#X0D E]W&Vk.}ąY)Vb5\Q$X9wDO^͖{F- s|)zFl%_s{Q!5]r!cNRI0.+Hip&cAFp N8Q>s< `1syQa'H́A#+mL^mHw.ud9gym?A*̚9i+R){YaT~Og3_d˵󸴣JKKgkebj_MLD3a7@A(TDol<:M1|PS˫ eKC>Hj3_61d/1j$ި 4qDӚb:D! fH&Xt}@,Ǚ6oN`4DLHf1n10*>nVEKj7~T"!ԡ:;+Y]"])g?Zx}oKVxPcRRLK9"lja|,%  U/Aw(t+1DBܚgAs#5`XQJ9'ڟk~Vnim#Ue+ymrpH, E A%>@qR6N5E+Vv&ҡ,JsOĴyD`&|"D66;[n̙FX=FԹ\00&uP`ju&Ojo~H'4>b8b7Y;((>\)^V)#\y595p.}F  K>`&Ԋm4up _1kTD-S5, ɠYt*RG%'. +#GiyfL,3HqYۮ: uX}Xw)(;CvYmlN^)9-*#b S.6֙JC9Dϴ8qaӲ0s=*}Sm]8j2^N3e!& B '_>ooI/݀[ q|Ɯ-zDgTo{+ ǝxdGk!*E^*syz%ǍxnaP"^`$%mv Å [@{wK-„lni'>Xo膈jRvWyVfe,h2/BdQ9 T 2:f3J37t;rl+\.- ̿rb˧C,I9Or 8@wF.­'O\׼'9S:p.h}=pGbQC{hhK=0w`-Z#cWPX.M&RwDZsq雷1 tQ 9 Oj[L~&;o$m?(Ϻ؇^" V]C#H-I6#@+dnӷjWݪWh3o`ugxZ/Y^a,Xq1['1*fUPwYw,.5 X&|Jm_ Q?6@. 6\q }?g9莛"8Z ->c1HgÀn7^_;odKY' nݥO˃Uԅ~x]fmTCXG| eHÑ5:Ԫ)>V^2]PIf L({i>_rdo2ٔGv u\~(uv nw | <_-$(0u(zP>Ód0kC_ E7f*iWJvFDSּv~\)=D5寭G3|QF~J8jByx:E;.4a=.%'1, q٧[^%YqTF؟_Vy^&Iq%W"~E#󧃨nM'˵?Ɋ͂5>d{9aZ>SU.B ,| zD '![dhp5{j)oD'[dV[Z1x7*-}_dRE+ +s,A4, QZY2ޅIrtxlҽ1QRR04Kz3_ W:MțJ* `r|J[i_5~qޝ T0TxsI!/Yנp>j/_6 z!j8:iG?Ak&s| WB{|,64@ 'a  Qa=]XabUX޳Jܘ=Vt oyاƢJ_:ՙ,OjGt$'J_qד0m <* c`<0"|zA 5,TIρmu-tEr3bQ%Eג*.*tq5Whnf[韔|Q7r>XI 6x|^}ҍI#{L& @Jՙt}O}@ ,~O]Nd`y ~N9CUrG#J 1?v"`>M!>Dl xy_4Wj_^v]Pf:[nme~?vWnׄdg0{#`zYo :?z˝%b۰{īaChp^< =r㙉DPYQ:yfF0#YXhhM$Ǿ汊fxU4 >/ǝ5O݆|ƽ/H#6ݗh>ŴtGӶ .<Ǣ'PM{hIb{:N)P(pb#OA3NJWApVI }JLV#~ucxVLciO_gp|Շ t(ݒ{}7v޸H2V=dw hQ7z_a# O)(OcHK7.^ !8gYyHS!":,/to`Qrߨrd #ț cVt=buRڎ j9f*u'VvzNN;X_+ [>ZEB~ZyYof(D+>2.\71Vb6q@=?8cc|ra9/34u:\չCdSƹQ5<13k#Xq>A"bY PP|~g6^(eg$'{츤IjEtÞ:B$~UCTz $e0 eha5of|iIX_+Z U1^3!v,s{U~i7.RIC80*23=!Q|=i1d;z%k0 g:H<~)(rvn]WyQܴᴻTq ڳ+ȇ iu-V!nSRɘ{,hyt|܄ wyِHդmPX]Xo_j]8&uOe)Ju`-B, lj_hm .(.~O|{[Zew\f~v~Ao; P(zoۨL d87A1q~5ANǼEAIIAN?$RT%id`'=eϛ}0%?"?*Ž)mE -ICd"8gEPU8u]`oo9.jNNw )lO_w+`0À;~ aa2AJȀ:W,0gjҜ#@lSV.H\zݎ0/%o${!m6MFÐ8|̌hM,EI{2c㙻0hZ/KU 0sc/<2Ld9l e#mQˮ% ť¹'šf; AhjZDl3OUv*Ž a:;X GX?|=a4|st*VxrBcŶO̮oH-WuzT*-MCza-Åo6EH[eĈBz{3Y )p 9˛ iUvq-c{[A#X܏.vą'i]jtYo"y֚^_wx3HKev|M_nwڨ-oKVe#3x5opeނ ("ygߚܱNJjc~m[Z5Vd?y][ Moޯ9ڊHbèMF6HFf6>((*lD:g=ؓO\8)GT ;wHi:(bSV YWY&=,)_ PyئZ~"5[!x!}.BrkIѐi) 'Qqʢz/tO%1IJi`$nn'Pt*Q v~=']qFlDwt iLnJ)*s{S(28wrock#!46xl԰󅏇m(n7H$hYr`)ϗaXȬ`pSkCq |!f~'{]E,1${eZs9R/L43wY*%1Rv"ȏ/'TMo!0e2_s=֡U\vK,?* CPDxW_km'D0yBVpvߚO} &n@" [C Rz3i[h|Qi}Z0a;R,RU?ݮjN3C {o|ȭ۽*\[OVkKαߔy"IƸ+ spWY!WKieF䀯:煣ԻVhx&!ƭ|.c|Yvw]e3~$lhYyMBBqdE5Wi%ʪy_o&Kܹ-"Scn]ժ^I0.*p*c E#o$=T)ȋTxQR*AAV\_͘JՇe0pkbIh3[ ȏ(m)&ydzS!`m'f"ރY ZsYN.U.uYW/!w8J*%NFc!f#U^|>G )`LÎ*0*&Ǎ9wjU8]7՞'b,+QEX^z9k%U~BMufYbb\7|ԀM80f 仈w}gN0 FuĄڭ)"|*+>)2;El6)rr\A.؂e+b8&@TN>&" 9G//.'ܛ4SS_kVu:ߋ4f#T]a*o-# f*)9 2 I5E7n6Ǹs.k2?sj^eF91k̠}+Sr= (*4ц!E:6!Wh,2Y {xSR-皳4CKszxg˳ 2{U*Ll,Z˳d3Xoեh!,2jB,4c(ўH1\߀Y ]V3ؒr[P>vv8R}bfŢ@lܵV%H{!! J| S&IU &(^ɸ"jgׯ&iLx-Q0a_Ά(gȘZ` ,NwL3R}QZinlVh[j1Gp?U qSJtNG=g~a2p].iX1f57.ȶqcF}ctϽA.Y}ı]Z$ɞehLgZGKt̄QCwVQꔨLZ"B8CO# eavVQ 3VX 4:lӕ<ٽWQלվPXvH2$>R+ P-cV^B20MCT6lֆ%7m̏Wl= ųg %jcdGÀi(۰E͍U켽ί5nQm0\;{^)ʗd9U0c\N0 ^8dAnϑ91l̴ i;uQTq;H?ԥ`c$Y+(3Y=SoP#ʲZ)X68P:.ng ZOaC?R zV^kCܐ6MGgV64CD S? ]a&g&cƃsd^m>(XVjl`B qz /wϩ*<?7\Xg-Pn2{G]P\'VBV?㣐pjkO̠җ腨_ MO:|JtSnCbKTB'a7˒dT]hMuSѮIP ʁKv!^J6IAFZ#s,l%#uȏ T`"aH5[7ވoduu2\3qMӴwtj!NZa(X pn$q~kg5՜Z7+|{hטv&([ 6b{ 3ľ#6ckv 4NctHb}~Li|/z/Aǿjv <eI!6Bxܿ nvIJC8r{BAȄ?x=x9<2ᄐbJC %RZnc~.m)ctO0bmb6;&8m+yBx%i-7կJ*c1ܾUN \VOHx7P>H1=cpU֦<(Рr#ONuEn&Eip UCw7=?VY!)gh BESQvcSp 4=.gi,IWe8(T)ԼX"aS Άgtvn  S`ΔTxNm ,qn L;tş݃"x\|nᤐLaE &7IЋǣyZR-m2m]&ИBDQuE_9By쉇&FF8TtGjVpb?0l|/zK3yL̦!!GA/;|;C\}5[ C 3u I-L$dj?d}a MT\.|WZ!o; hmT ^1#, nnqpF7o q:? -N5q%qLJ%>Od S"r&κS )S\bf#k>+ |n&=XI.Cɒܷ3#u-lBbKheP{P T:#hĩaUU'tTWF|ǖY1fGUtnP+[a%I/yfTpۈ&H7߾w7= VC!}`N]=8nGlhC:Ί.}ϭd/+z5D՚oG# vB˛*68d.bW}DJRQphRw6"ዄр +ܣx>72p:~;u{BF0G[ƭׇ+ݧ|If[24Q]Զi8QȦÄlx9]$[L KmeGEM2ϮOӝKֻEVR- eDLGkHL3αzCb9]O@]1֭b|qxh'9XE6+2[#fК{K[ R/e/l8 FG+ &-5 e+Z<E{2u^?Gˈl7/=ZGj?djLBit䃟GL8sW?,c\{K ꙽5G 5|I+Ff#F\'$95[+$Es'ׯYK);FіPJwgiBhzQcg'`Q!~CethL-:,T^}F7;*U<}oƕ.o:̄)z^b7@KROhʷk[c}[bO,1t0}OP/riS>'2˓jͳ$ yJg/2;҅MqH"rZiI>?R 錨5~7jrMa+^%$abQ8Xbb+ϰ`}/ȕ,XR܎,s]gSl]{RxB!ɇ+WM%7iU]%9Kij).;M6F"/Ɠ2 fosDÅUq+#ˌ0_Ca{DCS3!t~w![ sZ2DGeH(Rr}% Wui4Rn/dmԚѩy'!kl=._ԵRuP(~5dBX#|-cLvL^^gIh sdIjS!D 8l)]7y ܐ]SW=f\9֤ e}U1Hr)f-A0\Zԯâmr׶mHmb-9pox/g\Աi| 7)|m˥d~zx>e,*CMp}/:`ED򌗻(t5:! _(;L"[)1޵g0@+-igNǼ' ·qxIOQ} sWOFQqaaZP6z%ۍ-Wpvz0 [\0 ?1xBRqHj֣% LؽBPXh.neѻm XoAV 3;z#80YOI*ܲg6u~-O?,yT п(%Uhd=][+P}>Fڻa` ae|4 FO58AzG8{ь- r?jVg{6ik$yS u^Xh#R5~"+>wی0@ N 6;[`X@ ؓ [{LM{(6/@-@%8sٶ䠨p ;!jLF*F@M3؏e/i|4y*[^3+#S,9HG^u\v,OQ 8=AuM`j~vXQ7H T.mnp \#[CE\QJlE# }V8s tԥpVߓ?ҙ*8~Eb7V? lenlQd9 e%5PY65 m|IuƙXCME5OdƸӲmbdr eJ/+.d?]tA)cC[.K&`6oߒ%(vm9mKoXStNfMI `ni6c#qX3^ _o\޹1}ZmTOPxȄrG Xz:vѪR}vmµ:/RΗ<|ɯ )6fP UM;Bryʪ8W5'J14J`j>=EscηzVD &'Iy|? MZ$kw o/ 7)8&xqZjYnZpOkW]37dc Ј\Fs0gq!bTe%)}oK ^E_YCSqH? }sףhփi85 ]W`WS5ʐ%E{=rcx{s{PWׄN \ t%bQQvzq1y(7N$Haְٶ`F[{%E^s@A ̷f.~uĀ`Bv,oT&cM7\FR NB_C٥XMㄘb.1(x EG,pjy?,yA X0\rQפ5P:!rS2jϛX.椽OEʮߟft\E7'z~sn@M9N?^S3l)B,l9BoOC.5Wja$ÞXu Mxђ)4ׅ.38^ۧyم65Jn"`?Io)D44"Εt@lR\V  RI#Wwꆅ&$;7j.s_Typ \GBݾ/[AkeGۗ/3sk, 쟟X`9Lc[-vV@%8L=f.;Ϲ#Ї꫱C }zp+If|7V>E\!Qhg&L~X4u jMδE~wX#E'e=ĨVJ;V">o@$' 62]ij~劙W:Ft5 ZZ%횯^Fڰ|DZ eǛxr]u+ROG:!PW>_4}LKt35G&t 呒ڄzB^YĿm aSfĆfR͎;῕TF t`9UTEP?$2:ty—M'|,o,XjM R`<:HeZ<TO0R93q/|@«^\`"_qoVȢX&,Cn eAޝ>^ -#ts&K λFWo.ͬY'LJu(|X >\vagoN!+:y_x$EJONvO͗pcB/װ>қ|WK\&b=ٱ`p6 2^p9@ӇZ)S =\`n07LfBYHûD2x&EĂ2rA|=ѓ6| 熚!p9i^쿚H2 n+A8:{[wUO nۃJ7F>#$~-E $(5iR<,[cby-{zPD|t[ڢKcXYySԪ_Re Y7K=r^Lxݖ{diضHLC52)9*p恱ork N[ӑѨ)jz!?EIg35ACz)jI+-~?/C@9o^$*/FZ%2'd /9YpvI?2[11Y]sG87.:\'OZ _ZF-,|徃?r.`|=W7R0Ux4d ے~P8_'[]1] *B p[}̶)J7Jgjϧ.!Ufu|Au2=0K)8IilS[DyA"^R47K!RN}_żzbI|36v^Qe3Iz=8-I,3+‹H.*9+=< MhAv%+}IͮRē u A. Nd<6*3wP=uILC}V2 Зi Xg/>gR.U%acW5gşmc[aqxbd@igFaQ]DkS|S&1z  Njbj[O-̈M@$L+1JΨ~{M[[E /g%UiX߄& '+voaPq`3*7 ]]wp N:A纇G;P\wk$nƻYp]@ƢX%)|bd07 4'x%pn؟^>$^)x%xbbP پX4(Rf*s&lg4̀RrcsNHFc WUp_CQ-Q+7$E֩k\H id_,r-y>Ф`KQum>|uSUX iycLo"W|,VZ)B1dYn1Qlԁ@e_ʅOS, Ѭbµbe{!akHuF`TɒAsеQ>&YKqV?ho$42$${!`w*LkWt iv"0?gP*LR, FfU nTbW )O HOUJ*(V$ "piIG] FVޥ?iqvҐ'C&lhoש'.l{NJP9]c0&N g76є o-mco&"E4p(XEk6Kyb{sOE CURoC&zwap=lak<*:9ղ< Rtfiag6m?89Z"0 HF\&bJay\/㑊eT{eJQ-Pc:gzXwkyZ|q_n:eԉ @fj÷;2EFL9\~.~af Xwk0z bd2,@F{4K^9 zFW:=1Kѳ &n,?6\ rȚVcU5كR]ЪAzOKSX6р~08\jTFH CⅉSŠ@o} uc3Β(l<0Z*gJ?*췘ܟHng/Չtmoag^U}J?vtN 'v_,M.zdՔvI4I7wXڵ|՘ ^7$̯8#ܾ!MLQX#*!E@<(LMd=,G /YA&v +'2rQu]5QI!?; N~e|`, 9cD(Qţ΍;Sm<cT+zSbxT&iK*qo$> stream xڍu8JBGF;+;[YYswr!JHYl2#"\dh~~=ޯȒ0)edaP)(T$"b&cP (" )eED46L34} 0y%i%(BJ6}<Eh D+# `ED#8NvEaip `GQdSq% JK]' H( PXOjR M0;=D@`Ds("@3PƆ? 0)p?wqp%qh ƠRd/2;p O{`t8a i #!h$EBcv8BvڬCjXLԧ&{C~ BhyBX964EP rP^WN ojW 8FvF~@$ )(߿F  @d ƁDQ?0mD` |~?]mx11D켖Oʿx/WHJrҲ(+5(@/?Bш`m3 rP >]D_R0]GǢ1?-hL!.OMP?DS՞#isn$B!Md-:CI BN!цB.up!s 9% WcƖ76 ze,+_,}[nq'd@ZNH6T|uŞ0(/.Ȥ`wmS=UcT5<ٳfRpMJ^!{ OK}pU>#IT2&\iXiAjԡ7<?R]]4d/I~k=Wwwس.iQοΙu=z=+ i[*;3XrI=oJ4 `&{yĕPPWWs*ΰ(ٮLs5QuFӏw/LQf)QyĘDcQ=nW%pA=9!gDtuE"س9[o~e5 OD I*W߄Zo]x6ђx'.Y-ӟjuK\鱶Op;XMxqb\ԭHQWdeqiLU4"ߧS)1{*!|$P発 Zz>OڔWu۵CU\f|[k}l1֖B9QLo{rXE1 uݨ'h|:߯KYԒtć _~`._YpmAQduV^ʷϭcυ7' oyX[P*N6𻭡qNnǶLyZKqpCϋ.kr\ -w#*h8D3eȖf$0Ǿe0W>o;(%wږl29#j՗ NUh:-w8Wڑ&ˏ1+U<>v `f'BMm-Y> jo3ݸ3SЧ2tVz{t=ȵi*O`h4m(ոPJ+BꆛQ\-RAU|Ymö鰤]PUh@C0Lġc^t{zM 7tMä٧>  m_;E)96;K>_TOiw6ڐHKNBT0e|@ω晭,QSjBn=PNk;nO7*_o:$;ֽiH,-aq9!:zP4UKn͓I}b6+__9$ғ@-  %.Y"ލ,TxvE+](9my/$а~ tx!^ztEA3ϔ>(6PX.oNrv^Q™5OY7}R7$,I۱z<33:c6j9xGcS$ Yсt+3;;1M`.E!tVDZƻR}NFhj~d*x`؜cBuYzȧ g(Е镮ӛ:WΥheMfV_6t7Us+Yo'vTh*YK/Irzz*B8! 1*yTx@T2U6?\R.\vW~l1!~!!pY9jxzYWMpБސ/lf_eD8&_|h&JCLTG9RD4\52 .Քrg˗!/7]Xm-xi^إAlBy:#˲B LuIC.Ucy,E?w>`]#x8$yhAm!s# mS2YmJУ(n=g"?')i.QA )s9jy?Vg܈bKl6.5קЧ\%#@^/M(oOY 7؎ Z^e ipLba u=ݛտ9ۼ'65-8pNkre Vs-)K!2ax%[ DZK:۲ij|ܟ;<! K3'͊ r *ܫ~Z^]gD0X{ӧh-q[(6m+e_dWŚP^`O:Ⱦa YQ JY4 %E0d_k(P/[v$+x4B͐M e\7mot;uЦ,z \؛i5Х1lp'x]XlzZ\})ČpC:JtHV(' q3+G٪kmr73`vj/Q,ٖԇj?\NIfYHrQ"('M}]b3v|-c[{!j.{9`+71N]d4Lrs\}pjrỰp2@=rLwMOuJb7kWZ}Z/+w w皛0eCFX>f@'nïV6%%wc}4Oc.xP5&‡{6fR-V$5&{:ښT >OXIv0e% F!ϳZBz> -ɳs4a#PxpstX8-2o,qIT(}3IaYث7 ןeUL£s,YuM2"U>3ds"nb&]=Lks6_ӽmf5,wnٓO7O2=rgںU]ityO+Fmt|;{JVm\r\4õ_7Gno9kEgڳb^XN^,})Ҏ<{~2TMMF6JM. :,/3nӕZU`a(2[I^농!XKÚЌw"Ha WLkmFև xq=@*Wآ:Cè V}^mDZMr};"ȴ$aέZGb`Jݏx x\g?Ţw1&A>0}l,R3 endstream endobj 57 0 obj << /Producer (pdfTeX-1.40.14) /Creator (TeX) /CreationDate (D:20191206131600+01'00') /ModDate (D:20191206131600+01'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.1415926-2.5-1.40.14 (TeX Live 2013) kpathsea version 6.1.1) >> endobj 10 0 obj << /Type /ObjStm /N 39 /First 289 /Length 1917 /Filter /FlateDecode >> stream xYn9}Wq `v\v8C[ؚn))-(xhX,V)VbI=)aLa+^20L) $0eY WŨ"t0)cڕLftj!1Std81fF2QbgVW fr T,$4)w V >@#ƃ֝O;e^2~QܖޛMtQ3]veYϖՠxYG잽`h ւ;Ra:%~HΠ=}@! R !#4DB!f7傽'g~U01V͖sJd7_zN_'Ѡ|v9[Tr'$`{}S9Bv нDBHXͺ,TON)'6xsQm8EȀQ=ڮc.M1 x:pSOs%ذU%lfMtۤ=rk{ԎYzn3yȧ  DNJ$Jf[8q6ڵ)~)Z$^ݦZ!HʚhѭK܍5UkRM,ҭ$ 2kd>ujj=cLKZR?q#J Y'9)p[A'(EH<9 'R#Fv|PPIrdXJ2.dvYp)F_RkY7R9QFb-Ǎ%? f`YK!ZIjo[«V I )GKZ|G 8dCey@;F')m5թiJ'*4YA_VtYMR**[ul 7umɅWn{;>c>S>i+,_}?ٸ5/ꟽ@(.z$HнM|/WEµe;'yYfCi9[Iy]~.oG/QD?S6?1 #ml2)R>64) )zϯNz9VoFo^џyα!Ο<=U Ьƞ] B:qN/<9_bx/e*`+/&yQS 7c=.;~Sr\~Xd*Ŋ :P gU" gqQ ,ƼIuIVe(ͻ/;T݈p\: ݶJ^+ʎ4</k)fd\wƺYs_vRCu*'ɞʗ$8oOo>}*oQ6+9+`|yH]bqQ +p𜍪zA ;EtDh7oW[ݶmrpm[ɵmMvm6lm#nڶb۶lmoo[նq7uƷ nn}Ůq^Y%껣wѰf 24#F*3Uu jnR~-0C63Wf l N` " endstream endobj 58 0 obj << /Type /XRef /Index [0 59] /Size 59 /W [1 3 1] /Root 56 0 R /Info 57 0 R /ID [<8778F779FF2E210D53C39557464CD6AA> <8778F779FF2E210D53C39557464CD6AA>] /Length 176 /Filter /FlateDecode >> stream x%9an6u%PR LM*RR~|^= P@ EW D 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) \end{verbatim} \newpage \section{Further Examples} \subsection{Stock Price} \begin{verbatim} require(ggplot2) wb <- createWorkbook() ## read historical prices from yahoo finance ticker <- "CBA.AX" csv.url <- paste("http://ichart.finance.yahoo.com/table.csv?s=", ticker, "&a=01&b=9&c=2009&d=01&e=9&f=2014&g=d&ignore=.csv") 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") \end{verbatim} \newpage \subsection{Image Compression using PCA} \begin{verbatim} 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) } } \end{verbatim} \end{document} openxlsx/inst/extdata/0000755000176200001440000000000013560564727014560 5ustar liggesusersopenxlsx/inst/extdata/read_failure_test.xlsx0000644000176200001440000002267113560564727021171 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.jpg0000644000176200001440000014613513560564727017112 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/load_xlsx_testing.R0000644000176200001440000001176513560564727020447 0ustar liggesusers 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")) openxlsx/inst/extdata/conditional_formatting_testing.R0000644000176200001440000000626413560564727023205 0ustar liggesusers 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) openxlsx/inst/extdata/namedRegions2.xlsx0000644000176200001440000002404513560564727020202 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.xlsx0000644000176200001440000132174513560564727017750 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.R0000644000176200001440000000311013560564727021446 0ustar liggesusers 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 1:length(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 1:length(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) openxlsx/inst/extdata/namedRegions.xlsx0000644000176200001440000002002113560564727020106 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/stack_style_testing.R0000644000176200001440000001036013560564727020765 0ustar liggesusers require('openxlsx') wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, 1, head(iris)) ## What we expect # yellow fill and bold test rows 1:2 cols 1:5 addStyle(wb, sheet = 1, style = createStyle(fgFill = "yellow", textDecoration = "bold"), rows = 1:2, cols = 1:5, gridExpand = TRUE, stack = TRUE) # yellow fill and bold test rows 1:2 cols 1:5 # red fill for row 1 and italic addStyle(wb, sheet = 1, style = createStyle(fgFill = "red", textDecoration = "italic"), rows = 1, cols = 1:5, gridExpand = TRUE, stack = TRUE) # ## add a bluw line at row 5 addStyle(wb, sheet = 1, style = createStyle(fgFill = "blue"), rows = 5, cols = 1:5, gridExpand = TRUE, stack = TRUE) ## non-intersecting # ## Now borders and underlined around rows 1:3 for columns 1 and 5 addStyle(wb, sheet = 1, style = createStyle(border = "topbottomleftright", textDecoration = "underline"), rows = 2:3, cols = c(1, 5), gridExpand = TRUE, stack = TRUE) # # ## Now blue border only on top for rows 1:3, column 1 addStyle(wb, sheet = 1, style = createStyle(border = "top", borderColour = "blue"), rows = 1:3, cols = 1, gridExpand = TRUE, stack = TRUE) # # ## no stack! Wipe all formatting and put all black borders rows 1:4, col 3 # addStyle(wb, sheet = 1, style = createStyle(border = "topbottomleftright"), rows = 1:4, cols = c(3,3,3,3), stack = FALSE) # # ## cell 3,3 red bottom border # addStyle(wb, sheet = 1, style = createStyle(border = "bottom", borderColour = "red"), rows = 2:10, cols = 3, gridExpand = TRUE, stack = TRUE) openXL(wb) wb$addStyle ## Now not stacking addWorksheet(wb, "Sheet 2") writeData(wb, 2, matrix("abc", nrow = 4, ncol = 5)) addStyle(wb, 2, createStyle(halign = "center", border = "TopBottomLeftRight"), 1:5, 1:5, gridExpand = TRUE) addStyle(wb, 2, createStyle(textDecoration = "bold", fgFill = "salmon"), 2:4, 2:4,gridExpand = F, stack = TRUE) ## STACk == TRUE addWorksheet(wb, "Sheet 3") writeData(wb, 3, matrix("abc", nrow = 4, ncol = 5)) addStyle(wb, 3, createStyle(halign = "center", border = "TopBottomLeftRight"), 1:5, 1:5, gridExpand = TRUE) addStyle(wb, 3, createStyle(textDecoration = "bold", fgFill = "salmon"), 2:4, 2:4,gridExpand = F, stack = TRUE) openXL(wb) ## TEST NUMBER 2 - BUG REPORT #203 wb <- createWorkbook() addWorksheet(wb, "Sheet 1") writeData(wb, 1, head(iris)) ## Make a red block addStyle(wb, sheet = 1, style = createStyle(fgFill = "red", textDecoration = "italic"), rows = c(2, 3, 4), cols = 2:5, gridExpand = TRUE, stack = TRUE) ## Draw a yellow L around it addStyle(wb, sheet = 1, style = createStyle(fgFill = "yellow", textDecoration = "bold"), rows = c(1,2,3,4,5,5,5,5,5), cols = c(1,1,1,1,1,2,3,4,5), gridExpand = FALSE, stack = TRUE) # addStyle(wb, sheet = 1, style = createStyle(fgFill = "yellow", textDecoration = "bold"), rows = 5, cols = 1:5, gridExpand = TRUE, stack = TRUE) ## Now borders and underlined around rows 1:3 for columns 1 and 5 addStyle(wb, sheet = 1, style = createStyle(border = "topbottomleftright", textDecoration = "underline"), rows = 1:3, cols = c(1, 5), gridExpand = TRUE, stack = TRUE) ## Now blue border only on top for rows 1:3, column 1 addStyle(wb, sheet = 1, style = createStyle(border = "top", borderColour = "blue"), rows = 1:3, cols = 1, gridExpand = TRUE, stack = TRUE) ## no stack! Wipe all formatting and put all black borders rows 1:4, col 3 addStyle(wb, sheet = 1, style = createStyle(border = "topbottomleftright"), rows = 1:4, cols = c(3,3,3,3)) ## cell 3,3 red bottom border addStyle(wb, sheet = 1, style = createStyle(border = "bottom", borderColour = "red"), rows = 2:10, cols = 3, gridExpand = TRUE, stack = TRUE) ## Now not stacking addWorksheet(wb, "Sheet 2") writeData(wb, 2, matrix("abc", nrow = 4, ncol = 5)) addStyle(wb, 2, createStyle(halign = "center", border = "TopBottomLeftRight"), 1:5, 1:5, gridExpand = TRUE) addStyle(wb, 2, createStyle(textDecoration = "bold", fgFill = "salmon"), 2:4, 2:4,gridExpand = F, stack = TRUE) ## STACk == TRUE addWorksheet(wb, "Sheet 3") writeData(wb, 3, matrix("abc", nrow = 4, ncol = 5)) addStyle(wb, 3, createStyle(halign = "center", border = "TopBottomLeftRight"), 1:5, 1:5, gridExpand = TRUE) addStyle(wb, 3, createStyle(textDecoration = "bold", fgFill = "salmon"), 2:4, 2:4,gridExpand = F, stack = TRUE) openXL(wb) openxlsx/inst/extdata/readTest.xlsx0000644000176200001440000217253513560564727017272 0ustar liggesusersPK!wK)H_ [Content_Types].xml (̖N0Hq )v9{Xul3@L TUiUQ _%7)ǗUϐ߈Q=xF} Iy\Ј%8?,#` )Q)C+Ӑ:Ef2*=W3GxP!&0UO? ~FhVP1:1|?ANS5` @cC!z&p{W5Wf0lmCn~ns$kSnTǽ˅/!CכEvMQ)?7(mg,#Q!? 8.B8~qVy!a) QGK% 2[E9*'&C+Z><&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.xmlPKi

`]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(