Introduction

Choropleth Maps là một trong những công cụ hình ảnh hóa dữ liệu hữu ích và được sử dụng phổ biến. Sử dụng công cụ hình ảnh này có truyền tải một cách nhanh chóng và ấn tượng, ví dụ, thị phần của hãng Coca Cola tại các tỉnh ở VN ra sao hoặc tỉ lệ đói nghèo theo khu vực địa lí bằng cách căn cứ vào độ đậm hay nhạt của màu sắc. Trong bài viết này chúng ta sẽ thực hiện một số lệnh thực hành tạo ra bản đồ hành chính cũng như Choropleth Map với dữ liệu cho Việt Nam.

Administrative Map of Vietnam

Để vẽ bản đồ hành chính của Việt Nam trước hết chúng ta cần có Geo-spatial Data. Hiện R có nhiều package hỗ trợ cho việc download và lấy loại dữ liệu này một cách thuận tiện ví dụ như raster, sf. Tuy nhiên các packages này, vì nhiều lí do, sẽ chưa có dữ liệu của hai quần đảo Trường Sa (Spratly Islands) và Hoàng Sa (Paracel Islands) thuộc Việt Nam. Do vậy chúng ta có thể sử dụng các nguồn dữ liệu Geo-spatial Data cho hai quần đảo này được cung cấp bởi các tổ chức và cá nhân ở Việt Nam như dưới đây:

# Clear workspace: 
rm(list = ls())

# Load tidyverse package: 

library(tidyverse)
 
# Get geo-spatial data for Vietnam provided: 

link2 <- "https://data.opendevelopmentmekong.net/dataset/999c96d8-fae0-4b82-9a2b-e481f6f50e12/resource/2818c2c5-e9c3-440b-a9b8-3029d7298065/download/diaphantinhenglish.geojson?fbclid=IwAR1coUVLkuEoJRsgaH81q6ocz1nVeGBirqpKRBN8WWxXQIJREUL1buFi1eE"

link1 <- "https://raw.githubusercontent.com/nguyenduy1133/data/main/Dia_phan_Tinh_cap_nhat.geojson?fbclid=IwAR1cPIIswZ9y8ZvsmW8ioqh57malxnlrBr2L3EjYRfMPiDjCqu4kCA2PUxQ"

vn_spatial <- sf::st_read(link1)
## Reading layer `Dia_phan_Tinh_cap_nhat' from data source `https://raw.githubusercontent.com/nguyenduy1133/data/main/Dia_phan_Tinh_cap_nhat.geojson?fbclid=IwAR1cPIIswZ9y8ZvsmW8ioqh57malxnlrBr2L3EjYRfMPiDjCqu4kCA2PUxQ' using driver `GeoJSON'
## Simple feature collection with 63 features and 2 fields
## geometry type:  MULTIPOLYGON
## dimension:      XY
## bbox:           xmin: 102.1421 ymin: 6.953306 xmax: 116.9473 ymax: 23.3939
## geographic CRS: WGS 84
# Rename for Dak Lak province: 

vn_spatial %>% mutate(Name = case_when(Name == "Dak Lak" ~ "Dac Lac", TRUE ~ Name)) -> vn_spatial

# Make a draft of map: 

ggplot() + 
  geom_sf(data = vn_spatial) + 
  labs(title = "Vietnam Map - Version 1")

Hoặc theo một cách khác:

ggplot() + 
  geom_sf(data = vn_spatial, aes(fill = Name), show.legend = FALSE) + 
  labs(title = "Vietnam Map - Version 2")

Trong nhiều tình huống chúng ta cần bản đồ mà thể hiện cả cấp vùng (Region) hoặc tiểu vùng (sub-region). Trước hết cần lấy dữ liệu về vùng/tiểu vùng (vốn rất nhiều nguồn) như sau:

# Scrap list of provinces by region and sub-region level: 

library(rvest)

# Collect region/sub-region data from Wiki: 

provinces <- "https://en.wikipedia.org/wiki/Provinces_of_Vietnam"

provinces %>% 
  read_html() %>% 
  html_nodes(xpath = '//*[@id="mw-content-text"]/div[1]/table[6]') %>% 
  html_table() %>% 
  .[[1]] -> provinces_vn

# Function extracts data in table: 

extract_table <- function(i) {
  
  provinces_vn %>% 
    slice(i) %>% 
    pull(3) %>% 
    str_split("\n", simplify = TRUE) %>% 
    str_replace_all("†", "") %>% 
    str_squish() %>% 
    as.vector() -> province_names
  
  provinces_vn %>% 
    slice(i) %>% 
    pull(1) %>% 
    str_split("\\(", simplify = TRUE) %>% 
    str_split("\\,", simplify = TRUE) %>% 
    str_replace_all("Vietnam", "") %>% 
    as.vector() %>% 
    str_squish() -> region
  
  provinces_vn %>% 
    slice(i) %>% 
    pull(2) %>% 
    str_split("\\(", simplify = TRUE) %>% 
    str_replace_all("\\)", "") %>% 
    str_replace_all("Vietnam", "") %>% 
    as.vector() %>% 
    str_squish() -> sub_region 
  
  tibble(province = province_names, region_vn = region[2], region_en = region[1], 
         sub_region_vn = sub_region[2], sub_region_en = sub_region[1]) -> df_final
  
  return(df_final)
  
}

# Use the function: 

lapply(1:nrow(provinces_vn), extract_table) -> province_region

do.call("bind_rows", province_region) -> province_region_vietnam

# Rename for some provinnces: 

library(stringi)

province_region_vietnam %>% 
  mutate(province_latin = stri_trans_general(province, "Latin-ASCII")) %>% 
  mutate(province_latin = case_when(province_latin == "Thua Thien-Hue" ~ "Thua Thien - Hue", 
                                    province_latin == "Ba Ria-Vung Tau" ~ "Ba Ria - Vung Tau", 
                                    province_latin == "Ho Chi Minh City" ~ "TP. Ho Chi Minh", 
                                    TRUE ~ province_latin)) -> province_region_vietnam

Sau đó join hai bộ dữ liệu (Geo-spatial and Region Datasets) lại và mapping:

# Combine the two datasets: 

full_join(vn_spatial, province_region_vietnam, by = c("Name" = "province_latin")) -> vn_spatial_region

# Administrative map of Vietnam: 

ggplot() + 
  geom_sf(data = vn_spatial_region, aes(fill = sub_region_en)) + 
  labs(title = "Administrative Regions of Viet Nam")

Hoặc làm cho bản đồ trở nên đẹp hơn:

# Elegant map of Vietnam: 

my_colors <- c('#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00','#ffff33','#a65628','#f781bf')
bgr_color <- "azure"

library(extrafont)
my_font <- "Roboto Condensed"
ggplot() + 
  geom_sf(data = vn_spatial_region, aes(fill = sub_region_en)) + 
  scale_fill_manual(values = my_colors, name = "Region:") + 
  theme(legend.position = c(0.82, 0.83)) + 
  theme(text = element_text(family = my_font)) + 
  theme(legend.key.size = unit(0.5, "cm"), legend.key.width = unit(0.5, "cm") ) + 
  theme(legend.text = element_text(family = my_font, color = "grey20")) + 
  theme(panel.background = element_rect(fill = bgr_color)) +
  theme(plot.background = element_rect(fill = bgr_color)) + 
  theme(legend.background = element_rect(fill = bgr_color)) + 
  theme(plot.margin = unit(rep(0.7, 4), "cm")) + 
  theme(plot.title = element_text(size = 16)) + 
  labs(title = "Administrative Regions of Viet Nam", 
       caption = "Data Source: https://data.opendevelopmentmekong.net")

Choropleth Map

Trước hết lấy dữ liệu về mật độ dân số theo cả cấp Huyện và Xã cho các đơn vị hành chính của Việt Nam:

#================================
#  Stage 1: Extract all links
#================================

all_links <- "https://www.citypopulation.de/Vietnam.html"
pg <- read_html(all_links)
m <- html_nodes(pg, "a")
k <- html_attr(m, "href")
all_links_communes_level <-  str_c("https://www.citypopulation.de/en/vietnam/", k[-c(1:6)])

#============================================================
#  Stage 2: Function for getting popolation by commune level
#============================================================

get_pop_data <- function(link) {
  
  link %>% 
    read_html() %>% 
    html_nodes(xpath = '//*[@id="tl"]') %>% 
    html_table(fill = TRUE) %>% 
    .[[1]] -> df
  
  df %>%   
    select(1:3) -> df
  
  names(df) <- c("Name", "Status", "Pop")
  
  df %>% 
    mutate(Name_Latin = stringi::stri_trans_general(Name, "Latin-ASCII"), 
           Pop = str_replace_all(Pop, "[^0-9]", "") %>% as.integer()) %>% 
    return()
  
}


# Use the function: 

lapply(all_links_communes_level, get_pop_data) -> pop_cummunes_list

do.call("bind_rows", pop_cummunes_list) -> pop_cummunes_df

# Show some observations: 

library(knitr)

pop_cummunes_df %>% 
  select(-1) %>% 
  filter(!str_detect(Status, "District")) %>% 
  head() %>% 
  kable(caption = "Table 1: Population by Commune")
Table 1: Population by Commune
Status Pop Name_Latin
Ward 18499 An Thoi
Ward 18307 Binh Thuy
Ward 11745 Bui Huu Nghia
Ward 16450 Long Hoa
Ward 15232 Long Tuyen
Ward 10773 Thoi An Dong

Rồi thực hiện một số thao tác xử lí dữ liệu và lấy Geo-spatial Data bằng hàm raster::getData():

# Population by district level: 

commune_df_pop <- pop_cummunes_df %>% filter(str_detect(Status, "District"))

# Get geo-spatial data by district level: 

vietnam_dis <- raster::getData("GADM", country = "Vietnam", level = 2)
# Convert to data frame: 

vietnam_df <- vietnam_dis %>% fortify(region = "NAME_2")

vietnam_df %>% 
  mutate(dis_names = stri_trans_general(id, "Latin-ASCII")) -> vietnam_df

# Join the two data set: 

full_join(vietnam_df, commune_df_pop, by = c("dis_names" = "Name_Latin")) -> df_pop_dis_vn

df_pop_dis_vn %>% mutate(Pop = Pop / 1000) -> df_pop_dis_vn

Và vẽ:

# Choropleth map: 

special_provinces <- c("Khanh Hoa", "Da Nang")
library(viridis)

ggplot() +
  geom_sf(data = vn_spatial_region %>% filter(Name %in% special_provinces)) +  
  geom_polygon(data = df_pop_dis_vn, aes(fill = Pop, x = long, y = lat, group = group), color = "grey80") + 
  theme(legend.position = c(0.8, 0.9)) + 
  theme(text = element_text(family = my_font)) + 
  theme(legend.key.size = unit(0.5, "cm"), legend.key.width = unit(0.5, "cm") ) + 
  theme(legend.text = element_text(family = my_font, color = "grey20")) + 
  theme(legend.title = element_text(family = my_font, color = "black", size = 11)) + 
  theme(panel.background = element_rect(fill = bgr_color)) +
  theme(plot.background = element_rect(fill = bgr_color)) + 
  theme(legend.background = element_rect(fill = bgr_color)) + 
  theme(axis.text = element_blank()) + 
  theme(axis.title = element_blank()) + 
  theme(axis.ticks = element_blank()) + 
  theme(plot.margin = unit(rep(0.7, 4), "cm")) + 
  theme(plot.title = element_text(size = 16)) + 
  labs(title = "Population Density (in thousands) by District for Vietnam", caption = "Data Source: https://www.citypopulation.de/Vietnam.html") + 
  annotate("text", x = 112, y = 18, label = "Paracel Islands\n(Da Nang City)", family = my_font, size = 3, fontface = "italic") +  
  annotate("text", x = 115.5, y = 12.5, label = "Spratly Islands\n(Khanh Hoa)", family = my_font, size = 3, fontface = "italic") + 
  scale_fill_viridis(direction = -1, 
                     option = "C", 
                     name = "Density:", 
                     guide = guide_colourbar(direction = "horizontal",
                                             barheight = unit(3, units = "mm"),
                                             barwidth = unit(30, units = "mm"),
                                             title.hjust = 0.5,
                                             label.hjust = 0.5, 
                                             limits = c(0, 800), 
                                             title.position = "top"))

District and Commune Level

Dưới đây là R codes vẽ map cho tỉnh Nghệ An theo cấp huyện:

nghean <- vietnam_dis[vietnam_dis$NAME_1 == "Nghệ An", ]

nghean_df <- nghean %>% 
  fortify(region = "NAME_2") %>% 
  mutate(id = stri_trans_general(id, "Latin-ASCII"))

ggplot() +
  geom_polygon(data = nghean_df,
               aes(fill = id, x = long, y = lat, group = group), 
               color = "gray69") + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.7, 4), "cm")) + 
  labs(title = "Nghe An Province by District", 
       caption = "Data Source: http://diva-gis.org/gdata")

Theo cấp xã (rất nhiều xã nên bản đồ vẽ kiểu này không có nhiều ý nghĩa):

# Get geo-spatial data by commune level: 

vietnam_com <- raster::getData("GADM", country = "Vietnam", level = 3)

nghean_com <- vietnam_com[vietnam_com$NAME_1 == "Nghệ An", ]

nghean_df_com <- nghean_com %>% 
  fortify(region = "NAME_3") %>% 
  mutate(id = stri_trans_general(id, "Latin-ASCII"))

ggplot() +
  geom_polygon(data = nghean_df_com,
               aes(fill = id, x = long, y = lat, group = group), 
               color = "gray69", show.legend = FALSE) + 
  theme(text = element_text(family = my_font)) + 
  theme(plot.margin = unit(rep(0.7, 4), "cm")) + 
  labs(title = "Nghe An Province by Commune", 
       caption = "Data Source: http://diva-gis.org/gdata")

Conclusion

R là một công cụ mạnh và linh hoạt cho Data Visualization. Mặc dù không phải là một tool chuyên về Mapping và GIS nhưng R cũng đủ mạnh để có thể thực hiện hầu hết các tình huống về về mapping như trong post này đã chỉ ra.

LS0tDQp0aXRsZTogJ0FkbWluaXN0cmF0aXZlIE1hcCBvZiBWaWV0bmFtICsgU3ByYXRseSBhbmQgUGFyYWNlbCBJc2xhbmRzJw0KYXV0aG9yOiAnQXV0aG9yOiBOZ3V5ZW4gQ2hpIER1bmcnDQpzdWJ0aXRsZTogIkRhaWx5IEdyYXBoIFNlcmllcyINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgICMgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgIyBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiAiZmxhdGx5Ig0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBjYWNoZSA9IFRSVUUsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMTApDQoNCmBgYA0KDQohW10oQzovVXNlcnMvQWRtaW4vRG9jdW1lbnRzL21hcF92aWV0bmFtMS5qcGcpDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KQ2hvcm9wbGV0aCBNYXBzIGzDoCBt4buZdCB0cm9uZyBuaOG7r25nIGPDtG5nIGPhu6UgaMOsbmgg4bqjbmggaMOzYSBk4buvIGxp4buHdSBo4buvdSDDrWNoIHbDoCDEkcaw4bujYyBz4butIGThu6VuZyBwaOG7lSBiaeG6v24uIFPhu60gZOG7pW5nIGPDtG5nIGPhu6UgaMOsbmgg4bqjbmggbsOgeSBjw7MgdHJ1eeG7gW4gdOG6o2kgbeG7mXQgY8OhY2ggbmhhbmggY2jDs25nIHbDoCDhuqVuIHTGsOG7o25nLCB2w60gZOG7pSwgdGjhu4sgcGjhuqduIGPhu6dhIGjDo25nIENvY2EgQ29sYSB04bqhaSBjw6FjIHThu4luaCDhu58gVk4gcmEgc2FvIGhv4bq3YyB04buJIGzhu4cgxJHDs2kgbmdow6hvIHRoZW8ga2h1IHbhu7FjIMSR4buLYSBsw60gYuG6sW5nIGPDoWNoIGPEg24gY+G7qSB2w6BvIMSR4buZIMSR4bqtbSBoYXkgbmjhuqF0IGPhu6dhIG3DoHUgc+G6r2MuIFRyb25nIGLDoGkgdmnhur90IG7DoHkgY2jDum5nIHRhIHPhur0gdGjhu7FjIGhp4buHbiBt4buZdCBz4buRIGzhu4duaCB0aOG7sWMgaMOgbmggdOG6oW8gcmEgYuG6o24gxJHhu5MgaMOgbmggY2jDrW5oIGPFqW5nIG5oxrAgQ2hvcm9wbGV0aCBNYXAgduG7m2kgZOG7ryBsaeG7h3UgY2hvIFZp4buHdCBOYW0uIA0KDQoNCiMgQWRtaW5pc3RyYXRpdmUgTWFwIG9mIFZpZXRuYW0NCg0KxJDhu4MgduG6vSBi4bqjbiDEkeG7kyBow6BuaCBjaMOtbmggY+G7p2EgVmnhu4d0IE5hbSB0csaw4bubYyBo4bq/dCBjaMO6bmcgdGEgY+G6p24gY8OzIEdlby1zcGF0aWFsIERhdGEuIEhp4buHbiBSIGPDsyBuaGnhu4F1IHBhY2thZ2UgaOG7lyB0cuG7oyBjaG8gdmnhu4djIGRvd25sb2FkIHbDoCBs4bqleSBsb+G6oWkgZOG7ryBsaeG7h3UgbsOgeSBt4buZdCBjw6FjaCB0aHXhuq1uIHRp4buHbiB2w60gZOG7pSBuaMawICoqcmFzdGVyKiosICoqc2YqKi4gVHV5IG5oacOqbiBjw6FjIHBhY2thZ2VzIG7DoHksIHbDrCBuaGnhu4F1IGzDrSBkbywgc+G6vSBjaMawYSBjw7MgZOG7ryBsaeG7h3UgY+G7p2EgaGFpIHF14bqnbiDEkeG6o28gVHLGsOG7nW5nIFNhIChTcHJhdGx5IElzbGFuZHMpIHbDoCBIb8OgbmcgU2EgKFBhcmFjZWwgSXNsYW5kcykgdGh14buZYyBWaeG7h3QgTmFtLiBEbyB24bqteSBjaMO6bmcgdGEgY8OzIHRo4buDIHPhu60gZOG7pW5nIGPDoWMgbmd14buTbiBk4buvIGxp4buHdSBHZW8tc3BhdGlhbCBEYXRhIGNobyBoYWkgcXXhuqduIMSR4bqjbyBuw6B5IMSRxrDhu6NjIGN1bmcgY+G6pXAgYuG7n2kgY8OhYyB04buVIGNo4bupYyB2w6AgY8OhIG5ow6JuIOG7nyBWaeG7h3QgTmFtIG5oxrAgZMaw4bubaSDEkcOieTogDQoNCmBgYHtyfQ0KDQojIENsZWFyIHdvcmtzcGFjZTogDQpybShsaXN0ID0gbHMoKSkNCg0KIyBMb2FkIHRpZHl2ZXJzZSBwYWNrYWdlOiANCg0KbGlicmFyeSh0aWR5dmVyc2UpDQogDQojIEdldCBnZW8tc3BhdGlhbCBkYXRhIGZvciBWaWV0bmFtIHByb3ZpZGVkOiANCg0KbGluazIgPC0gImh0dHBzOi8vZGF0YS5vcGVuZGV2ZWxvcG1lbnRtZWtvbmcubmV0L2RhdGFzZXQvOTk5Yzk2ZDgtZmFlMC00YjgyLTlhMmItZTQ4MWY2ZjUwZTEyL3Jlc291cmNlLzI4MThjMmM1LWU5YzMtNDQwYi1hOWI4LTMwMjlkNzI5ODA2NS9kb3dubG9hZC9kaWFwaGFudGluaGVuZ2xpc2guZ2VvanNvbj9mYmNsaWQ9SXdBUjFjb1VWTGt1RW9KUnNnYUg4MXE2b2N6MW5WZUdCaXJxcEtSQk44V1d4WFFJSlJFVUwxYnVGaTFlRSINCg0KbGluazEgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9uZ3V5ZW5kdXkxMTMzL2RhdGEvbWFpbi9EaWFfcGhhbl9UaW5oX2NhcF9uaGF0Lmdlb2pzb24/ZmJjbGlkPUl3QVIxY1BJSXN3Wjl5OFp2c21XOGlvcWg1N21hbHhubHJCcjJMM0VqWVJmTVBpRGpDcXU0a0NBMlBVeFEiDQoNCnZuX3NwYXRpYWwgPC0gc2Y6OnN0X3JlYWQobGluazEpDQoNCiMgUmVuYW1lIGZvciBEYWsgTGFrIHByb3ZpbmNlOiANCg0Kdm5fc3BhdGlhbCAlPiUgbXV0YXRlKE5hbWUgPSBjYXNlX3doZW4oTmFtZSA9PSAiRGFrIExhayIgfiAiRGFjIExhYyIsIFRSVUUgfiBOYW1lKSkgLT4gdm5fc3BhdGlhbA0KDQojIE1ha2UgYSBkcmFmdCBvZiBtYXA6IA0KDQpnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGRhdGEgPSB2bl9zcGF0aWFsKSArIA0KICBsYWJzKHRpdGxlID0gIlZpZXRuYW0gTWFwIC0gVmVyc2lvbiAxIikNCmBgYA0KDQpIb+G6t2MgdGhlbyBt4buZdCBjw6FjaCBraMOhYzogDQoNCmBgYHtyfQ0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhID0gdm5fc3BhdGlhbCwgYWVzKGZpbGwgPSBOYW1lKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyANCiAgbGFicyh0aXRsZSA9ICJWaWV0bmFtIE1hcCAtIFZlcnNpb24gMiIpDQpgYGANCg0KVHJvbmcgbmhp4buBdSB0w6xuaCBodeG7kW5nIGNow7puZyB0YSBj4bqnbiBi4bqjbiDEkeG7kyBtw6AgdGjhu4MgaGnhu4duIGPhuqMgY+G6pXAgdsO5bmcgKFJlZ2lvbikgaG/hurdjIHRp4buDdSB2w7luZyAoc3ViLXJlZ2lvbikuIFRyxrDhu5tjIGjhur90IGPhuqduIGzhuqV5IGThu68gbGnhu4d1IHbhu4EgdsO5bmcvdGnhu4N1IHbDuW5nICh24buRbiBy4bqldCBuaGnhu4F1ICBuZ3Xhu5NuKSBuaMawIHNhdTogDQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQojIFNjcmFwIGxpc3Qgb2YgcHJvdmluY2VzIGJ5IHJlZ2lvbiBhbmQgc3ViLXJlZ2lvbiBsZXZlbDogDQoNCmxpYnJhcnkocnZlc3QpDQoNCiMgQ29sbGVjdCByZWdpb24vc3ViLXJlZ2lvbiBkYXRhIGZyb20gV2lraTogDQoNCnByb3ZpbmNlcyA8LSAiaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUHJvdmluY2VzX29mX1ZpZXRuYW0iDQoNCnByb3ZpbmNlcyAlPiUgDQogIHJlYWRfaHRtbCgpICU+JSANCiAgaHRtbF9ub2Rlcyh4cGF0aCA9ICcvLypbQGlkPSJtdy1jb250ZW50LXRleHQiXS9kaXZbMV0vdGFibGVbNl0nKSAlPiUgDQogIGh0bWxfdGFibGUoKSAlPiUgDQogIC5bWzFdXSAtPiBwcm92aW5jZXNfdm4NCg0KIyBGdW5jdGlvbiBleHRyYWN0cyBkYXRhIGluIHRhYmxlOiANCg0KZXh0cmFjdF90YWJsZSA8LSBmdW5jdGlvbihpKSB7DQogIA0KICBwcm92aW5jZXNfdm4gJT4lIA0KICAgIHNsaWNlKGkpICU+JSANCiAgICBwdWxsKDMpICU+JSANCiAgICBzdHJfc3BsaXQoIlxuIiwgc2ltcGxpZnkgPSBUUlVFKSAlPiUgDQogICAgc3RyX3JlcGxhY2VfYWxsKCLigKAiLCAiIikgJT4lIA0KICAgIHN0cl9zcXVpc2goKSAlPiUgDQogICAgYXMudmVjdG9yKCkgLT4gcHJvdmluY2VfbmFtZXMNCiAgDQogIHByb3ZpbmNlc192biAlPiUgDQogICAgc2xpY2UoaSkgJT4lIA0KICAgIHB1bGwoMSkgJT4lIA0KICAgIHN0cl9zcGxpdCgiXFwoIiwgc2ltcGxpZnkgPSBUUlVFKSAlPiUgDQogICAgc3RyX3NwbGl0KCJcXCwiLCBzaW1wbGlmeSA9IFRSVUUpICU+JSANCiAgICBzdHJfcmVwbGFjZV9hbGwoIlZpZXRuYW0iLCAiIikgJT4lIA0KICAgIGFzLnZlY3RvcigpICU+JSANCiAgICBzdHJfc3F1aXNoKCkgLT4gcmVnaW9uDQogIA0KICBwcm92aW5jZXNfdm4gJT4lIA0KICAgIHNsaWNlKGkpICU+JSANCiAgICBwdWxsKDIpICU+JSANCiAgICBzdHJfc3BsaXQoIlxcKCIsIHNpbXBsaWZ5ID0gVFJVRSkgJT4lIA0KICAgIHN0cl9yZXBsYWNlX2FsbCgiXFwpIiwgIiIpICU+JSANCiAgICBzdHJfcmVwbGFjZV9hbGwoIlZpZXRuYW0iLCAiIikgJT4lIA0KICAgIGFzLnZlY3RvcigpICU+JSANCiAgICBzdHJfc3F1aXNoKCkgLT4gc3ViX3JlZ2lvbiANCiAgDQogIHRpYmJsZShwcm92aW5jZSA9IHByb3ZpbmNlX25hbWVzLCByZWdpb25fdm4gPSByZWdpb25bMl0sIHJlZ2lvbl9lbiA9IHJlZ2lvblsxXSwgDQogICAgICAgICBzdWJfcmVnaW9uX3ZuID0gc3ViX3JlZ2lvblsyXSwgc3ViX3JlZ2lvbl9lbiA9IHN1Yl9yZWdpb25bMV0pIC0+IGRmX2ZpbmFsDQogIA0KICByZXR1cm4oZGZfZmluYWwpDQogIA0KfQ0KDQojIFVzZSB0aGUgZnVuY3Rpb246IA0KDQpsYXBwbHkoMTpucm93KHByb3ZpbmNlc192biksIGV4dHJhY3RfdGFibGUpIC0+IHByb3ZpbmNlX3JlZ2lvbg0KDQpkby5jYWxsKCJiaW5kX3Jvd3MiLCBwcm92aW5jZV9yZWdpb24pIC0+IHByb3ZpbmNlX3JlZ2lvbl92aWV0bmFtDQoNCiMgUmVuYW1lIGZvciBzb21lIHByb3Zpbm5jZXM6IA0KDQpsaWJyYXJ5KHN0cmluZ2kpDQoNCnByb3ZpbmNlX3JlZ2lvbl92aWV0bmFtICU+JSANCiAgbXV0YXRlKHByb3ZpbmNlX2xhdGluID0gc3RyaV90cmFuc19nZW5lcmFsKHByb3ZpbmNlLCAiTGF0aW4tQVNDSUkiKSkgJT4lIA0KICBtdXRhdGUocHJvdmluY2VfbGF0aW4gPSBjYXNlX3doZW4ocHJvdmluY2VfbGF0aW4gPT0gIlRodWEgVGhpZW4tSHVlIiB+ICJUaHVhIFRoaWVuIC0gSHVlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm92aW5jZV9sYXRpbiA9PSAiQmEgUmlhLVZ1bmcgVGF1IiB+ICJCYSBSaWEgLSBWdW5nIFRhdSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvdmluY2VfbGF0aW4gPT0gIkhvIENoaSBNaW5oIENpdHkiIH4gIlRQLiBIbyBDaGkgTWluaCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHByb3ZpbmNlX2xhdGluKSkgLT4gcHJvdmluY2VfcmVnaW9uX3ZpZXRuYW0NCg0KYGBgDQoNClNhdSDEkcOzIGpvaW4gaGFpIGLhu5kgZOG7ryBsaeG7h3UgKEdlby1zcGF0aWFsIGFuZCBSZWdpb24gRGF0YXNldHMpIGzhuqFpIHbDoCBtYXBwaW5nOiANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQojIENvbWJpbmUgdGhlIHR3byBkYXRhc2V0czogDQoNCmZ1bGxfam9pbih2bl9zcGF0aWFsLCBwcm92aW5jZV9yZWdpb25fdmlldG5hbSwgYnkgPSBjKCJOYW1lIiA9ICJwcm92aW5jZV9sYXRpbiIpKSAtPiB2bl9zcGF0aWFsX3JlZ2lvbg0KDQojIEFkbWluaXN0cmF0aXZlIG1hcCBvZiBWaWV0bmFtOiANCg0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhID0gdm5fc3BhdGlhbF9yZWdpb24sIGFlcyhmaWxsID0gc3ViX3JlZ2lvbl9lbikpICsgDQogIGxhYnModGl0bGUgPSAiQWRtaW5pc3RyYXRpdmUgUmVnaW9ucyBvZiBWaWV0IE5hbSIpDQoNCmBgYA0KDQohW10oQzovVXNlcnMvQWRtaW4vRG9jdW1lbnRzL21hcDIuanBnKQ0KDQpIb+G6t2MgbMOgbSBjaG8gYuG6o24gxJHhu5MgdHLhu58gbsOqbiDEkeG6uXAgaMahbjogDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KIyBFbGVnYW50IG1hcCBvZiBWaWV0bmFtOiANCg0KbXlfY29sb3JzIDwtIGMoJyNlNDFhMWMnLCcjMzc3ZWI4JywnIzRkYWY0YScsJyM5ODRlYTMnLCcjZmY3ZjAwJywnI2ZmZmYzMycsJyNhNjU2MjgnLCcjZjc4MWJmJykNCmJncl9jb2xvciA8LSAiYXp1cmUiDQoNCmxpYnJhcnkoZXh0cmFmb250KQ0KbXlfZm9udCA8LSAiUm9ib3RvIENvbmRlbnNlZCINCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCg0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhID0gdm5fc3BhdGlhbF9yZWdpb24sIGFlcyhmaWxsID0gc3ViX3JlZ2lvbl9lbikpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG15X2NvbG9ycywgbmFtZSA9ICJSZWdpb246IikgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjgyLCAwLjgzKSkgKyANCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSBteV9mb250KSkgKyANCiAgdGhlbWUobGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjUsICJjbSIpLCBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjUsICJjbSIpICkgKyANCiAgdGhlbWUobGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gbXlfZm9udCwgY29sb3IgPSAiZ3JleTIwIikpICsgDQogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGJncl9jb2xvcikpICsNCiAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBiZ3JfY29sb3IpKSArIA0KICB0aGVtZShsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYmdyX2NvbG9yKSkgKyANCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KHJlcCgwLjcsIDQpLCAiY20iKSkgKyANCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpKSArIA0KICBsYWJzKHRpdGxlID0gIkFkbWluaXN0cmF0aXZlIFJlZ2lvbnMgb2YgVmlldCBOYW0iLCANCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBodHRwczovL2RhdGEub3BlbmRldmVsb3BtZW50bWVrb25nLm5ldCIpDQpgYGANCg0KIVtdKEM6L1VzZXJzL0FkbWluL0RvY3VtZW50cy9tYXBfdmlldG5hbTEuanBnKQ0KDQojIENob3JvcGxldGggTWFwDQoNClRyxrDhu5tjIGjhur90IGzhuqV5IGThu68gbGnhu4d1IHbhu4EgbeG6rXQgxJHhu5kgZMOibiBz4buRIHRoZW8gY+G6oyBj4bqlcCBIdXnhu4duIHbDoCBYw6MgY2hvIGPDoWMgxJHGoW4gduG7iyBow6BuaCBjaMOtbmggY+G7p2EgVmnhu4d0IE5hbTogDQogIA0KDQpgYGB7cn0NCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgU3RhZ2UgMTogRXh0cmFjdCBhbGwgbGlua3MNCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQphbGxfbGlua3MgPC0gImh0dHBzOi8vd3d3LmNpdHlwb3B1bGF0aW9uLmRlL1ZpZXRuYW0uaHRtbCINCnBnIDwtIHJlYWRfaHRtbChhbGxfbGlua3MpDQptIDwtIGh0bWxfbm9kZXMocGcsICJhIikNCmsgPC0gaHRtbF9hdHRyKG0sICJocmVmIikNCmFsbF9saW5rc19jb21tdW5lc19sZXZlbCA8LSAgc3RyX2MoImh0dHBzOi8vd3d3LmNpdHlwb3B1bGF0aW9uLmRlL2VuL3ZpZXRuYW0vIiwga1stYygxOjYpXSkNCg0KIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KIyAgU3RhZ2UgMjogRnVuY3Rpb24gZm9yIGdldHRpbmcgcG9wb2xhdGlvbiBieSBjb21tdW5lIGxldmVsDQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmdldF9wb3BfZGF0YSA8LSBmdW5jdGlvbihsaW5rKSB7DQogIA0KICBsaW5rICU+JSANCiAgICByZWFkX2h0bWwoKSAlPiUgDQogICAgaHRtbF9ub2Rlcyh4cGF0aCA9ICcvLypbQGlkPSJ0bCJdJykgJT4lIA0KICAgIGh0bWxfdGFibGUoZmlsbCA9IFRSVUUpICU+JSANCiAgICAuW1sxXV0gLT4gZGYNCiAgDQogIGRmICU+JSAgIA0KICAgIHNlbGVjdCgxOjMpIC0+IGRmDQogIA0KICBuYW1lcyhkZikgPC0gYygiTmFtZSIsICJTdGF0dXMiLCAiUG9wIikNCiAgDQogIGRmICU+JSANCiAgICBtdXRhdGUoTmFtZV9MYXRpbiA9IHN0cmluZ2k6OnN0cmlfdHJhbnNfZ2VuZXJhbChOYW1lLCAiTGF0aW4tQVNDSUkiKSwgDQogICAgICAgICAgIFBvcCA9IHN0cl9yZXBsYWNlX2FsbChQb3AsICJbXjAtOV0iLCAiIikgJT4lIGFzLmludGVnZXIoKSkgJT4lIA0KICAgIHJldHVybigpDQogIA0KfQ0KDQoNCiMgVXNlIHRoZSBmdW5jdGlvbjogDQoNCmxhcHBseShhbGxfbGlua3NfY29tbXVuZXNfbGV2ZWwsIGdldF9wb3BfZGF0YSkgLT4gcG9wX2N1bW11bmVzX2xpc3QNCg0KZG8uY2FsbCgiYmluZF9yb3dzIiwgcG9wX2N1bW11bmVzX2xpc3QpIC0+IHBvcF9jdW1tdW5lc19kZg0KDQojIFNob3cgc29tZSBvYnNlcnZhdGlvbnM6IA0KDQpsaWJyYXJ5KGtuaXRyKQ0KDQpwb3BfY3VtbXVuZXNfZGYgJT4lIA0KICBzZWxlY3QoLTEpICU+JSANCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KFN0YXR1cywgIkRpc3RyaWN0IikpICU+JSANCiAgaGVhZCgpICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSAxOiBQb3B1bGF0aW9uIGJ5IENvbW11bmUiKQ0KDQpgYGANCg0KDQpS4buTaSB0aOG7sWMgaGnhu4duIG3hu5l0IHPhu5EgdGhhbyB0w6FjIHjhu60gbMOtIGThu68gbGnhu4d1IHbDoCBs4bqleSBHZW8tc3BhdGlhbCBEYXRhIGLhurFuZyBow6BtIGByYXN0ZXI6OmdldERhdGEoKWA6IA0KDQpgYGB7cn0NCg0KIyBQb3B1bGF0aW9uIGJ5IGRpc3RyaWN0IGxldmVsOiANCg0KY29tbXVuZV9kZl9wb3AgPC0gcG9wX2N1bW11bmVzX2RmICU+JSBmaWx0ZXIoc3RyX2RldGVjdChTdGF0dXMsICJEaXN0cmljdCIpKQ0KDQojIEdldCBnZW8tc3BhdGlhbCBkYXRhIGJ5IGRpc3RyaWN0IGxldmVsOiANCg0KdmlldG5hbV9kaXMgPC0gcmFzdGVyOjpnZXREYXRhKCJHQURNIiwgY291bnRyeSA9ICJWaWV0bmFtIiwgbGV2ZWwgPSAyKQ0KYGBgDQoNCg0KYGBge3J9DQojIENvbnZlcnQgdG8gZGF0YSBmcmFtZTogDQoNCnZpZXRuYW1fZGYgPC0gdmlldG5hbV9kaXMgJT4lIGZvcnRpZnkocmVnaW9uID0gIk5BTUVfMiIpDQoNCnZpZXRuYW1fZGYgJT4lIA0KICBtdXRhdGUoZGlzX25hbWVzID0gc3RyaV90cmFuc19nZW5lcmFsKGlkLCAiTGF0aW4tQVNDSUkiKSkgLT4gdmlldG5hbV9kZg0KDQojIEpvaW4gdGhlIHR3byBkYXRhIHNldDogDQoNCmZ1bGxfam9pbih2aWV0bmFtX2RmLCBjb21tdW5lX2RmX3BvcCwgYnkgPSBjKCJkaXNfbmFtZXMiID0gIk5hbWVfTGF0aW4iKSkgLT4gZGZfcG9wX2Rpc192bg0KDQpkZl9wb3BfZGlzX3ZuICU+JSBtdXRhdGUoUG9wID0gUG9wIC8gMTAwMCkgLT4gZGZfcG9wX2Rpc192bg0KDQpgYGANCg0KVsOgIHbhur06IA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCg0KIyBDaG9yb3BsZXRoIG1hcDogDQoNCnNwZWNpYWxfcHJvdmluY2VzIDwtIGMoIktoYW5oIEhvYSIsICJEYSBOYW5nIikNCmxpYnJhcnkodmlyaWRpcykNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB2bl9zcGF0aWFsX3JlZ2lvbiAlPiUgZmlsdGVyKE5hbWUgJWluJSBzcGVjaWFsX3Byb3ZpbmNlcykpICsgIA0KICBnZW9tX3BvbHlnb24oZGF0YSA9IGRmX3BvcF9kaXNfdm4sIGFlcyhmaWxsID0gUG9wLCB4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yID0gImdyZXk4MCIpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44LCAwLjkpKSArIA0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9IG15X2ZvbnQpKSArIA0KICB0aGVtZShsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuNSwgImNtIiksIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuNSwgImNtIikgKSArIA0KICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSBteV9mb250LCBjb2xvciA9ICJncmV5MjAiKSkgKyANCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhbWlseSA9IG15X2ZvbnQsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDExKSkgKyANCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYmdyX2NvbG9yKSkgKw0KICB0aGVtZShwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGJncl9jb2xvcikpICsgDQogIHRoZW1lKGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBiZ3JfY29sb3IpKSArIA0KICB0aGVtZShheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChyZXAoMC43LCA0KSwgImNtIikpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2KSkgKyANCiAgbGFicyh0aXRsZSA9ICJQb3B1bGF0aW9uIERlbnNpdHkgKGluIHRob3VzYW5kcykgYnkgRGlzdHJpY3QgZm9yIFZpZXRuYW0iLCBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBodHRwczovL3d3dy5jaXR5cG9wdWxhdGlvbi5kZS9WaWV0bmFtLmh0bWwiKSArIA0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSAxMTIsIHkgPSAxOCwgbGFiZWwgPSAiUGFyYWNlbCBJc2xhbmRzXG4oRGEgTmFuZyBDaXR5KSIsIGZhbWlseSA9IG15X2ZvbnQsIHNpemUgPSAzLCBmb250ZmFjZSA9ICJpdGFsaWMiKSArICANCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMTE1LjUsIHkgPSAxMi41LCBsYWJlbCA9ICJTcHJhdGx5IElzbGFuZHNcbihLaGFuaCBIb2EpIiwgZmFtaWx5ID0gbXlfZm9udCwgc2l6ZSA9IDMsIGZvbnRmYWNlID0gIml0YWxpYyIpICsgDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwgDQogICAgICAgICAgICAgICAgICAgICBvcHRpb24gPSAiQyIsIA0KICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJEZW5zaXR5OiIsIA0KICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9jb2xvdXJiYXIoZGlyZWN0aW9uID0gImhvcml6b250YWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFyaGVpZ2h0ID0gdW5pdCgzLCB1bml0cyA9ICJtbSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFyd2lkdGggPSB1bml0KDMwLCB1bml0cyA9ICJtbSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUuaGp1c3QgPSAwLjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbC5oanVzdCA9IDAuNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgwMCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUucG9zaXRpb24gPSAidG9wIikpDQoNCg0KYGBgDQoNCiFbXShDOi9Vc2Vycy9BZG1pbi9Eb2N1bWVudHMvbWFwNi5qcGcpDQoNCiMgRGlzdHJpY3QgYW5kIENvbW11bmUgTGV2ZWwNCg0KRMaw4bubaSDEkcOieSBsw6AgUiBjb2RlcyB24bq9IG1hcCBjaG8gdOG7iW5oIE5naOG7hyBBbiB0aGVvIGPhuqVwIGh1eeG7h246IA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCm5naGVhbiA8LSB2aWV0bmFtX2Rpc1t2aWV0bmFtX2RpcyROQU1FXzEgPT0gIk5naOG7hyBBbiIsIF0NCg0KbmdoZWFuX2RmIDwtIG5naGVhbiAlPiUgDQogIGZvcnRpZnkocmVnaW9uID0gIk5BTUVfMiIpICU+JSANCiAgbXV0YXRlKGlkID0gc3RyaV90cmFuc19nZW5lcmFsKGlkLCAiTGF0aW4tQVNDSUkiKSkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3BvbHlnb24oZGF0YSA9IG5naGVhbl9kZiwNCiAgICAgICAgICAgICAgIGFlcyhmaWxsID0gaWQsIHggPSBsb25nLCB5ID0gbGF0LCBncm91cCA9IGdyb3VwKSwgDQogICAgICAgICAgICAgICBjb2xvciA9ICJncmF5NjkiKSArIA0KICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9IG15X2ZvbnQpKSArIA0KICB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQocmVwKDAuNywgNCksICJjbSIpKSArIA0KICBsYWJzKHRpdGxlID0gIk5naGUgQW4gUHJvdmluY2UgYnkgRGlzdHJpY3QiLCANCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBodHRwOi8vZGl2YS1naXMub3JnL2dkYXRhIikNCiAgDQpgYGANCg0KIVtdKEM6L1VzZXJzL0FkbWluL0RvY3VtZW50cy9uYTEuanBnKQ0KVGhlbyBj4bqlcCB4w6MgKHLhuqV0IG5oaeG7gXUgeMOjIG7Dqm4gYuG6o24gxJHhu5MgduG6vSBraeG7g3UgbsOgeSBraMO0bmcgY8OzIG5oaeG7gXUgw70gbmdoxKlhKTogDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KDQojIEdldCBnZW8tc3BhdGlhbCBkYXRhIGJ5IGNvbW11bmUgbGV2ZWw6IA0KDQp2aWV0bmFtX2NvbSA8LSByYXN0ZXI6OmdldERhdGEoIkdBRE0iLCBjb3VudHJ5ID0gIlZpZXRuYW0iLCBsZXZlbCA9IDMpDQoNCm5naGVhbl9jb20gPC0gdmlldG5hbV9jb21bdmlldG5hbV9jb20kTkFNRV8xID09ICJOZ2jhu4cgQW4iLCBdDQoNCm5naGVhbl9kZl9jb20gPC0gbmdoZWFuX2NvbSAlPiUgDQogIGZvcnRpZnkocmVnaW9uID0gIk5BTUVfMyIpICU+JSANCiAgbXV0YXRlKGlkID0gc3RyaV90cmFuc19nZW5lcmFsKGlkLCAiTGF0aW4tQVNDSUkiKSkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX3BvbHlnb24oZGF0YSA9IG5naGVhbl9kZl9jb20sDQogICAgICAgICAgICAgICBhZXMoZmlsbCA9IGlkLCB4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIA0KICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JheTY5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyANCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSBteV9mb250KSkgKyANCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KHJlcCgwLjcsIDQpLCAiY20iKSkgKyANCiAgbGFicyh0aXRsZSA9ICJOZ2hlIEFuIFByb3ZpbmNlIGJ5IENvbW11bmUiLCANCiAgICAgICBjYXB0aW9uID0gIkRhdGEgU291cmNlOiBodHRwOi8vZGl2YS1naXMub3JnL2dkYXRhIikNCmBgYA0KDQohW10oQzovVXNlcnMvQWRtaW4vRG9jdW1lbnRzL25hMi5qcGcpDQoNCiMgQ29uY2x1c2lvbg0KDQpSIGzDoCBt4buZdCBjw7RuZyBj4bulIG3huqFuaCB2w6AgbGluaCBob+G6oXQgY2hvIERhdGEgVmlzdWFsaXphdGlvbi4gTeG6t2MgZMO5IGtow7RuZyBwaOG6o2kgbMOgIG3hu5l0IHRvb2wgY2h1ecOqbiB24buBIE1hcHBpbmcgdsOgIEdJUyBuaMawbmcgUiBjxaluZyDEkeG7pyBt4bqhbmggxJHhu4MgY8OzIHRo4buDIHRo4buxYyBoaeG7h24gaOG6p3UgaOG6v3QgY8OhYyB0w6xuaCBodeG7kW5nIHbhu4EgduG7gSBtYXBwaW5nIG5oxrAgdHJvbmcgcG9zdCBuw6B5IMSRw6MgY2jhu4kgcmEuIA0KDQojIFJlZmVyZW5jZXMNCg0KQ8OhYyBjYXNlcyB0csOsbmggYsOgeSB0cm9uZyBwb3N0IG7DoHkgbmjhuqV0IMSR4buLbmggbMOgIGtow7RuZyB0aOG7gyBjb3ZlciBo4bq/dCB04bqldCBj4bqjIGPDoWMgdMOsbmggaHXhu5FuZyB24buBIG1hcHBpbmcgdHJvbmcgdGjhu7FjIHThur8uIERvIHbhuq15IGPDoWMgbmd14buTbiB0aGFvIGto4bqjbyBzYXUgbMOgIGjhu691IMOtY2g6IA0KDQoxLiBodHRwczovL21pa2VncnV6LmdpdGh1Yi5pby9hcnRpY2xlcy8yMDE3LTAzL21hcHBpbmctaW4tUg0KMi4gaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy9yLzIwMTgvMTAvMjUvZ2dwbG90Mi1zZi0zLmh0bWwNCjMuIGh0dHBzOi8vd3d3Lmplc3Nlc2FkbGVyLmNvbS9wb3N0L3NpbXBsZS1mZWF0dXJlLW9iamVjdHMvDQo0LiBodHRwczovL2NlbmdlbC5naXRodWIuaW8vUi1zcGF0aWFsL2ludHJvLmh0bWwNCjUuIGh0dHBzOi8vZG9taW5pY3JveWUuZ2l0aHViLmlvL2VuLzIwMTgvYWNjZXNzLXRvLWNsaW1hdGUtcmVhbmFseXNpcy1kYXRhLWZyb20tci8NCjYuIGh0dHBzOi8vZ2VvY29tcHIucm9iaW5sb3ZlbGFjZS5uZXQvZ2VvbWV0cmljLW9wZXJhdGlvbnMuaHRtbA0KNy4gaHR0cHM6Ly93d3cuci1zcGF0aWFsLm9yZy9yLzIwMTgvMTAvMjUvZ2dwbG90Mi1zZi0yLmh0bWwNCjguIGh0dHBzOi8vZ2l0aHViLmNvbS9yb3BlbnNjaS9nZW9qc29uaW8NCjkuIGh0dHBzOi8vZ2VvY29tcHIucm9iaW5sb3ZlbGFjZS5uZXQvYXR0ci5odG1sDQoNCg==