In this typical exercise, we will learn how to read multiple files and plot them all together. This is the basic step of data analysis.
Reading the data files
- Load the
tidyverse
and readxl
libraries
library(tidyverse)
library(readxl)
- Store into
flist
the list of all .xls
files in the Data/
folder. Take a look at the list.files()
function for this, and especially at its parameters path
, pattern
, and full.names
.
flist <- list.files(path="Data", pattern = ".xls", full.names = TRUE)
- Open one of the .xls files with Excel and study the structure of the file. At the top of the file, the first group of 2-column data is the blank measurement (columns A and B, lines 8 through 3408), while the second group under the blank is the actual raw measurement (columns A and B, lines 3413 through 6813). Working on the first file in the list, successively:
- Read and store the blank data in a tibble called
blank.df
- Read and store the raw data in a tibble called
raw.df
- Combine them into a tibble called
alldata
containing the columns w
, blank
, raw
, and transmittance
, the later being the transmittance (in percent) with respect to the blank measurement (i.e. transmittance = raw / blank * 100
).
The final table should look like this:
blank.df <- read_excel(flist[1],
range = "A8:B3408",
col_names = c("w","blank"))
raw.df <- read_excel(flist[1],
range = "A3413:B6813",
col_names = c("w","raw"))
# Base R version
alldata <- tibble(blank.df, raw=raw.df$raw)
alldata$transmittance <- alldata$raw/alldata$blank*100
alldata
## # A tibble: 3,401 × 4
## w blank raw transmittance
## <dbl> <dbl> <dbl> <dbl>
## 1 4000 100 94.2 94.2
## 2 3999 100 94.3 94.3
## 3 3998 100 94.2 94.2
## 4 3997 100 94.2 94.2
## 5 3996 100 94.2 94.2
## 6 3995 100 94.2 94.2
## 7 3994 100 94.2 94.2
## 8 3993 100 94.2 94.2
## 9 3992 100 94.2 94.2
## 10 3991 100 94.2 94.2
## # … with 3,391 more rows
# Tidyverse version:
# alldata <- inner_join(blank.df, raw.df, by = "w") %>%
# mutate(transmittance=raw/blank*100)
- Wrap this procedure into a function
readFTIR()
that, given a file name, returns a tibble with the columns w
, blank
, raw
, and transmittance
. This means you just need to do the same thing as before but providing the file name as a variable, and finish the function by printing the alldata
tibble – if you don’t print it as the last action of your function, it won’t return the tibble. Test it on the first file in the list.
readFTIR <- function(filename) {
blank.df <- read_excel(filename,
range = "A8:B3408",
col_names = c("w","blank"))
raw.df <- read_excel(filename,
range = "A3413:B6813",
col_names = c("w","raw"))
# Base R version
alldata <- tibble(blank.df, raw=raw.df$raw)
alldata$transmittance <- alldata$raw/alldata$blank*100
alldata
# Tidyverse version:
# inner_join(blank.df, raw.df, by = "w") %>%
# mutate(transmittance=raw/blank*100)
}
- Plot the first file in
flist
, and try to make your plot look like this:
readFTIR(flist[1]) %>%
ggplot(aes(x=w, y=raw))+
geom_line(size=1)+
geom_line(aes(y=blank), lty=2, col="gray")+
labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
y="Transmittance [%]")+
scale_x_continuous(breaks=seq(0,5000,500))+
theme_bw()+
theme(panel.grid=element_blank(),
text=element_text(size=16))
- Now we want to read all files in the list and store them in a tidy tibble. For this we will start easy and use a
for
loop. This is the step-by-step procedure:
- Define an empty tibble called
df
- Define a
for
loop on all elements of flist
- Then, within this
for
loop, successively:
- Read the current file with the above-defined
readFTIR()
function and store it in tempdf
- Add the column
name
to tempdf
containing the current file name. If needed, trim the path from the name string using basename()
.
- Stack
tempdf
below df
by binding them row-wise (look at bind_rows()
).
The final table should look like this:
df <- tibble()
for(file in flist){
tempdf <- readFTIR(file)
tempdf$name <- basename(file)
df <- bind_rows(df, tempdf)
}
df
## # A tibble: 34,010 × 5
## w blank raw transmittance name
## <dbl> <dbl> <dbl> <dbl> <chr>
## 1 4000 100 94.2 94.2 sample_0_25C.xls
## 2 3999 100 94.3 94.3 sample_0_25C.xls
## 3 3998 100 94.2 94.2 sample_0_25C.xls
## 4 3997 100 94.2 94.2 sample_0_25C.xls
## 5 3996 100 94.2 94.2 sample_0_25C.xls
## 6 3995 100 94.2 94.2 sample_0_25C.xls
## 7 3994 100 94.2 94.2 sample_0_25C.xls
## 8 3993 100 94.2 94.2 sample_0_25C.xls
## 9 3992 100 94.2 94.2 sample_0_25C.xls
## 10 3991 100 94.2 94.2 sample_0_25C.xls
## # … with 34,000 more rows
- More advanced: you can do this without using a
for
loop using the tidyverse
. For this, look into the map()
function as described here.
df <- tibble(name = flist) %>%
mutate(data=map(name, ~readFTIR(.)),
name=basename(name)) %>%
unnest(data)
write.csv(df,"Data/alldata.csv", row.names=FALSE)
Plotting
In case you didn’t manage to do the above procedure, just load the alldata.csv
file in the Data
folder by running df <- read_csv("Data/alldata.csv")
.
We will now plot all these files together using ggplot2
.
- First, you can try several things, like:
- Plot them all together in one frame with a color depending on the file name.
- Plot them all on a different frame (look at
facet_wrap()
) for each file.
The final plots should look like this:
theme_set(theme_bw()+
theme(strip.background = element_blank(),
strip.text = element_text(face="bold")))
ggplot(data=df, aes(x=w, y=transmittance, color=name))+
geom_line()+
labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
y="Transmittance [%]")
ggplot(data=df, aes(x=w, y=transmittance))+
geom_line()+
labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
y="Transmittance [%]")+
facet_wrap(~name)
- You see in the file names that there are actually 5 samples with 2 temperatures for each sample. Based on the column
name
of the df
tibble, create the columns sample
and temperature
that contain numerical values of the sample number and temperature. Think about using the function gsub()
to replace (delete) text in a string, the substr(string, start, end)
function to get a portion of a string. You can also take a look at the separate()
function from the tidyverse
(see here).
# Base version
df$sample <- as.numeric(substr(df$name,8,8))
df$temperature <- substr(df$name,10,20)
df$temperature <- as.numeric(gsub("C.xls","",df$temperature))
# tidyverse version
df <- tibble(name = flist) %>%
mutate(data=map(name, ~readFTIR(.)),
name=basename(name)) %>%
separate(name, c(NA, "sample","temperature",NA), convert = TRUE) %>%
mutate(temperature=as.numeric(gsub("C","",temperature))) %>%
unnest(data)
- Now it is time to make nicer plots:
- Make a grid plot showing the data evolution for each sample as a function of temperature
- Make a grid plot with one frame per sample, and the two temperatures in different colors.
The final plots should look like this:
ggplot(data=df, aes(x=w, y=transmittance))+
geom_line()+
labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
y="Transmittance [%]")+
facet_grid(paste("Sample", sample) ~ paste(temperature,"ËšC"))
ggplot(data=df, aes(x=w, y=transmittance, color=factor(temperature)))+
geom_line()+
labs(x=expression(paste("Wavenumber [", cm^{-1},"]")),
y="Transmittance [%]",
color="Temperature [ËšC]")+
facet_wrap(~paste("Sample",sample))
LS0tCnRpdGxlIDogIlIgRXhlcmNpc2VzIC0gTG9hZCBhbmQgcGxvdCBtdWx0aXBsZSBmaWxlcyIKZGF0ZSAgOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogCiAgICBodG1sX2RvY3VtZW50OgogICAgICAgIHRvYyAgICAgICAgICAgIDogeWVzCiAgICAgICAgdG9jX2Zsb2F0ICAgICAgOiB5ZXMKICAgICAgICB0b2NfZGVwdGggICAgICA6IDQKICAgICAgICBoaWdobGlnaHQgICAgICA6IHRhbmdvCiAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgICAgICBjb2RlX2Rvd25sb2FkICA6IFRSVUUKcGFyYW1zOiAKICAgIHNvbHV0aW9uOgogICAgICAgIHZhbHVlOiBUUlVFCi0tLQoKYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLmFsaWduPSJjZW50ZXIifQpsaWJyYXJ5KGRvd25sb2FkdGhpcykKZG93bmxvYWRfbGluaygKICBsaW5rID0gIi4vQXJjaGl2ZS56aXAiLAogIG91dHB1dF9uYW1lID0gIkRhdGEgRmlsZXMiLAogIGJ1dHRvbl9sYWJlbCA9ICJEb3dubG9hZCBEYXRhIEZpbGVzIiwKICBidXR0b25fdHlwZSA9ICJkZWZhdWx0IiwKICBoYXNfaWNvbiA9IFRSVUUsCiAgaWNvbiA9ICJmYSBmYS1zYXZlIiwKICBzZWxmX2NvbnRhaW5lZCA9IEZBTFNFCikKYGBgCjxicj4KCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjYWNoZSA9IEZBTFNFLCBvdXQud2lkdGg9JzEwMCUnLCB3YXJuaW5ncz1GQUxTRSwgbWVzc2FnZT1GQUxTRSkKb3B0aW9ucyh3aWR0aCA9IDgwKQpgYGAKCi0tLS0KCkluIHRoaXMgdHlwaWNhbCBleGVyY2lzZSwgd2Ugd2lsbCBsZWFybiBob3cgdG8gcmVhZCBtdWx0aXBsZSBmaWxlcyBhbmQgcGxvdCB0aGVtIGFsbCB0b2dldGhlci4gVGhpcyBpcyB0aGUgYmFzaWMgc3RlcCBvZiBkYXRhIGFuYWx5c2lzLgoKIyBSZWFkaW5nIHRoZSBkYXRhIGZpbGVzCgotIExvYWQgdGhlIGB0aWR5dmVyc2VgIGFuZCBgcmVhZHhsYCBsaWJyYXJpZXMKCmBgYHtyIGVjaG89cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmBgYAoKLSBTdG9yZSBpbnRvIGBmbGlzdGAgdGhlIGxpc3Qgb2YgYWxsIGAueGxzYCBmaWxlcyBpbiB0aGUgYERhdGEvYCBmb2xkZXIuIFRha2UgYSBsb29rIGF0IHRoZSBgbGlzdC5maWxlcygpYHsuUn0gZnVuY3Rpb24gZm9yIHRoaXMsIGFuZCBlc3BlY2lhbGx5IGF0IGl0cyBwYXJhbWV0ZXJzIGBwYXRoYCwgYHBhdHRlcm5gLCBhbmQgYGZ1bGwubmFtZXNgLgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmZsaXN0IDwtIGxpc3QuZmlsZXMocGF0aD0iRGF0YSIsIHBhdHRlcm4gPSAiLnhscyIsIGZ1bGwubmFtZXMgPSBUUlVFKQpgYGAKCi0gT3BlbiBvbmUgb2YgdGhlIC54bHMgZmlsZXMgd2l0aCBFeGNlbCBhbmQgc3R1ZHkgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZmlsZS4gQXQgdGhlIHRvcCBvZiB0aGUgZmlsZSwgdGhlIGZpcnN0IGdyb3VwIG9mIDItY29sdW1uIGRhdGEgaXMgdGhlIGJsYW5rIG1lYXN1cmVtZW50IChjb2x1bW5zIEEgYW5kIEIsIGxpbmVzIDggdGhyb3VnaCAzNDA4KSwgd2hpbGUgdGhlIHNlY29uZCBncm91cCB1bmRlciB0aGUgYmxhbmsgaXMgdGhlIGFjdHVhbCByYXcgbWVhc3VyZW1lbnQgKGNvbHVtbnMgQSBhbmQgQiwgbGluZXMgMzQxMyB0aHJvdWdoIDY4MTMpLiBXb3JraW5nIG9uIHRoZSBmaXJzdCBmaWxlIGluIHRoZSBsaXN0LCBzdWNjZXNzaXZlbHk6CiAgICArIFJlYWQgYW5kIHN0b3JlIHRoZSBibGFuayBkYXRhIGluIGEgdGliYmxlIGNhbGxlZCBgYmxhbmsuZGZgCiAgICArIFJlYWQgYW5kIHN0b3JlIHRoZSByYXcgZGF0YSBpbiBhIHRpYmJsZSBjYWxsZWQgYHJhdy5kZmAKICAgICsgQ29tYmluZSB0aGVtIGludG8gYSB0aWJibGUgY2FsbGVkICBgYWxsZGF0YWAgY29udGFpbmluZyB0aGUgY29sdW1ucyBgd2AsIGBibGFua2AsIGByYXdgLCBhbmQgYHRyYW5zbWl0dGFuY2VgLCB0aGUgbGF0ZXIgYmVpbmcgdGhlIHRyYW5zbWl0dGFuY2UgKGluIHBlcmNlbnQpIHdpdGggcmVzcGVjdCB0byB0aGUgYmxhbmsgbWVhc3VyZW1lbnQgKCppLmUuKiBgdHJhbnNtaXR0YW5jZSA9IHJhdyAvIGJsYW5rICogMTAwYCkuCgpUaGUgZmluYWwgdGFibGUgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmJsYW5rLmRmIDwtIHJlYWRfZXhjZWwoZmxpc3RbMV0sIAogICAgICAgICAgICByYW5nZSA9ICJBODpCMzQwOCIsIAogICAgICAgICAgICBjb2xfbmFtZXMgPSBjKCJ3IiwiYmxhbmsiKSkKcmF3LmRmICAgPC0gcmVhZF9leGNlbChmbGlzdFsxXSwgCiAgICAgICAgICAgIHJhbmdlID0gIkEzNDEzOkI2ODEzIiwgCiAgICAgICAgICAgIGNvbF9uYW1lcyA9IGMoInciLCJyYXciKSkKIyBCYXNlIFIgdmVyc2lvbgphbGxkYXRhIDwtIHRpYmJsZShibGFuay5kZiwgcmF3PXJhdy5kZiRyYXcpCmFsbGRhdGEkdHJhbnNtaXR0YW5jZSA8LSBhbGxkYXRhJHJhdy9hbGxkYXRhJGJsYW5rKjEwMAphbGxkYXRhCiMgVGlkeXZlcnNlIHZlcnNpb246CiMgYWxsZGF0YSA8LSBpbm5lcl9qb2luKGJsYW5rLmRmLCByYXcuZGYsIGJ5ID0gInciKSAlPiUgCiMgICAgIG11dGF0ZSh0cmFuc21pdHRhbmNlPXJhdy9ibGFuayoxMDApCmBgYAoKLSBXcmFwIHRoaXMgcHJvY2VkdXJlIGludG8gYSBmdW5jdGlvbiBgcmVhZEZUSVIoKWB7LlJ9IHRoYXQsIGdpdmVuIGEgZmlsZSBuYW1lLCByZXR1cm5zIGEgdGliYmxlIHdpdGggdGhlIGNvbHVtbnMgYHdgLCBgYmxhbmtgLCBgcmF3YCwgYW5kIGB0cmFuc21pdHRhbmNlYC4gVGhpcyBtZWFucyB5b3UganVzdCBuZWVkIHRvIGRvIHRoZSBzYW1lIHRoaW5nIGFzIGJlZm9yZSBidXQgcHJvdmlkaW5nIHRoZSBmaWxlIG5hbWUgYXMgYSB2YXJpYWJsZSwgYW5kIGZpbmlzaCB0aGUgZnVuY3Rpb24gYnkgcHJpbnRpbmcgdGhlIGBhbGxkYXRhYCB0aWJibGUgLS0gaWYgeW91IGRvbid0IHByaW50IGl0IGFzIHRoZSBsYXN0IGFjdGlvbiBvZiB5b3VyIGZ1bmN0aW9uLCBpdCB3b24ndCByZXR1cm4gdGhlIHRpYmJsZS4gVGVzdCBpdCBvbiB0aGUgZmlyc3QgZmlsZSBpbiB0aGUgbGlzdC4KCmBgYHtyIGVjaG89cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpyZWFkRlRJUiA8LSBmdW5jdGlvbihmaWxlbmFtZSkgewogICAgYmxhbmsuZGYgPC0gcmVhZF9leGNlbChmaWxlbmFtZSwgCiAgICAgICAgICAgICAgICByYW5nZSA9ICJBODpCMzQwOCIsIAogICAgICAgICAgICAgICAgY29sX25hbWVzID0gYygidyIsImJsYW5rIikpCiAgICByYXcuZGYgICA8LSByZWFkX2V4Y2VsKGZpbGVuYW1lLCAKICAgICAgICAgICAgICAgIHJhbmdlID0gIkEzNDEzOkI2ODEzIiwgCiAgICAgICAgICAgICAgICBjb2xfbmFtZXMgPSBjKCJ3IiwicmF3IikpCiAgICAjIEJhc2UgUiB2ZXJzaW9uCiAgICBhbGxkYXRhIDwtIHRpYmJsZShibGFuay5kZiwgcmF3PXJhdy5kZiRyYXcpCiAgICBhbGxkYXRhJHRyYW5zbWl0dGFuY2UgPC0gYWxsZGF0YSRyYXcvYWxsZGF0YSRibGFuayoxMDAKICAgIGFsbGRhdGEKICAgICMgVGlkeXZlcnNlIHZlcnNpb246CiAgICAjIGlubmVyX2pvaW4oYmxhbmsuZGYsIHJhdy5kZiwgYnkgPSAidyIpICU+JSAKICAgICMgICAgIG11dGF0ZSh0cmFuc21pdHRhbmNlPXJhdy9ibGFuayoxMDApCn0KYGBgCgoKLSBQbG90IHRoZSBmaXJzdCBmaWxlIGluIGBmbGlzdGAsIGFuZCB0cnkgdG8gbWFrZSB5b3VyIHBsb3QgbG9vayBsaWtlIHRoaXM6CgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KcmVhZEZUSVIoZmxpc3RbMV0pICU+JSAKICAgIGdncGxvdChhZXMoeD13LCB5PXJhdykpKwogICAgICAgIGdlb21fbGluZShzaXplPTEpKwogICAgICAgIGdlb21fbGluZShhZXMoeT1ibGFuayksIGx0eT0yLCBjb2w9ImdyYXkiKSsKICAgICAgICBsYWJzKHg9ZXhwcmVzc2lvbihwYXN0ZSgiV2F2ZW51bWJlciBbIiwgY21eey0xfSwiXSIpKSwKICAgICAgICAgICAgIHk9IlRyYW5zbWl0dGFuY2UgWyVdIikrCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1zZXEoMCw1MDAwLDUwMCkpKwogICAgICAgIHRoZW1lX2J3KCkrCiAgICAgICAgdGhlbWUocGFuZWwuZ3JpZD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgdGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xNikpCmBgYAoKLSBOb3cgd2Ugd2FudCB0byByZWFkIGFsbCBmaWxlcyBpbiB0aGUgbGlzdCBhbmQgc3RvcmUgdGhlbSBpbiBhIHRpZHkgdGliYmxlLiBGb3IgdGhpcyB3ZSB3aWxsIHN0YXJ0IGVhc3kgYW5kIHVzZSBhIGBmb3JgIGxvb3AuIFRoaXMgaXMgdGhlIHN0ZXAtYnktc3RlcCBwcm9jZWR1cmU6CiAgICArIERlZmluZSBhbiBlbXB0eSB0aWJibGUgY2FsbGVkIGBkZmAKICAgICsgRGVmaW5lIGEgYGZvcmAgbG9vcCBvbiBhbGwgZWxlbWVudHMgb2YgYGZsaXN0YAogICAgKyBUaGVuLCB3aXRoaW4gdGhpcyBgZm9yYCBsb29wLCBzdWNjZXNzaXZlbHk6CiAgICAgICAgKyBSZWFkIHRoZSBjdXJyZW50IGZpbGUgd2l0aCB0aGUgYWJvdmUtZGVmaW5lZCBgcmVhZEZUSVIoKWAgZnVuY3Rpb24gYW5kIHN0b3JlIGl0IGluIGB0ZW1wZGZgCiAgICAgICAgKyBBZGQgdGhlIGNvbHVtbiBgbmFtZWAgdG8gYHRlbXBkZmAgY29udGFpbmluZyB0aGUgY3VycmVudCBmaWxlIG5hbWUuIElmIG5lZWRlZCwgdHJpbSB0aGUgcGF0aCBmcm9tIHRoZSBuYW1lIHN0cmluZyB1c2luZyBgYmFzZW5hbWUoKWB7LlJ9LgogICAgICAgICsgU3RhY2sgYHRlbXBkZmAgYmVsb3cgYGRmYCBieSBiaW5kaW5nIHRoZW0gcm93LXdpc2UgKGxvb2sgYXQgYGJpbmRfcm93cygpYHsuUn0pLgoKVGhlIGZpbmFsIHRhYmxlIHNob3VsZCBsb29rIGxpa2UgdGhpczoKCmBgYHtyIGVjaG89cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpkZiA8LSB0aWJibGUoKQpmb3IoZmlsZSBpbiBmbGlzdCl7CiAgICB0ZW1wZGYgPC0gcmVhZEZUSVIoZmlsZSkKICAgIHRlbXBkZiRuYW1lIDwtIGJhc2VuYW1lKGZpbGUpCiAgICBkZiA8LSBiaW5kX3Jvd3MoZGYsIHRlbXBkZikKfQpkZgpgYGAKCi0gTW9yZSBhZHZhbmNlZDogeW91IGNhbiBkbyB0aGlzIHdpdGhvdXQgdXNpbmcgYSBgZm9yYCBsb29wIHVzaW5nIHRoZSBgdGlkeXZlcnNlYC4gRm9yIHRoaXMsIGxvb2sgaW50byB0aGUgYG1hcCgpYHsuUn0gZnVuY3Rpb24gYXMgZGVzY3JpYmVkIFtoZXJlXShodHRwczovL2xtaS5jbnJzLmZyL3IvZGF0YS1mcmFtZXMuaHRtbCNhcHBseS1hLWZ1bmN0aW9uLXJlY3Vyc2l2ZWx5LW9uLWVhY2gtZWxlbWVudC1vZi1hLWNvbHVtbikuCgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KZGYgIDwtIHRpYmJsZShuYW1lID0gZmxpc3QpICU+JSAKICAgIG11dGF0ZShkYXRhPW1hcChuYW1lLCB+cmVhZEZUSVIoLikpLAogICAgICAgICAgIG5hbWU9YmFzZW5hbWUobmFtZSkpICU+JSAKICAgIHVubmVzdChkYXRhKQp3cml0ZS5jc3YoZGYsIkRhdGEvYWxsZGF0YS5jc3YiLCByb3cubmFtZXM9RkFMU0UpCmBgYAoKCiMgUGxvdHRpbmcKCkluIGNhc2UgeW91IGRpZG4ndCBtYW5hZ2UgdG8gZG8gdGhlIGFib3ZlIHByb2NlZHVyZSwganVzdCBsb2FkIHRoZSBgYWxsZGF0YS5jc3ZgIGZpbGUgaW4gdGhlIGBEYXRhYCBmb2xkZXIgYnkgcnVubmluZyBgZGYgPC0gcmVhZF9jc3YoIkRhdGEvYWxsZGF0YS5jc3YiKWB7LlJ9LgoKV2Ugd2lsbCBub3cgcGxvdCBhbGwgdGhlc2UgZmlsZXMgdG9nZXRoZXIgdXNpbmcgYGdncGxvdDJgLgoKLSBGaXJzdCwgeW91IGNhbiB0cnkgc2V2ZXJhbCB0aGluZ3MsIGxpa2U6CiAgICArIFBsb3QgdGhlbSBhbGwgdG9nZXRoZXIgaW4gb25lIGZyYW1lIHdpdGggYSBjb2xvciBkZXBlbmRpbmcgb24gdGhlIGZpbGUgbmFtZS4KICAgICsgUGxvdCB0aGVtIGFsbCBvbiBhIGRpZmZlcmVudCBmcmFtZSAobG9vayBhdCBgZmFjZXRfd3JhcCgpYHsuUn0pIGZvciBlYWNoIGZpbGUuCgpUaGUgZmluYWwgcGxvdHMgc2hvdWxkIGxvb2sgbGlrZSB0aGlzOgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CnRoZW1lX3NldCh0aGVtZV9idygpKwogICAgICAgICAgdGhlbWUoc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZT0iYm9sZCIpKSkKZ2dwbG90KGRhdGE9ZGYsIGFlcyh4PXcsIHk9dHJhbnNtaXR0YW5jZSwgY29sb3I9bmFtZSkpKwogICAgZ2VvbV9saW5lKCkrCiAgICBsYWJzKHg9ZXhwcmVzc2lvbihwYXN0ZSgiV2F2ZW51bWJlciBbIiwgY21eey0xfSwiXSIpKSwKICAgICAgICAgeT0iVHJhbnNtaXR0YW5jZSBbJV0iKQpnZ3Bsb3QoZGF0YT1kZiwgYWVzKHg9dywgeT10cmFuc21pdHRhbmNlKSkrCiAgICBnZW9tX2xpbmUoKSsKICAgIGxhYnMoeD1leHByZXNzaW9uKHBhc3RlKCJXYXZlbnVtYmVyIFsiLCBjbV57LTF9LCJdIikpLAogICAgICAgICB5PSJUcmFuc21pdHRhbmNlIFslXSIpKwogICAgZmFjZXRfd3JhcCh+bmFtZSkKYGBgCgotIFlvdSBzZWUgaW4gdGhlIGZpbGUgbmFtZXMgdGhhdCB0aGVyZSBhcmUgYWN0dWFsbHkgNSBzYW1wbGVzIHdpdGggMiB0ZW1wZXJhdHVyZXMgZm9yIGVhY2ggc2FtcGxlLiBCYXNlZCBvbiB0aGUgY29sdW1uIGBuYW1lYCBvZiB0aGUgYGRmYCB0aWJibGUsIGNyZWF0ZSB0aGUgY29sdW1ucyBgc2FtcGxlYCBhbmQgYHRlbXBlcmF0dXJlYCB0aGF0IGNvbnRhaW4gKm51bWVyaWNhbCB2YWx1ZXMqIG9mIHRoZSBzYW1wbGUgbnVtYmVyIGFuZCB0ZW1wZXJhdHVyZS4gVGhpbmsgYWJvdXQgdXNpbmcgdGhlIGZ1bmN0aW9uIGBnc3ViKClgey5SfSB0byByZXBsYWNlIChkZWxldGUpIHRleHQgaW4gYSBzdHJpbmcsIHRoZSBgc3Vic3RyKHN0cmluZywgc3RhcnQsIGVuZClgey5SfSBmdW5jdGlvbiB0byBnZXQgYSBwb3J0aW9uIG9mIGEgc3RyaW5nLiBZb3UgY2FuIGFsc28gdGFrZSBhIGxvb2sgYXQgdGhlIGBzZXBhcmF0ZSgpYHsuUn0gZnVuY3Rpb24gZnJvbSB0aGUgYHRpZHl2ZXJzZWAgKHNlZSBbaGVyZV0oaHR0cHM6Ly9sbWkuY25ycy5mci9yL2RhdGEtZnJhbWVzLmh0bWwjc2VwYXJhdGUtY29sdW1ucykpLgoKYGBge3IgZWNobz1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CiMgQmFzZSB2ZXJzaW9uCmRmJHNhbXBsZSAgICAgIDwtIGFzLm51bWVyaWMoc3Vic3RyKGRmJG5hbWUsOCw4KSkKZGYkdGVtcGVyYXR1cmUgPC0gc3Vic3RyKGRmJG5hbWUsMTAsMjApCmRmJHRlbXBlcmF0dXJlIDwtIGFzLm51bWVyaWMoZ3N1YigiQy54bHMiLCIiLGRmJHRlbXBlcmF0dXJlKSkKIyB0aWR5dmVyc2UgdmVyc2lvbgpkZiAgPC0gdGliYmxlKG5hbWUgPSBmbGlzdCkgJT4lIAogICAgbXV0YXRlKGRhdGE9bWFwKG5hbWUsIH5yZWFkRlRJUiguKSksCiAgICAgICAgICAgbmFtZT1iYXNlbmFtZShuYW1lKSkgJT4lIAogICAgc2VwYXJhdGUobmFtZSwgYyhOQSwgInNhbXBsZSIsInRlbXBlcmF0dXJlIixOQSksIGNvbnZlcnQgPSBUUlVFKSAlPiUgCiAgICBtdXRhdGUodGVtcGVyYXR1cmU9YXMubnVtZXJpYyhnc3ViKCJDIiwiIix0ZW1wZXJhdHVyZSkpKSAlPiUgCiAgICB1bm5lc3QoZGF0YSkKYGBgCgotIE5vdyBpdCBpcyB0aW1lIHRvIG1ha2UgbmljZXIgcGxvdHM6CiAgICAtIE1ha2UgYSBncmlkIHBsb3Qgc2hvd2luZyB0aGUgZGF0YSBldm9sdXRpb24gZm9yIGVhY2ggc2FtcGxlIGFzIGEgZnVuY3Rpb24gb2YgdGVtcGVyYXR1cmUKICAgIC0gTWFrZSBhIGdyaWQgcGxvdCB3aXRoIG9uZSBmcmFtZSBwZXIgc2FtcGxlLCBhbmQgdGhlIHR3byB0ZW1wZXJhdHVyZXMgaW4gZGlmZmVyZW50IGNvbG9ycy4KClRoZSBmaW5hbCBwbG90cyBzaG91bGQgbG9vayBsaWtlIHRoaXM6CgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KZ2dwbG90KGRhdGE9ZGYsIGFlcyh4PXcsIHk9dHJhbnNtaXR0YW5jZSkpKwogICAgZ2VvbV9saW5lKCkrCiAgICBsYWJzKHg9ZXhwcmVzc2lvbihwYXN0ZSgiV2F2ZW51bWJlciBbIiwgY21eey0xfSwiXSIpKSwKICAgICAgICAgeT0iVHJhbnNtaXR0YW5jZSBbJV0iKSsKICAgIGZhY2V0X2dyaWQocGFzdGUoIlNhbXBsZSIsIHNhbXBsZSkgfiBwYXN0ZSh0ZW1wZXJhdHVyZSwiy5pDIikpCmdncGxvdChkYXRhPWRmLCBhZXMoeD13LCB5PXRyYW5zbWl0dGFuY2UsIGNvbG9yPWZhY3Rvcih0ZW1wZXJhdHVyZSkpKSsKICAgIGdlb21fbGluZSgpKwogICAgbGFicyh4PWV4cHJlc3Npb24ocGFzdGUoIldhdmVudW1iZXIgWyIsIGNtXnstMX0sIl0iKSksCiAgICAgICAgIHk9IlRyYW5zbWl0dGFuY2UgWyVdIiwKICAgICAgICAgY29sb3I9IlRlbXBlcmF0dXJlIFvLmkNdIikrCiAgICBmYWNldF93cmFwKH5wYXN0ZSgiU2FtcGxlIixzYW1wbGUpKQpgYGAK