Required Packages

library(tidyverse)
── Attaching core tidyverse packages ─────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.2     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.0
✔ ggplot2   3.4.3     ✔ tibble    3.2.1
✔ lubridate 1.9.2     ✔ tidyr     1.3.0
✔ purrr     1.0.1     ── Conflicts ───────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(pdftools)
Using poppler version 22.02.0
library(vctrs)

Attaching package: ‘vctrs’

The following object is masked from ‘package:dplyr’:

    data_frame

The following object is masked from ‘package:tibble’:

    data_frame

The following code will act as a quick reference on how to scrape tables from PDF’s using the pdftools library in R. I am using the school budget books for DC Public Schools and am interested in gathering data just on the total budget and the Full Time equivalents for all of the schools. Prior to scraping the data, due to the amount of extra data and number of pages in each budget book, I did a decent amount of preprocessing on the PDF’s prior to working with them. Specifically, I cropped them twice to create two seperate PDF’s. One had just the names for the schools, the other had just the relevent tables. This simlified the scraping process, as the budget books were not fully standardized in terms of how they structured the tables and info.

School Budgets

Book 1

I am first looking to capture the data related to the school budgets only.

book_1 <- pdf_text("budget_1.pdf")
# Split list by lines, creating a vector for each line
book_1 <- strsplit(book_1, split = "\n")
# Cpature the relevent data accross all schools
book_1 <- lapply(book_1, head, 4)
book_1 <- lapply(book_1, tail, 3)
book_1 <- lapply(book_1, str_remove, ".*:")
book_1 <- lapply(book_1, tibble)
# map through all of the schools, adjust variables
book_1 <- map_df(1:length(book_1), ~ {
  book_1[[.x]] %>%
    rename(budget ="<chr>") %>% 
     mutate(
    year = 2018:2020, 
    budget = str_remove(budget, ","), 
    budget = as.numeric(budget) * 1000
  ) 
})


# Get School Names
school_names <- pdf_text("names_1.pdf")
# Split list by lines
school_names <- strsplit(school_names, "\n")
# Capture Names 
school_names <- lapply(school_names, head, 1)
school_names <- lapply(school_names, trimws)
school_names <- lapply(school_names, rep, 3)
# Create a single  DF 
school_names <- as.data.frame(do.call(rbind, school_names))
school_names <- school_names %>% pivot_longer(cols = 1:3, names_to = "v", 
                                              values_to = "schools") %>% 
  select(-v)
budget_1 <- bind_cols(book_1, school_names)

Book 2

# upload budget
book_2 <- pdf_text("budget_2.pdf")
# Split list by lines, creating a vector for each line
book_2 <- strsplit(book_2, split = "\n")
# Cpature the relevent data accross all schools
book_2 <- lapply(book_2, head, 4)
book_2 <- lapply(book_2, tail, 3)
book_2 <- lapply(book_2, str_remove, ".*:")
book_2 <- lapply(book_2, tibble)
# map through all of the schools, adjust variables
book_2 <- map_df(1:length(book_2), ~ {
  book_2[[.x]] %>%
    rename(budget ="<chr>") %>% 
     mutate(
    year = 2021:2023, 
    budget = str_remove(budget, ","), 
    budget = as.numeric(budget) * 1000
  ) 
})

# Get School Names
school_names <- pdf_text("names_2.pdf")
# Split list by lines
school_names <- strsplit(school_names, "\n")
# Capture Names 
school_names <- lapply(school_names, head, 1)
school_names <- lapply(school_names, trimws)
school_names <- lapply(school_names, rep, 3)
# Create a single  DF 
school_names <- as.data.frame(do.call(rbind, school_names))
school_names <- school_names %>% pivot_longer(cols = 1:3, names_to = "v", 
                                              values_to = "schools") %>% 
  select(-v)
budget_2 <- bind_cols(book_2, school_names)

Combine DF

# Pivot the DFs
budget_1 <- budget_1 %>% pivot_wider(
  names_from = year, values_from = budget
)
budget_2 <- budget_2 %>% pivot_wider(
  names_from = year, values_from = budget
) %>% arrange(schools)
# Standardize Schools 
## High Schools
budget_1$schools <- str_replace_all(budget_1$schools, "High School", "HS")
## Middle Schools
budget_1$schools <- str_replace_all(budget_1$schools, "Middle School", "MS")
## Elementary Schools
budget_1$schools <- str_replace_all(budget_1$schools, "Elementary School", "ES")
## Education Campus
budget_1$schools <- str_replace_all(budget_1$schools, "Education Campus", "EC")
df <- full_join(budget_1, budget_2, by = "schools")
df %>% arrange(schools) %>% writexl::write_xlsx("school_budgets.xlsx")
writexl::write_xlsx(list(
  budget_1 = budget_1, 
  budget_2 = budget_2
), "school_budgets.xlsx")

Full time equivalents

Budget 1

# Load in PDF
ft_1 <- pdf_text("test.pdf")
# Split into Vectors
ft_1 <- strsplit(ft_1, "\n")
ft_1 <- lapply(ft_1, trimws)
# Filter across all elements of the large list with a lambda function
ft_1 <- lapply(ft_1, function(x)
  x[str_detect(x,"^Total   ")])
# ft_1 Empty Lists 
ft_1 <- list_drop_empty(ft_1)
# Create Data Frame, select columns of interest
ft_1 <- map_dfr(ft_1, ~{
  data.frame(str_split_fixed(.x, " {1,}", 11))
}) %>% select(X2, X3, X4, X7, X8, X9) %>% 
  rename(budget_2018 = X2, 
         budget_2019 = X3, 
         budget_2020 = X4,
         ft_2018 = X7, 
         ft_2019 = X8, 
         ft_2020 = X9) %>% 
  pivot_longer(
    cols = 1:6,
    names_to = c("type", "year"), names_sep = "_", values_to = "value"
  ) %>% 
  mutate(
    value = str_remove(value, ","), 
    value = as.numeric(value), 
    value = case_when(
      type == "budget" ~ value * 1000, 
      T ~ value
    )
  )
# Add in School Names
# Get School Names
school_names <- pdf_text("names_1.pdf")
# Split list by lines
school_names <- strsplit(school_names, "\n")
# Capture Names 
school_names <- lapply(school_names, head, 1)
school_names <- lapply(school_names, trimws)
school_names <- lapply(school_names, rep, 6)
# Create a single  DF 
school_names <- as.data.frame(do.call(rbind, school_names))
school_names <- school_names %>% pivot_longer(cols = 1:6, names_to = "v", 
                                              values_to = "schools") %>% 
  select(-v)
ft_1 <- bind_cols(ft_1, school_names)

Budget 2

# Load in PDF
ft_2 <- pdf_text("ft_2.pdf")
# Split into Vectors
ft_2 <- strsplit(ft_2, "\n")
ft_2 <- lapply(ft_2, trimws)
# Filter across all elements of the large list with a lambda function
ft_2 <- lapply(ft_2, function(x)
  x[str_detect(x,"^Total   ")])

# ft_1 Empty Lists 
ft_2 <- list_drop_empty(ft_2)
# Create Data Frame, select columns of interest
ft_2 <- map_dfr(ft_2, ~{
  data.frame(str_split_fixed(.x, " {1,}", 11))
}) %>%
  select(X2, X3, X4, X7, X8, X9) %>% 
  rename(budget_2021 = X2, 
         budget_2022 = X3, 
         budget_2023 = X4,
         ft_2021 = X7, 
         ft_2022 = X8, 
         ft_2023 = X9) %>% 
  pivot_longer(
    cols = 1:6,
    names_to = c("type", "year"), names_sep = "_", values_to = "value"
  ) %>% 
  mutate(
    value = str_remove(value, ","), 
    value = as.numeric(value), 
    value = case_when(
      type == "budget" ~ value * 1000, 
      T ~ value
    )
  )

# Get School Names
school_names <- pdf_text("names_2.pdf")
# Split list by lines
school_names <- strsplit(school_names, "\n")
# Capture Names 
school_names <- lapply(school_names, head, 1)
school_names <- lapply(school_names, trimws)
school_names <- lapply(school_names, rep, 6)
# Create a single  DF 
school_names <- as.data.frame(do.call(rbind, school_names))
school_names <- school_names %>% pivot_longer(cols = 1:6, names_to = "v", 
                                              values_to = "schools") %>% 
  select(-v)
ft_2 <- bind_cols(ft_2, school_names)

Combine DF

# Pivot Df
ft_1 <- ft_1 %>% 
  pivot_wider(names_from = c(type, year), values_from = value)
ft_2 <- ft_2 %>% 
  pivot_wider(names_from = c(type, year), values_from = value)
# Standardize Schools 
## High Schools
ft_1$schools <- str_replace_all(ft_1$schools, "High School", "HS")
## Middle Schools
ft_1$schools <- str_replace_all(ft_1$schools, "Middle School", "MS")
## Elementary Schools
ft_1$schools <- str_replace_all(ft_1$schools, "Elementary School", "ES")
## Education Campus
ft_1$schools <- str_replace_all(ft_1$schools, "Education Campus", "EC")
df <- full_join(ft_1, ft_2, by = c("schools"))
df %>% arrange(schools) %>%  writexl::write_xlsx("school_budgets.xlsx")
LS0tCnRpdGxlOiAiU2NyYXBpbmcgVGFibGVzIGZyb20gUERGJ3MgdXNpbmcgcGRmdG9vbHMiCmF1dGhvcjogTmljayBEb2RkcwpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCiMgUmVxdWlyZWQgUGFja2FnZXMKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwZGZ0b29scykKYGBgClRoZSBmb2xsb3dpbmcgY29kZSB3aWxsIGFjdCBhcyBhIHF1aWNrIHJlZmVyZW5jZSBvbiBob3cgdG8gc2NyYXBlIHRhYmxlcyBmcm9tIApQREYncyB1c2luZyB0aGUgcGRmdG9vbHMgbGlicmFyeSBpbiBSLiBJIGFtIHVzaW5nIHRoZSBzY2hvb2wgYnVkZ2V0IGJvb2tzIGZvciAKREMgUHVibGljIFNjaG9vbHMgYW5kIGFtIGludGVyZXN0ZWQgaW4gZ2F0aGVyaW5nIGRhdGEganVzdCBvbiB0aGUgdG90YWwgYnVkZ2V0CmFuZCB0aGUgRnVsbCBUaW1lIGVxdWl2YWxlbnRzIGZvciBhbGwgb2YgdGhlIHNjaG9vbHMuIApQcmlvciB0byBzY3JhcGluZyB0aGUgZGF0YSwgZHVlIHRvIHRoZSBhbW91bnQgb2YgZXh0cmEgZGF0YSBhbmQgbnVtYmVyIG9mIApwYWdlcyBpbiBlYWNoIGJ1ZGdldCBib29rLCBJIGRpZCBhIGRlY2VudCBhbW91bnQgb2YgcHJlcHJvY2Vzc2luZyBvbiB0aGUgUERGJ3MgCnByaW9yIHRvIHdvcmtpbmcgd2l0aCB0aGVtLiBTcGVjaWZpY2FsbHksIEkgY3JvcHBlZCB0aGVtIHR3aWNlIHRvIGNyZWF0ZSAKdHdvIHNlcGVyYXRlIFBERidzLiBPbmUgaGFkIGp1c3QgdGhlIG5hbWVzIGZvciB0aGUgc2Nob29scywgdGhlIG90aGVyIGhhZCBqdXN0IAp0aGUgcmVsZXZlbnQgdGFibGVzLiBUaGlzIHNpbWxpZmllZCB0aGUgc2NyYXBpbmcgcHJvY2VzcywgYXMgdGhlIGJ1ZGdldCBib29rcyAKd2VyZSBub3QgZnVsbHkgc3RhbmRhcmRpemVkIGluIHRlcm1zIG9mIGhvdyB0aGV5IHN0cnVjdHVyZWQgdGhlIHRhYmxlcyBhbmQgaW5mby4gCgojIFNjaG9vbCBCdWRnZXRzIAojIyBCb29rIDEKSSBhbSBmaXJzdCBsb29raW5nIHRvIGNhcHR1cmUgdGhlIGRhdGEgcmVsYXRlZCB0byB0aGUgc2Nob29sIGJ1ZGdldHMgb25seS4KYGBge3IgZWNobz1UUlVFfQpib29rXzEgPC0gcGRmX3RleHQoImJ1ZGdldF8xLnBkZiIpCiMgU3BsaXQgbGlzdCBieSBsaW5lcywgY3JlYXRpbmcgYSB2ZWN0b3IgZm9yIGVhY2ggbGluZQpib29rXzEgPC0gc3Ryc3BsaXQoYm9va18xLCBzcGxpdCA9ICJcbiIpCiMgQ3BhdHVyZSB0aGUgcmVsZXZlbnQgZGF0YSBhY2Nyb3NzIGFsbCBzY2hvb2xzCmJvb2tfMSA8LSBsYXBwbHkoYm9va18xLCBoZWFkLCA0KQpib29rXzEgPC0gbGFwcGx5KGJvb2tfMSwgdGFpbCwgMykKYm9va18xIDwtIGxhcHBseShib29rXzEsIHN0cl9yZW1vdmUsICIuKjoiKQpib29rXzEgPC0gbGFwcGx5KGJvb2tfMSwgdGliYmxlKQojIG1hcCB0aHJvdWdoIGFsbCBvZiB0aGUgc2Nob29scywgYWRqdXN0IHZhcmlhYmxlcwpib29rXzEgPC0gbWFwX2RmKDE6bGVuZ3RoKGJvb2tfMSksIH4gewogIGJvb2tfMVtbLnhdXSAlPiUKICAgIHJlbmFtZShidWRnZXQgPSI8Y2hyPiIpICU+JSAKICAgICBtdXRhdGUoCiAgICB5ZWFyID0gMjAxODoyMDIwLCAKICAgIGJ1ZGdldCA9IHN0cl9yZW1vdmUoYnVkZ2V0LCAiLCIpLCAKICAgIGJ1ZGdldCA9IGFzLm51bWVyaWMoYnVkZ2V0KSAqIDEwMDAKICApIAp9KQoKCiMgR2V0IFNjaG9vbCBOYW1lcwpzY2hvb2xfbmFtZXMgPC0gcGRmX3RleHQoIm5hbWVzXzEucGRmIikKIyBTcGxpdCBsaXN0IGJ5IGxpbmVzCnNjaG9vbF9uYW1lcyA8LSBzdHJzcGxpdChzY2hvb2xfbmFtZXMsICJcbiIpCiMgQ2FwdHVyZSBOYW1lcyAKc2Nob29sX25hbWVzIDwtIGxhcHBseShzY2hvb2xfbmFtZXMsIGhlYWQsIDEpCnNjaG9vbF9uYW1lcyA8LSBsYXBwbHkoc2Nob29sX25hbWVzLCB0cmltd3MpCnNjaG9vbF9uYW1lcyA8LSBsYXBwbHkoc2Nob29sX25hbWVzLCByZXAsIDMpCiMgQ3JlYXRlIGEgc2luZ2xlICBERiAKc2Nob29sX25hbWVzIDwtIGFzLmRhdGEuZnJhbWUoZG8uY2FsbChyYmluZCwgc2Nob29sX25hbWVzKSkKc2Nob29sX25hbWVzIDwtIHNjaG9vbF9uYW1lcyAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSAxOjMsIG5hbWVzX3RvID0gInYiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJzY2hvb2xzIikgJT4lIAogIHNlbGVjdCgtdikKYnVkZ2V0XzEgPC0gYmluZF9jb2xzKGJvb2tfMSwgc2Nob29sX25hbWVzKQpgYGAKCiMjIEJvb2sgMgpgYGB7ciBlY2hvPVRSVUV9CiMgdXBsb2FkIGJ1ZGdldApib29rXzIgPC0gcGRmX3RleHQoImJ1ZGdldF8yLnBkZiIpCiMgU3BsaXQgbGlzdCBieSBsaW5lcywgY3JlYXRpbmcgYSB2ZWN0b3IgZm9yIGVhY2ggbGluZQpib29rXzIgPC0gc3Ryc3BsaXQoYm9va18yLCBzcGxpdCA9ICJcbiIpCiMgQ3BhdHVyZSB0aGUgcmVsZXZlbnQgZGF0YSBhY2Nyb3NzIGFsbCBzY2hvb2xzCmJvb2tfMiA8LSBsYXBwbHkoYm9va18yLCBoZWFkLCA0KQpib29rXzIgPC0gbGFwcGx5KGJvb2tfMiwgdGFpbCwgMykKYm9va18yIDwtIGxhcHBseShib29rXzIsIHN0cl9yZW1vdmUsICIuKjoiKQpib29rXzIgPC0gbGFwcGx5KGJvb2tfMiwgdGliYmxlKQojIG1hcCB0aHJvdWdoIGFsbCBvZiB0aGUgc2Nob29scywgYWRqdXN0IHZhcmlhYmxlcwpib29rXzIgPC0gbWFwX2RmKDE6bGVuZ3RoKGJvb2tfMiksIH4gewogIGJvb2tfMltbLnhdXSAlPiUKICAgIHJlbmFtZShidWRnZXQgPSI8Y2hyPiIpICU+JSAKICAgICBtdXRhdGUoCiAgICB5ZWFyID0gMjAyMToyMDIzLCAKICAgIGJ1ZGdldCA9IHN0cl9yZW1vdmUoYnVkZ2V0LCAiLCIpLCAKICAgIGJ1ZGdldCA9IGFzLm51bWVyaWMoYnVkZ2V0KSAqIDEwMDAKICApIAp9KQoKIyBHZXQgU2Nob29sIE5hbWVzCnNjaG9vbF9uYW1lcyA8LSBwZGZfdGV4dCgibmFtZXNfMi5wZGYiKQojIFNwbGl0IGxpc3QgYnkgbGluZXMKc2Nob29sX25hbWVzIDwtIHN0cnNwbGl0KHNjaG9vbF9uYW1lcywgIlxuIikKIyBDYXB0dXJlIE5hbWVzIApzY2hvb2xfbmFtZXMgPC0gbGFwcGx5KHNjaG9vbF9uYW1lcywgaGVhZCwgMSkKc2Nob29sX25hbWVzIDwtIGxhcHBseShzY2hvb2xfbmFtZXMsIHRyaW13cykKc2Nob29sX25hbWVzIDwtIGxhcHBseShzY2hvb2xfbmFtZXMsIHJlcCwgMykKIyBDcmVhdGUgYSBzaW5nbGUgIERGIApzY2hvb2xfbmFtZXMgPC0gYXMuZGF0YS5mcmFtZShkby5jYWxsKHJiaW5kLCBzY2hvb2xfbmFtZXMpKQpzY2hvb2xfbmFtZXMgPC0gc2Nob29sX25hbWVzICU+JSBwaXZvdF9sb25nZXIoY29scyA9IDE6MywgbmFtZXNfdG8gPSAidiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInNjaG9vbHMiKSAlPiUgCiAgc2VsZWN0KC12KQpidWRnZXRfMiA8LSBiaW5kX2NvbHMoYm9va18yLCBzY2hvb2xfbmFtZXMpCmBgYAojIyAgQ29tYmluZSBERgpgYGB7ciBlY2hvPVRSVUV9CiMgUGl2b3QgdGhlIERGcwpidWRnZXRfMSA8LSBidWRnZXRfMSAlPiUgcGl2b3Rfd2lkZXIoCiAgbmFtZXNfZnJvbSA9IHllYXIsIHZhbHVlc19mcm9tID0gYnVkZ2V0CikKYnVkZ2V0XzIgPC0gYnVkZ2V0XzIgJT4lIHBpdm90X3dpZGVyKAogIG5hbWVzX2Zyb20gPSB5ZWFyLCB2YWx1ZXNfZnJvbSA9IGJ1ZGdldAopICU+JSBhcnJhbmdlKHNjaG9vbHMpCiMgU3RhbmRhcmRpemUgU2Nob29scyAKIyMgSGlnaCBTY2hvb2xzCmJ1ZGdldF8xJHNjaG9vbHMgPC0gc3RyX3JlcGxhY2VfYWxsKGJ1ZGdldF8xJHNjaG9vbHMsICJIaWdoIFNjaG9vbCIsICJIUyIpCiMjIE1pZGRsZSBTY2hvb2xzCmJ1ZGdldF8xJHNjaG9vbHMgPC0gc3RyX3JlcGxhY2VfYWxsKGJ1ZGdldF8xJHNjaG9vbHMsICJNaWRkbGUgU2Nob29sIiwgIk1TIikKIyMgRWxlbWVudGFyeSBTY2hvb2xzCmJ1ZGdldF8xJHNjaG9vbHMgPC0gc3RyX3JlcGxhY2VfYWxsKGJ1ZGdldF8xJHNjaG9vbHMsICJFbGVtZW50YXJ5IFNjaG9vbCIsICJFUyIpCiMjIEVkdWNhdGlvbiBDYW1wdXMKYnVkZ2V0XzEkc2Nob29scyA8LSBzdHJfcmVwbGFjZV9hbGwoYnVkZ2V0XzEkc2Nob29scywgIkVkdWNhdGlvbiBDYW1wdXMiLCAiRUMiKQpkZiA8LSBmdWxsX2pvaW4oYnVkZ2V0XzEsIGJ1ZGdldF8yLCBieSA9ICJzY2hvb2xzIikKZGYgJT4lIGFycmFuZ2Uoc2Nob29scykgJT4lIHdyaXRleGw6OndyaXRlX3hsc3goInNjaG9vbF9idWRnZXRzLnhsc3giKQp3cml0ZXhsOjp3cml0ZV94bHN4KGxpc3QoCiAgYnVkZ2V0XzEgPSBidWRnZXRfMSwgCiAgYnVkZ2V0XzIgPSBidWRnZXRfMgopLCAic2Nob29sX2J1ZGdldHMueGxzeCIpCmBgYAojIEZ1bGwgdGltZSBlcXVpdmFsZW50cwojIyBCdWRnZXQgMQpgYGB7ciBlY2hvPVRSVUV9CiMgTG9hZCBpbiBQREYKZnRfMSA8LSBwZGZfdGV4dCgidGVzdC5wZGYiKQojIFNwbGl0IGludG8gVmVjdG9ycwpmdF8xIDwtIHN0cnNwbGl0KGZ0XzEsICJcbiIpCmZ0XzEgPC0gbGFwcGx5KGZ0XzEsIHRyaW13cykKIyBGaWx0ZXIgYWNyb3NzIGFsbCBlbGVtZW50cyBvZiB0aGUgbGFyZ2UgbGlzdCB3aXRoIGEgbGFtYmRhIGZ1bmN0aW9uCmZ0XzEgPC0gbGFwcGx5KGZ0XzEsIGZ1bmN0aW9uKHgpCiAgeFtzdHJfZGV0ZWN0KHgsIl5Ub3RhbCAgICIpXSkKIyBmdF8xIEVtcHR5IExpc3RzIApmdF8xIDwtIGxpc3RfZHJvcF9lbXB0eShmdF8xKQojIENyZWF0ZSBEYXRhIEZyYW1lLCBzZWxlY3QgY29sdW1ucyBvZiBpbnRlcmVzdApmdF8xIDwtIG1hcF9kZnIoZnRfMSwgfnsKICBkYXRhLmZyYW1lKHN0cl9zcGxpdF9maXhlZCgueCwgIiB7MSx9IiwgMTEpKQp9KSAlPiUgc2VsZWN0KFgyLCBYMywgWDQsIFg3LCBYOCwgWDkpICU+JSAKICByZW5hbWUoYnVkZ2V0XzIwMTggPSBYMiwgCiAgICAgICAgIGJ1ZGdldF8yMDE5ID0gWDMsIAogICAgICAgICBidWRnZXRfMjAyMCA9IFg0LAogICAgICAgICBmdF8yMDE4ID0gWDcsIAogICAgICAgICBmdF8yMDE5ID0gWDgsIAogICAgICAgICBmdF8yMDIwID0gWDkpICU+JSAKICBwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gMTo2LAogICAgbmFtZXNfdG8gPSBjKCJ0eXBlIiwgInllYXIiKSwgbmFtZXNfc2VwID0gIl8iLCB2YWx1ZXNfdG8gPSAidmFsdWUiCiAgKSAlPiUgCiAgbXV0YXRlKAogICAgdmFsdWUgPSBzdHJfcmVtb3ZlKHZhbHVlLCAiLCIpLCAKICAgIHZhbHVlID0gYXMubnVtZXJpYyh2YWx1ZSksIAogICAgdmFsdWUgPSBjYXNlX3doZW4oCiAgICAgIHR5cGUgPT0gImJ1ZGdldCIgfiB2YWx1ZSAqIDEwMDAsIAogICAgICBUIH4gdmFsdWUKICAgICkKICApCiMgQWRkIGluIFNjaG9vbCBOYW1lcwojIEdldCBTY2hvb2wgTmFtZXMKc2Nob29sX25hbWVzIDwtIHBkZl90ZXh0KCJuYW1lc18xLnBkZiIpCiMgU3BsaXQgbGlzdCBieSBsaW5lcwpzY2hvb2xfbmFtZXMgPC0gc3Ryc3BsaXQoc2Nob29sX25hbWVzLCAiXG4iKQojIENhcHR1cmUgTmFtZXMgCnNjaG9vbF9uYW1lcyA8LSBsYXBwbHkoc2Nob29sX25hbWVzLCBoZWFkLCAxKQpzY2hvb2xfbmFtZXMgPC0gbGFwcGx5KHNjaG9vbF9uYW1lcywgdHJpbXdzKQpzY2hvb2xfbmFtZXMgPC0gbGFwcGx5KHNjaG9vbF9uYW1lcywgcmVwLCA2KQojIENyZWF0ZSBhIHNpbmdsZSAgREYgCnNjaG9vbF9uYW1lcyA8LSBhcy5kYXRhLmZyYW1lKGRvLmNhbGwocmJpbmQsIHNjaG9vbF9uYW1lcykpCnNjaG9vbF9uYW1lcyA8LSBzY2hvb2xfbmFtZXMgJT4lIHBpdm90X2xvbmdlcihjb2xzID0gMTo2LCBuYW1lc190byA9ICJ2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAic2Nob29scyIpICU+JSAKICBzZWxlY3QoLXYpCmZ0XzEgPC0gYmluZF9jb2xzKGZ0XzEsIHNjaG9vbF9uYW1lcykKYGBgCiMjIEJ1ZGdldCAyCmBgYHtyIGVjaG89VFJVRX0KIyBMb2FkIGluIFBERgpmdF8yIDwtIHBkZl90ZXh0KCJmdF8yLnBkZiIpCiMgU3BsaXQgaW50byBWZWN0b3JzCmZ0XzIgPC0gc3Ryc3BsaXQoZnRfMiwgIlxuIikKZnRfMiA8LSBsYXBwbHkoZnRfMiwgdHJpbXdzKQojIEZpbHRlciBhY3Jvc3MgYWxsIGVsZW1lbnRzIG9mIHRoZSBsYXJnZSBsaXN0IHdpdGggYSBsYW1iZGEgZnVuY3Rpb24KZnRfMiA8LSBsYXBwbHkoZnRfMiwgZnVuY3Rpb24oeCkKICB4W3N0cl9kZXRlY3QoeCwiXlRvdGFsICAgIildKQoKIyBmdF8xIEVtcHR5IExpc3RzIApmdF8yIDwtIGxpc3RfZHJvcF9lbXB0eShmdF8yKQojIENyZWF0ZSBEYXRhIEZyYW1lLCBzZWxlY3QgY29sdW1ucyBvZiBpbnRlcmVzdApmdF8yIDwtIG1hcF9kZnIoZnRfMiwgfnsKICBkYXRhLmZyYW1lKHN0cl9zcGxpdF9maXhlZCgueCwgIiB7MSx9IiwgMTEpKQp9KSAlPiUKICBzZWxlY3QoWDIsIFgzLCBYNCwgWDcsIFg4LCBYOSkgJT4lIAogIHJlbmFtZShidWRnZXRfMjAyMSA9IFgyLCAKICAgICAgICAgYnVkZ2V0XzIwMjIgPSBYMywgCiAgICAgICAgIGJ1ZGdldF8yMDIzID0gWDQsCiAgICAgICAgIGZ0XzIwMjEgPSBYNywgCiAgICAgICAgIGZ0XzIwMjIgPSBYOCwgCiAgICAgICAgIGZ0XzIwMjMgPSBYOSkgJT4lIAogIHBpdm90X2xvbmdlcigKICAgIGNvbHMgPSAxOjYsCiAgICBuYW1lc190byA9IGMoInR5cGUiLCAieWVhciIpLCBuYW1lc19zZXAgPSAiXyIsIHZhbHVlc190byA9ICJ2YWx1ZSIKICApICU+JSAKICBtdXRhdGUoCiAgICB2YWx1ZSA9IHN0cl9yZW1vdmUodmFsdWUsICIsIiksIAogICAgdmFsdWUgPSBhcy5udW1lcmljKHZhbHVlKSwgCiAgICB2YWx1ZSA9IGNhc2Vfd2hlbigKICAgICAgdHlwZSA9PSAiYnVkZ2V0IiB+IHZhbHVlICogMTAwMCwgCiAgICAgIFQgfiB2YWx1ZQogICAgKQogICkKCiMgR2V0IFNjaG9vbCBOYW1lcwpzY2hvb2xfbmFtZXMgPC0gcGRmX3RleHQoIm5hbWVzXzIucGRmIikKIyBTcGxpdCBsaXN0IGJ5IGxpbmVzCnNjaG9vbF9uYW1lcyA8LSBzdHJzcGxpdChzY2hvb2xfbmFtZXMsICJcbiIpCiMgQ2FwdHVyZSBOYW1lcyAKc2Nob29sX25hbWVzIDwtIGxhcHBseShzY2hvb2xfbmFtZXMsIGhlYWQsIDEpCnNjaG9vbF9uYW1lcyA8LSBsYXBwbHkoc2Nob29sX25hbWVzLCB0cmltd3MpCnNjaG9vbF9uYW1lcyA8LSBsYXBwbHkoc2Nob29sX25hbWVzLCByZXAsIDYpCiMgQ3JlYXRlIGEgc2luZ2xlICBERiAKc2Nob29sX25hbWVzIDwtIGFzLmRhdGEuZnJhbWUoZG8uY2FsbChyYmluZCwgc2Nob29sX25hbWVzKSkKc2Nob29sX25hbWVzIDwtIHNjaG9vbF9uYW1lcyAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSAxOjYsIG5hbWVzX3RvID0gInYiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJzY2hvb2xzIikgJT4lIAogIHNlbGVjdCgtdikKZnRfMiA8LSBiaW5kX2NvbHMoZnRfMiwgc2Nob29sX25hbWVzKQpgYGAKCiMjICBDb21iaW5lIERGIApgYGB7ciBlY2hvPVRSVUV9CiMgUGl2b3QgRGYKZnRfMSA8LSBmdF8xICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gYyh0eXBlLCB5ZWFyKSwgdmFsdWVzX2Zyb20gPSB2YWx1ZSkKZnRfMiA8LSBmdF8yICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gYyh0eXBlLCB5ZWFyKSwgdmFsdWVzX2Zyb20gPSB2YWx1ZSkKIyBTdGFuZGFyZGl6ZSBTY2hvb2xzIAojIyBIaWdoIFNjaG9vbHMKZnRfMSRzY2hvb2xzIDwtIHN0cl9yZXBsYWNlX2FsbChmdF8xJHNjaG9vbHMsICJIaWdoIFNjaG9vbCIsICJIUyIpCiMjIE1pZGRsZSBTY2hvb2xzCmZ0XzEkc2Nob29scyA8LSBzdHJfcmVwbGFjZV9hbGwoZnRfMSRzY2hvb2xzLCAiTWlkZGxlIFNjaG9vbCIsICJNUyIpCiMjIEVsZW1lbnRhcnkgU2Nob29scwpmdF8xJHNjaG9vbHMgPC0gc3RyX3JlcGxhY2VfYWxsKGZ0XzEkc2Nob29scywgIkVsZW1lbnRhcnkgU2Nob29sIiwgIkVTIikKIyMgRWR1Y2F0aW9uIENhbXB1cwpmdF8xJHNjaG9vbHMgPC0gc3RyX3JlcGxhY2VfYWxsKGZ0XzEkc2Nob29scywgIkVkdWNhdGlvbiBDYW1wdXMiLCAiRUMiKQpkZiA8LSBmdWxsX2pvaW4oZnRfMSwgZnRfMiwgYnkgPSBjKCJzY2hvb2xzIikpCmRmICU+JSBhcnJhbmdlKHNjaG9vbHMpICU+JSAgd3JpdGV4bDo6d3JpdGVfeGxzeCgic2Nob29sX2J1ZGdldHMueGxzeCIpCmBgYAoK