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
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")

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==