Начало работы
Грузим библиотеки и данные
library(xml2)
library(tidyverse)
library(igraph)
library(ggraph)
Загружаем XML-файл романа
ns <- c(tei = "http://www.tei-c.org/ns/1.0")
xml_file_path <- "War_and_Peace.xml"
doc <- read_xml(xml_file_path)
Персонажи
Найдем всех персонажей
persons <- xml_find_all(doc, "//tei:person", ns = ns)
xml_strip_text <- \(text) text |>
xml_text() |>
str_squish()
build_name <- function(pers_name_node) {
forename <- pers_name_node |>
xml_find_all(".//tei:forename", ns = ns) |>
xml_strip_text()
surname <- pers_name_node |>
xml_find_all(".//tei:surname", ns = ns) |>
xml_strip_text()
patronymic <- pers_name_node |>
xml_find_first(".//tei:patronymic", ns = ns) |>
xml_strip_text()
name_parts <- c(forename, patronymic, surname)
name_parts <- name_parts[!is.na(name_parts) & name_parts != ""]
if (length(name_parts) == 0) {
return(xml_text(pers_name_node) |> str_squish())
}
paste(name_parts, collapse = " ")
}
Соберем их в таблицу
person_list <- persons |>
map_df(function(p) {
pid <- xml_attr(p, "id")
pers_name <- xml_find_first(p, ".//tei:persName", ns = ns)
name_text <- build_name(pers_name) |> str_squish()
if (is.na(name_text) || name_text == "") {
name_text <- pid
}
tibble(id = pid, name = name_text)
}) |>
distinct(id, .keep_all = TRUE) |>
filter(!is.na(name), name != "")
cat("Персонажей всего:", nrow(person_list), "\n\n")
Персонажей всего: 84
Диалоги
Достанем диалоги
said_nodes <- xml_find_all(doc, "//tei:said[@who]", ns = ns)
cat("Найдено реплик (said) с атрибутом who:", length(said_nodes), "\n")
Найдено реплик (said) с атрибутом who: 6306
# для поиска абзаца-родителя
find_parent_paragraph <- function(node) {
p <- xml_parent(node)
while (!is.na(p) && xml_name(p) != "p") {
p <- xml_parent(p)
}
return(p)
}
extract_persons_from_paragraph <- function(paragraph) {
if (is.na(paragraph) || xml_name(paragraph) != "p") return(character(0))
rs_nodes <- xml_find_all(paragraph, ".//tei:rs[@ref]", ns = ns)
rs_nodes |>
map_chr(~ xml_attr(.x, "ref") |> str_remove("^#")) |>
unique()
}
dialogues_df <- map_dfr(said_nodes, function(said) {
speaker <- xml_attr(said, "who") |> str_remove("^#")
if (is.na(speaker) || speaker == "") return(NULL)
# абзац диалога
parent_p <- find_parent_paragraph(said)
if (is.na(parent_p) || xml_name(parent_p) != "p") return(NULL)
# персонажи в абзаце
all_persons <- extract_persons_from_paragraph(parent_p)
# адресатами считаем всех, кроме говорящего
addressees <- setdiff(all_persons, speaker)
if (length(addressees) == 0) return(NULL)
# ребра от говорящего до адресатов
map_dfr(addressees, function(addressee) {
tibble(from = speaker, to = addressee, weight = 1)
})
})
cat("Найдено диалогов:", nrow(dialogues_df), "\n")
Найдено диалогов: 3709
Суммируем веса, собираем ребра
edges_df <- dialogues_df |>
group_by(from, to) |>
summarise(weight = sum(weight), .groups = "drop") |>
mutate(
from_sorted = pmin(from, to),
to_sorted = pmax(from, to)
) |>
group_by(from_sorted, to_sorted) |>
summarise(weight = sum(weight), .groups = "drop") |>
rename(from = from_sorted, to = to_sorted)
cat("Уникальных взаимодействий (пар персонажей):", nrow(edges_df), "\n")
Уникальных взаимодействий (пар персонажей): 746
cat("Всего диалогов:", sum(edges_df$weight), "\n\n")
Всего диалогов: 3709
Некоторые персонажи, упомянутые в диалогах, могут отсутствовать в
<listPerson>. Добавим их.
vertex_ids_from_edges <- unique(c(edges_df$from, edges_df$to))
# какие id из рёбер отсутствуют в person_list
missing_ids <- setdiff(vertex_ids_from_edges, person_list$id)
if (length(missing_ids) > 0) {
cat("Найдено", length(missing_ids), "ID персонажей в рёбрах, которых нет в person_list\n")
missing_persons <- tibble(
id = missing_ids,
name = missing_ids
)
person_list <- bind_rows(person_list, missing_persons) |>
distinct(id, .keep_all = TRUE)
}
Найдено 200 ID персонажей в рёбрах, которых нет в person_list
# у всех должно быть имя
person_list <- person_list |>
mutate(name = ifelse(is.na(name) | name == "", id, name))
Граф диалогов
g <- graph_from_data_frame(edges_df, directed = FALSE, vertices = person_list)
V(g)$display_name <- V(g)$name
V(g)$id <- V(g)$name
Описание графа
Проверим связность
is_connected(g)
[1] FALSE
Несвязный.
Ненаправленный.
cat("Количество вершин:", vcount(g), "\n")
Количество вершин: 284
cat("Количество рёбер:", ecount(g), "\n")
Количество рёбер: 746
cat("Плотность графа:", round(edge_density(g), 4), "\n")
Плотность графа: 0.0186
cat("Количество компонент связности:", count_components(g), "\n")
Количество компонент связности: 7
Граф очень большой и разреженный, много персонажей, но мало кто
взаимодействует со всеми или почти всеми остальными (что ожидаемо)
Центральности
V(g)$degree <- degree(g, mode = "all")
V(g)$weighted_degree <- strength(g)
V(g)$betweenness <- betweenness(g, normalized = TRUE)
V(g)$closeness <- closeness(g, normalized = TRUE)
V(g)$eigen_centrality <- eigen_centrality(g)$vector
V(g)$coreness <- coreness(g)
Подграф: топ-15 персонажей по кол-ву диалогов
Обоснование: хотим посмотреть на наиболее “разговорчивых”
персонажей
top_n <- 15
degree_df <- data.frame(
name = V(g)$name,
weighted_degree = V(g)$weighted_degree
) |>
arrange(desc(weighted_degree)) |>
head(top_n)
top_nodes <- degree_df$name
g_core <- induced_subgraph(g, top_nodes)
Характеристики подграфа
cat("Метод: топ-", length(top_nodes), "персонажей по количеству диалогов\n")
Метод: топ- 15 персонажей по количеству диалогов
cat("Вершин в подграфе:", vcount(g_core), "\n")
Вершин в подграфе: 15
cat("Рёбер в подграфе:", ecount(g_core), "\n")
Рёбер в подграфе: 83
cat("Плотность подграфа:", round(edge_density(g_core), 4), "\n")
Плотность подграфа: 0.7905
У подграфа плотность гораздо выше, чем у графа. Топ-15 персонажей по
кол-ву диалогов активно взаимодействуют друг с другом (являются таким
“ядром” произведения)
Персонажи
print(degree_df)
Сообщества
set.seed(42)
communities <- cluster_walktrap(g, weights = E(g)$weight)
V(g)$community <- membership(communities)
mod_val <- modularity(communities)
Результат анализа сообществ с помощью walktrap
cat("Модулярность:", round(mod_val, 4), "\n")
Модулярность: 0.351
cat("Количество сообществ:", max(V(g)$community), "\n")
Количество сообществ: 112
модулярность > 0.3 указывает на значимую кластерную структуру
(хорошо выделяются кластера). То есть, хорошо выделимы (заметны) группы
часто общающихся персонажей.
articulation_pts <- articulation_points(g)
articulation_names <- V(g)$name[articulation_pts]
cat("Найдено:", length(articulation_pts), "точек сочленения\n")
Найдено: 39 точек сочленения
cat("Примеры:", paste(head(articulation_names, 10), collapse = ", "), "\n")
Примеры: Рамбаль, Василий Сергеевич Курагин, Ипполит Курагин, abbe_Morio, Анна Павловна Шерер, Тушин, Вася Василий Васька Дмитрич Денисов, Жерков, Яков Алпатыч, Ростова
clique_max_size <- tryCatch(clique_num(g), error = function(e) NA)
cat("Наибольшая клика содержит", clique_max_size, "персонажей\n")
Наибольшая клика содержит 9 персонажей
Топ-20 персонажей по центральностям
top_characters <- data.frame(
Персонаж = V(g)$name,
Диалогов = V(g)$weighted_degree,
Партнёров = V(g)$degree,
Betweenness = round(V(g)$betweenness, 4),
Closeness = round(V(g)$closeness, 4),
Eigenvector = round(V(g)$eigen_centrality, 4),
Coreness = V(g)$coreness,
Сообщество = V(g)$community
) |>
arrange(desc(Диалогов)) |>
head(20)
print(top_characters)
NA
Визуализация основного графа (топ-15)
top_n_main <- 15
top_indices <- order(V(g)$weighted_degree, decreasing = TRUE)[1:min(top_n_main, vcount(g))]
top_nodes_main <- V(g)$name[top_indices]
top_nodes_main <- top_nodes_main[!is.na(top_nodes_main)]
g_top <- induced_subgraph(g, top_nodes_main)
min_edge_weight <- 30
g_top_filtered <- subgraph.edges(g_top, eids = which(E(g_top)$weight >= min_edge_weight), delete.vertices = FALSE)
set.seed(42)
p_main <- ggraph(g_top_filtered, layout = "stress") +
geom_edge_link(aes(alpha = weight, width = weight),
color = "grey50", show.legend = FALSE) +
geom_node_point(aes(size = weighted_degree,
fill = as.factor(community)),
shape = 21, color = "grey30", alpha = 0.9) +
geom_node_text(aes(label = name),
repel = TRUE, size = 3.5) +
scale_size_continuous(range = c(4, 16), name = "Количество диалогов") +
scale_fill_brewer("Сообщество", palette = "Set3") +
labs(title = "Сеть диалогов персонажей романа 'Война и мир'",
subtitle = paste0("Топ-15 по количеству диалогов | Вес рёбер ≥ ", min_edge_weight, " | ",
"Модулярность = ", round(mod_val, 3), " | ",
"Вершин: ", vcount(g_top_filtered), " | Рёбер: ", ecount(g_top_filtered))) +
theme_graph() +
theme(legend.position = "bottom")
print(p_main)

Визуализация подграфа (топ-15)
set.seed(42)
p_core <- ggraph(g_core, layout = "stress") +
geom_edge_link(aes(alpha = weight, width = weight),
color = "grey50", show.legend = FALSE) +
geom_node_point(aes(size = weighted_degree,
fill = as.factor(coreness)),
shape = 21, color = "grey30", alpha = 0.9) +
geom_node_text(aes(label = name),
repel = TRUE, size = 4, fontface = "bold") +
scale_size_continuous(range = c(5, 18), name = "Количество диалогов") +
scale_fill_brewer("K-core", palette = "RdYlBu") +
labs(title = "Топ-15 персонажей по количеству диалогов",
subtitle = paste0("Вершин: ", vcount(g_core), " | Рёбер: ", ecount(g_core),
" | Плотность: ", round(edge_density(g_core), 3))) +
theme_graph() +
theme(legend.position = "bottom")
print(p_core)

LS0tCnRpdGxlOiAi0JDQvdCw0LvQuNC3INC00LjQsNC70L7Qs9C+0LIg0L/QtdGA0YHQvtC90LDQttC10Lkg0YDQvtC80LDQvdCwICfQktC+0LnQvdCwINC4INC80LjRgCciCmF1dGhvcjogIkVnb3IgVm9yb25jaGlraGluIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojINCd0LDRh9Cw0LvQviDRgNCw0LHQvtGC0YsKCtCT0YDRg9C30LjQvCDQsdC40LHQu9C40L7RgtC10LrQuCDQuCDQtNCw0L3QvdGL0LUKCmBgYHtyfQpsaWJyYXJ5KHhtbDIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmBgYAoK0JfQsNCz0YDRg9C20LDQtdC8IFhNTC3RhNCw0LnQuyDRgNC+0LzQsNC90LAKCmBgYHtyfQpucyA8LSBjKHRlaSA9ICJodHRwOi8vd3d3LnRlaS1jLm9yZy9ucy8xLjAiKQp4bWxfZmlsZV9wYXRoIDwtICJXYXJfYW5kX1BlYWNlLnhtbCIKCmRvYyA8LSByZWFkX3htbCh4bWxfZmlsZV9wYXRoKQpgYGAKCiMg0J/QtdGA0YHQvtC90LDQttC4CgrQndCw0LnQtNC10Lwg0LLRgdC10YUg0L/QtdGA0YHQvtC90LDQttC10LkKCmBgYHtyfQpwZXJzb25zIDwtIHhtbF9maW5kX2FsbChkb2MsICIvL3RlaTpwZXJzb24iLCBucyA9IG5zKQoKeG1sX3N0cmlwX3RleHQgPC0gXCh0ZXh0KSB0ZXh0IHw+CiAgeG1sX3RleHQoKSB8PgogIHN0cl9zcXVpc2goKQoKYnVpbGRfbmFtZSA8LSBmdW5jdGlvbihwZXJzX25hbWVfbm9kZSkgewogIGZvcmVuYW1lIDwtIHBlcnNfbmFtZV9ub2RlIHw+CiAgICB4bWxfZmluZF9hbGwoIi4vL3RlaTpmb3JlbmFtZSIsIG5zID0gbnMpIHw+CiAgICB4bWxfc3RyaXBfdGV4dCgpCiAgCiAgc3VybmFtZSA8LSBwZXJzX25hbWVfbm9kZSB8PgogICAgeG1sX2ZpbmRfYWxsKCIuLy90ZWk6c3VybmFtZSIsIG5zID0gbnMpIHw+CiAgICB4bWxfc3RyaXBfdGV4dCgpCiAgCiAgcGF0cm9ueW1pYyA8LSBwZXJzX25hbWVfbm9kZSB8PgogICAgeG1sX2ZpbmRfZmlyc3QoIi4vL3RlaTpwYXRyb255bWljIiwgbnMgPSBucykgfD4KICAgIHhtbF9zdHJpcF90ZXh0KCkKICAKICBuYW1lX3BhcnRzIDwtIGMoZm9yZW5hbWUsIHBhdHJvbnltaWMsIHN1cm5hbWUpCiAgbmFtZV9wYXJ0cyA8LSBuYW1lX3BhcnRzWyFpcy5uYShuYW1lX3BhcnRzKSAmIG5hbWVfcGFydHMgIT0gIiJdCiAgCiAgaWYgKGxlbmd0aChuYW1lX3BhcnRzKSA9PSAwKSB7CiAgICByZXR1cm4oeG1sX3RleHQocGVyc19uYW1lX25vZGUpIHw+IHN0cl9zcXVpc2goKSkKICB9CiAgCiAgcGFzdGUobmFtZV9wYXJ0cywgY29sbGFwc2UgPSAiICIpCn0KYGBgCgrQodC+0LHQtdGA0LXQvCDQuNGFINCyINGC0LDQsdC70LjRhtGDCgpgYGB7cn0KcGVyc29uX2xpc3QgPC0gcGVyc29ucyB8PgogIG1hcF9kZihmdW5jdGlvbihwKSB7CiAgICBwaWQgPC0geG1sX2F0dHIocCwgImlkIikKICAgIHBlcnNfbmFtZSA8LSB4bWxfZmluZF9maXJzdChwLCAiLi8vdGVpOnBlcnNOYW1lIiwgbnMgPSBucykKICAgIG5hbWVfdGV4dCA8LSBidWlsZF9uYW1lKHBlcnNfbmFtZSkgfD4gc3RyX3NxdWlzaCgpCiAgICAKICAgIGlmIChpcy5uYShuYW1lX3RleHQpIHx8IG5hbWVfdGV4dCA9PSAiIikgewogICAgICBuYW1lX3RleHQgPC0gcGlkCiAgICB9CiAgICAKICAgIHRpYmJsZShpZCA9IHBpZCwgbmFtZSA9IG5hbWVfdGV4dCkKICB9KSB8PgogIGRpc3RpbmN0KGlkLCAua2VlcF9hbGwgPSBUUlVFKSB8PgogIGZpbHRlcighaXMubmEobmFtZSksIG5hbWUgIT0gIiIpCgpjYXQoItCf0LXRgNGB0L7QvdCw0LbQtdC5INCy0YHQtdCz0L46IiwgbnJvdyhwZXJzb25fbGlzdCksICJcblxuIikKYGBgCgojINCU0LjQsNC70L7Qs9C4CgrQlNC+0YHRgtCw0L3QtdC8INC00LjQsNC70L7Qs9C4CgpgYGB7cn0Kc2FpZF9ub2RlcyA8LSB4bWxfZmluZF9hbGwoZG9jLCAiLy90ZWk6c2FpZFtAd2hvXSIsIG5zID0gbnMpCgpjYXQoItCd0LDQudC00LXQvdC+INGA0LXQv9C70LjQuiAoc2FpZCkg0YEg0LDRgtGA0LjQsdGD0YLQvtC8IHdobzoiLCBsZW5ndGgoc2FpZF9ub2RlcyksICJcbiIpCmBgYAoKYGBge3J9CiMg0LTQu9GPINC/0L7QuNGB0LrQsCDQsNCx0LfQsNGG0LAt0YDQvtC00LjRgtC10LvRjwpmaW5kX3BhcmVudF9wYXJhZ3JhcGggPC0gZnVuY3Rpb24obm9kZSkgewogIHAgPC0geG1sX3BhcmVudChub2RlKQogIHdoaWxlICghaXMubmEocCkgJiYgeG1sX25hbWUocCkgIT0gInAiKSB7CiAgICBwIDwtIHhtbF9wYXJlbnQocCkKICB9CiAgcmV0dXJuKHApCn0KCmV4dHJhY3RfcGVyc29uc19mcm9tX3BhcmFncmFwaCA8LSBmdW5jdGlvbihwYXJhZ3JhcGgpIHsKICBpZiAoaXMubmEocGFyYWdyYXBoKSB8fCB4bWxfbmFtZShwYXJhZ3JhcGgpICE9ICJwIikgcmV0dXJuKGNoYXJhY3RlcigwKSkKICAKICByc19ub2RlcyA8LSB4bWxfZmluZF9hbGwocGFyYWdyYXBoLCAiLi8vdGVpOnJzW0ByZWZdIiwgbnMgPSBucykKICByc19ub2RlcyB8PgogICAgbWFwX2Nocih+IHhtbF9hdHRyKC54LCAicmVmIikgfD4gc3RyX3JlbW92ZSgiXiMiKSkgfD4KICAgIHVuaXF1ZSgpCn0KCmRpYWxvZ3Vlc19kZiA8LSBtYXBfZGZyKHNhaWRfbm9kZXMsIGZ1bmN0aW9uKHNhaWQpIHsKICBzcGVha2VyIDwtIHhtbF9hdHRyKHNhaWQsICJ3aG8iKSB8PiBzdHJfcmVtb3ZlKCJeIyIpCiAgaWYgKGlzLm5hKHNwZWFrZXIpIHx8IHNwZWFrZXIgPT0gIiIpIHJldHVybihOVUxMKQogIAogICMg0LDQsdC30LDRhiDQtNC40LDQu9C+0LPQsAogIHBhcmVudF9wIDwtIGZpbmRfcGFyZW50X3BhcmFncmFwaChzYWlkKQogIGlmIChpcy5uYShwYXJlbnRfcCkgfHwgeG1sX25hbWUocGFyZW50X3ApICE9ICJwIikgcmV0dXJuKE5VTEwpCiAgCiAgIyDQv9C10YDRgdC+0L3QsNC20Lgg0LIg0LDQsdC30LDRhtC1CiAgYWxsX3BlcnNvbnMgPC0gZXh0cmFjdF9wZXJzb25zX2Zyb21fcGFyYWdyYXBoKHBhcmVudF9wKQogIAogICMg0LDQtNGA0LXRgdCw0YLQsNC80Lgg0YHRh9C40YLQsNC10Lwg0LLRgdC10YUsINC60YDQvtC80LUg0LPQvtCy0L7RgNGP0YnQtdCz0L4KICBhZGRyZXNzZWVzIDwtIHNldGRpZmYoYWxsX3BlcnNvbnMsIHNwZWFrZXIpCiAgCiAgaWYgKGxlbmd0aChhZGRyZXNzZWVzKSA9PSAwKSByZXR1cm4oTlVMTCkKICAKICAjINGA0LXQsdGA0LAg0L7RgiDQs9C+0LLQvtGA0Y/RidC10LPQviDQtNC+INCw0LTRgNC10YHQsNGC0L7QsgogIG1hcF9kZnIoYWRkcmVzc2VlcywgZnVuY3Rpb24oYWRkcmVzc2VlKSB7CiAgICB0aWJibGUoZnJvbSA9IHNwZWFrZXIsIHRvID0gYWRkcmVzc2VlLCB3ZWlnaHQgPSAxKQogIH0pCn0pCgpjYXQoItCd0LDQudC00LXQvdC+INC00LjQsNC70L7Qs9C+0LI6IiwgbnJvdyhkaWFsb2d1ZXNfZGYpLCAiXG4iKQpgYGAKCtCh0YPQvNC80LjRgNGD0LXQvCDQstC10YHQsCwg0YHQvtCx0LjRgNCw0LXQvCDRgNC10LHRgNCwCgpgYGB7cn0KZWRnZXNfZGYgPC0gZGlhbG9ndWVzX2RmIHw+CiAgZ3JvdXBfYnkoZnJvbSwgdG8pIHw+CiAgc3VtbWFyaXNlKHdlaWdodCA9IHN1bSh3ZWlnaHQpLCAuZ3JvdXBzID0gImRyb3AiKSB8PgogIG11dGF0ZSgKICAgIGZyb21fc29ydGVkID0gcG1pbihmcm9tLCB0byksCiAgICB0b19zb3J0ZWQgPSBwbWF4KGZyb20sIHRvKQogICkgfD4KICBncm91cF9ieShmcm9tX3NvcnRlZCwgdG9fc29ydGVkKSB8PgogIHN1bW1hcmlzZSh3ZWlnaHQgPSBzdW0od2VpZ2h0KSwgLmdyb3VwcyA9ICJkcm9wIikgfD4KICByZW5hbWUoZnJvbSA9IGZyb21fc29ydGVkLCB0byA9IHRvX3NvcnRlZCkKCmNhdCgi0KPQvdC40LrQsNC70YzQvdGL0YUg0LLQt9Cw0LjQvNC+0LTQtdC50YHRgtCy0LjQuSAo0L/QsNGAINC/0LXRgNGB0L7QvdCw0LbQtdC5KToiLCBucm93KGVkZ2VzX2RmKSwgIlxuIikKY2F0KCLQktGB0LXQs9C+INC00LjQsNC70L7Qs9C+0LI6Iiwgc3VtKGVkZ2VzX2RmJHdlaWdodCksICJcblxuIikKYGBgCgrQndC10LrQvtGC0L7RgNGL0LUg0L/QtdGA0YHQvtC90LDQttC4LCDRg9C/0L7QvNGP0L3Rg9GC0YvQtSDQsiDQtNC40LDQu9C+0LPQsNGFLCDQvNC+0LPRg9GCINC+0YLRgdGD0YLRgdGC0LLQvtCy0LDRgtGMINCyIGA8bGlzdFBlcnNvbj5gLiDQlNC+0LHQsNCy0LjQvCDQuNGFLgoKYGBge3J9CnZlcnRleF9pZHNfZnJvbV9lZGdlcyA8LSB1bmlxdWUoYyhlZGdlc19kZiRmcm9tLCBlZGdlc19kZiR0bykpCgojINC60LDQutC40LUgaWQg0LjQtyDRgNGR0LHQtdGAINC+0YLRgdGD0YLRgdGC0LLRg9GO0YIg0LIgcGVyc29uX2xpc3QKbWlzc2luZ19pZHMgPC0gc2V0ZGlmZih2ZXJ0ZXhfaWRzX2Zyb21fZWRnZXMsIHBlcnNvbl9saXN0JGlkKQoKaWYgKGxlbmd0aChtaXNzaW5nX2lkcykgPiAwKSB7CiAgY2F0KCLQndCw0LnQtNC10L3QviIsIGxlbmd0aChtaXNzaW5nX2lkcyksICJJRCDQv9C10YDRgdC+0L3QsNC20LXQuSDQsiDRgNGR0LHRgNCw0YUsINC60L7RgtC+0YDRi9GFINC90LXRgiDQsiBwZXJzb25fbGlzdFxuIikKCiAgbWlzc2luZ19wZXJzb25zIDwtIHRpYmJsZSgKICAgIGlkID0gbWlzc2luZ19pZHMsCiAgICBuYW1lID0gbWlzc2luZ19pZHMKICApCiAgCiAgcGVyc29uX2xpc3QgPC0gYmluZF9yb3dzKHBlcnNvbl9saXN0LCBtaXNzaW5nX3BlcnNvbnMpIHw+CiAgICBkaXN0aW5jdChpZCwgLmtlZXBfYWxsID0gVFJVRSkKfQoKIyDRgyDQstGB0LXRhSDQtNC+0LvQttC90L4g0LHRi9GC0Ywg0LjQvNGPCnBlcnNvbl9saXN0IDwtIHBlcnNvbl9saXN0IHw+CiAgbXV0YXRlKG5hbWUgPSBpZmVsc2UoaXMubmEobmFtZSkgfCBuYW1lID09ICIiLCBpZCwgbmFtZSkpCmBgYAoKIyMg0JPRgNCw0YQg0LTQuNCw0LvQvtCz0L7QsgoKYGBge3J9CmcgPC0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGVkZ2VzX2RmLCBkaXJlY3RlZCA9IEZBTFNFLCB2ZXJ0aWNlcyA9IHBlcnNvbl9saXN0KQoKVihnKSRkaXNwbGF5X25hbWUgPC0gVihnKSRuYW1lClYoZykkaWQgPC0gVihnKSRuYW1lCmBgYAoKKirQntC/0LjRgdCw0L3QuNC1INCz0YDQsNGE0LAqKgoK0J/RgNC+0LLQtdGA0LjQvCDRgdCy0Y/Qt9C90L7RgdGC0YwKCmBgYHtyfQppc19jb25uZWN0ZWQoZykKYGBgCgrQndC10YHQstGP0LfQvdGL0LkuCgrQndC10L3QsNC/0YDQsNCy0LvQtdC90L3Ri9C5LgoKYGBge3J9CmNhdCgi0JrQvtC70LjRh9C10YHRgtCy0L4g0LLQtdGA0YjQuNC9OiIsIHZjb3VudChnKSwgIlxuIikKY2F0KCLQmtC+0LvQuNGH0LXRgdGC0LLQviDRgNGR0LHQtdGAOiIsIGVjb3VudChnKSwgIlxuIikKY2F0KCLQn9C70L7RgtC90L7RgdGC0Ywg0LPRgNCw0YTQsDoiLCByb3VuZChlZGdlX2RlbnNpdHkoZyksIDQpLCAiXG4iKQpjYXQoItCa0L7Qu9C40YfQtdGB0YLQstC+INC60L7QvNC/0L7QvdC10L3RgiDRgdCy0Y/Qt9C90L7RgdGC0Lg6IiwgY291bnRfY29tcG9uZW50cyhnKSwgIlxuIikKYGBgCgrQk9GA0LDRhCDQvtGH0LXQvdGMINCx0L7Qu9GM0YjQvtC5INC4INGA0LDQt9GA0LXQttC10L3QvdGL0LksINC80L3QvtCz0L4g0L/QtdGA0YHQvtC90LDQttC10LksINC90L4g0LzQsNC70L4g0LrRgtC+INCy0LfQsNC40LzQvtC00LXQudGB0YLQstGD0LXRgiDRgdC+INCy0YHQtdC80Lgg0LjQu9C4INC/0L7Rh9GC0Lgg0LLRgdC10LzQuCDQvtGB0YLQsNC70YzQvdGL0LzQuCAo0YfRgtC+INC+0LbQuNC00LDQtdC80L4pCgoqKtCm0LXQvdGC0YDQsNC70YzQvdC+0YHRgtC4KioKCmBgYHtyfQpWKGcpJGRlZ3JlZSA8LSBkZWdyZWUoZywgbW9kZSA9ICJhbGwiKQpWKGcpJHdlaWdodGVkX2RlZ3JlZSA8LSBzdHJlbmd0aChnKQoKVihnKSRiZXR3ZWVubmVzcyA8LSBiZXR3ZWVubmVzcyhnLCBub3JtYWxpemVkID0gVFJVRSkKClYoZykkY2xvc2VuZXNzIDwtIGNsb3NlbmVzcyhnLCBub3JtYWxpemVkID0gVFJVRSkKVihnKSRlaWdlbl9jZW50cmFsaXR5IDwtIGVpZ2VuX2NlbnRyYWxpdHkoZykkdmVjdG9yClYoZykkY29yZW5lc3MgPC0gY29yZW5lc3MoZykKYGBgCgojIyDQn9C+0LTQs9GA0LDRhDog0YLQvtC/LTE1INC/0LXRgNGB0L7QvdCw0LbQtdC5INC/0L4g0LrQvtC7LdCy0YMg0LTQuNCw0LvQvtCz0L7QsgoK0J7QsdC+0YHQvdC+0LLQsNC90LjQtTog0YXQvtGC0LjQvCDQv9C+0YHQvNC+0YLRgNC10YLRjCDQvdCwINC90LDQuNCx0L7Qu9C10LUgItGA0LDQt9Cz0L7QstC+0YDRh9C40LLRi9GFIiDQv9C10YDRgdC+0L3QsNC20LXQuQoKYGBge3J9CnRvcF9uIDwtIDE1CgpkZWdyZWVfZGYgPC0gZGF0YS5mcmFtZSgKICBuYW1lID0gVihnKSRuYW1lLAogIHdlaWdodGVkX2RlZ3JlZSA9IFYoZykkd2VpZ2h0ZWRfZGVncmVlCikgfD4KICBhcnJhbmdlKGRlc2Mod2VpZ2h0ZWRfZGVncmVlKSkgfD4KICBoZWFkKHRvcF9uKQoKdG9wX25vZGVzIDwtIGRlZ3JlZV9kZiRuYW1lCmdfY29yZSA8LSBpbmR1Y2VkX3N1YmdyYXBoKGcsIHRvcF9ub2RlcykKYGBgCgrQpdCw0YDQsNC60YLQtdGA0LjRgdGC0LjQutC4INC/0L7QtNCz0YDQsNGE0LAKCmBgYHtyfQpjYXQoItCc0LXRgtC+0LQ6INGC0L7Qvy0iLCBsZW5ndGgodG9wX25vZGVzKSwgItC/0LXRgNGB0L7QvdCw0LbQtdC5INC/0L4g0LrQvtC70LjRh9C10YHRgtCy0YMg0LTQuNCw0LvQvtCz0L7QslxuIikKY2F0KCLQktC10YDRiNC40L0g0LIg0L/QvtC00LPRgNCw0YTQtToiLCB2Y291bnQoZ19jb3JlKSwgIlxuIikKY2F0KCLQoNGR0LHQtdGAINCyINC/0L7QtNCz0YDQsNGE0LU6IiwgZWNvdW50KGdfY29yZSksICJcbiIpCmNhdCgi0J/Qu9C+0YLQvdC+0YHRgtGMINC/0L7QtNCz0YDQsNGE0LA6Iiwgcm91bmQoZWRnZV9kZW5zaXR5KGdfY29yZSksIDQpLCAiXG4iKQpgYGAKCtCjINC/0L7QtNCz0YDQsNGE0LAg0L/Qu9C+0YLQvdC+0YHRgtGMINCz0L7RgNCw0LfQtNC+INCy0YvRiNC1LCDRh9C10Lwg0YMg0LPRgNCw0YTQsC4g0KLQvtC/LTE1INC/0LXRgNGB0L7QvdCw0LbQtdC5INC/0L4g0LrQvtC7LdCy0YMg0LTQuNCw0LvQvtCz0L7QsiDQsNC60YLQuNCy0L3QviDQstC30LDQuNC80L7QtNC10LnRgdGC0LLRg9GO0YIg0LTRgNGD0LMg0YEg0LTRgNGD0LPQvtC8ICjRj9Cy0LvRj9GO0YLRgdGPINGC0LDQutC40LwgItGP0LTRgNC+0LwiINC/0YDQvtC40LfQstC10LTQtdC90LjRjykKCioq0J/QtdGA0YHQvtC90LDQttC4KioKCmBgYHtyfQpwcmludChkZWdyZWVfZGYpCmBgYAoKIyDQodC+0L7QsdGJ0LXRgdGC0LLQsAoKYGBge3J9CnNldC5zZWVkKDQyKQpjb21tdW5pdGllcyA8LSBjbHVzdGVyX3dhbGt0cmFwKGcsIHdlaWdodHMgPSBFKGcpJHdlaWdodCkKClYoZykkY29tbXVuaXR5IDwtIG1lbWJlcnNoaXAoY29tbXVuaXRpZXMpCgptb2RfdmFsIDwtIG1vZHVsYXJpdHkoY29tbXVuaXRpZXMpCmBgYAoK0KDQtdC30YPQu9GM0YLQsNGCINCw0L3QsNC70LjQt9CwINGB0L7QvtCx0YnQtdGB0YLQsiDRgSDQv9C+0LzQvtGJ0YzRjiB3YWxrdHJhcAoKYGBge3J9CmNhdCgi0JzQvtC00YPQu9GP0YDQvdC+0YHRgtGMOiIsIHJvdW5kKG1vZF92YWwsIDQpLCAiXG4iKQpjYXQoItCa0L7Qu9C40YfQtdGB0YLQstC+INGB0L7QvtCx0YnQtdGB0YLQsjoiLCBtYXgoVihnKSRjb21tdW5pdHkpLCAiXG4iKQpgYGAKCtC80L7QtNGD0LvRj9GA0L3QvtGB0YLRjCBcPiAwLjMg0YPQutCw0LfRi9Cy0LDQtdGCINC90LAg0LfQvdCw0YfQuNC80YPRjiDQutC70LDRgdGC0LXRgNC90YPRjiDRgdGC0YDRg9C60YLRg9GA0YMgKNGF0L7RgNC+0YjQviDQstGL0LTQtdC70Y/RjtGC0YHRjyDQutC70LDRgdGC0LXRgNCwKS4g0KLQviDQtdGB0YLRjCwg0YXQvtGA0L7RiNC+INCy0YvQtNC10LvQuNC80YsgKNC30LDQvNC10YLQvdGLKSDQs9GA0YPQv9C/0Ysg0YfQsNGB0YLQviDQvtCx0YnQsNGO0YnQuNGF0YHRjyDQv9C10YDRgdC+0L3QsNC20LXQuS4KCmBgYHtyfQphcnRpY3VsYXRpb25fcHRzIDwtIGFydGljdWxhdGlvbl9wb2ludHMoZykKCmFydGljdWxhdGlvbl9uYW1lcyA8LSBWKGcpJG5hbWVbYXJ0aWN1bGF0aW9uX3B0c10KY2F0KCLQndCw0LnQtNC10L3QvjoiLCBsZW5ndGgoYXJ0aWN1bGF0aW9uX3B0cyksICLRgtC+0YfQtdC6INGB0L7Rh9C70LXQvdC10L3QuNGPXG4iKQpjYXQoItCf0YDQuNC80LXRgNGLOiIsIHBhc3RlKGhlYWQoYXJ0aWN1bGF0aW9uX25hbWVzLCAxMCksIGNvbGxhcHNlID0gIiwgIiksICJcbiIpCgpjbGlxdWVfbWF4X3NpemUgPC0gdHJ5Q2F0Y2goY2xpcXVlX251bShnKSwgZXJyb3IgPSBmdW5jdGlvbihlKSBOQSkKY2F0KCLQndCw0LjQsdC+0LvRjNGI0LDRjyDQutC70LjQutCwINGB0L7QtNC10YDQttC40YIiLCBjbGlxdWVfbWF4X3NpemUsICLQv9C10YDRgdC+0L3QsNC20LXQuVxuIikKYGBgCgojIyMjIyDQotC+0L8tMjAg0L/QtdGA0YHQvtC90LDQttC10Lkg0L/QviDRhtC10L3RgtGA0LDQu9GM0L3QvtGB0YLRj9C8CgpgYGB7cn0KdG9wX2NoYXJhY3RlcnMgPC0gZGF0YS5mcmFtZSgKICDQn9C10YDRgdC+0L3QsNC2ID0gVihnKSRuYW1lLAogINCU0LjQsNC70L7Qs9C+0LIgPSBWKGcpJHdlaWdodGVkX2RlZ3JlZSwKICDQn9Cw0YDRgtC90ZHRgNC+0LIgPSBWKGcpJGRlZ3JlZSwKICBCZXR3ZWVubmVzcyA9IHJvdW5kKFYoZykkYmV0d2Vlbm5lc3MsIDQpLAogIENsb3NlbmVzcyA9IHJvdW5kKFYoZykkY2xvc2VuZXNzLCA0KSwKICBFaWdlbnZlY3RvciA9IHJvdW5kKFYoZykkZWlnZW5fY2VudHJhbGl0eSwgNCksCiAgQ29yZW5lc3MgPSBWKGcpJGNvcmVuZXNzLAogINCh0L7QvtCx0YnQtdGB0YLQstC+ID0gVihnKSRjb21tdW5pdHkKKSB8PgogIGFycmFuZ2UoZGVzYyjQlNC40LDQu9C+0LPQvtCyKSkgfD4KICBoZWFkKDIwKQoKcHJpbnQodG9wX2NoYXJhY3RlcnMpCgpgYGAKCiMg0JLQuNC30YPQsNC70LjQt9Cw0YbQuNGPINC+0YHQvdC+0LLQvdC+0LPQviDQs9GA0LDRhNCwICjRgtC+0L8tMTUpCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTB9CnRvcF9uX21haW4gPC0gMTUKdG9wX2luZGljZXMgPC0gb3JkZXIoVihnKSR3ZWlnaHRlZF9kZWdyZWUsIGRlY3JlYXNpbmcgPSBUUlVFKVsxOm1pbih0b3Bfbl9tYWluLCB2Y291bnQoZykpXQp0b3Bfbm9kZXNfbWFpbiA8LSBWKGcpJG5hbWVbdG9wX2luZGljZXNdCnRvcF9ub2Rlc19tYWluIDwtIHRvcF9ub2Rlc19tYWluWyFpcy5uYSh0b3Bfbm9kZXNfbWFpbildCgpnX3RvcCA8LSBpbmR1Y2VkX3N1YmdyYXBoKGcsIHRvcF9ub2Rlc19tYWluKQoKbWluX2VkZ2Vfd2VpZ2h0IDwtIDMwCmdfdG9wX2ZpbHRlcmVkIDwtIHN1YmdyYXBoLmVkZ2VzKGdfdG9wLCBlaWRzID0gd2hpY2goRShnX3RvcCkkd2VpZ2h0ID49IG1pbl9lZGdlX3dlaWdodCksIGRlbGV0ZS52ZXJ0aWNlcyA9IEZBTFNFKQoKc2V0LnNlZWQoNDIpCnBfbWFpbiA8LSBnZ3JhcGgoZ190b3BfZmlsdGVyZWQsIGxheW91dCA9ICJzdHJlc3MiKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGFscGhhID0gd2VpZ2h0LCB3aWR0aCA9IHdlaWdodCksIAogICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXk1MCIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSB3ZWlnaHRlZF9kZWdyZWUsIAogICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFzLmZhY3Rvcihjb21tdW5pdHkpKSwKICAgICAgICAgICAgICAgICAgc2hhcGUgPSAyMSwgY29sb3IgPSAiZ3JleTMwIiwgYWxwaGEgPSAwLjkpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwKICAgICAgICAgICAgICAgICByZXBlbCA9IFRSVUUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDQsIDE2KSwgbmFtZSA9ICLQmtC+0LvQuNGH0LXRgdGC0LLQviDQtNC40LDQu9C+0LPQvtCyIikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKCLQodC+0L7QsdGJ0LXRgdGC0LLQviIsIHBhbGV0dGUgPSAiU2V0MyIpICsKICBsYWJzKHRpdGxlID0gItCh0LXRgtGMINC00LjQsNC70L7Qs9C+0LIg0L/QtdGA0YHQvtC90LDQttC10Lkg0YDQvtC80LDQvdCwICfQktC+0LnQvdCwINC4INC80LjRgCciLAogICAgICAgc3VidGl0bGUgPSBwYXN0ZTAoItCi0L7Qvy0xNSDQv9C+INC60L7Qu9C40YfQtdGB0YLQstGDINC00LjQsNC70L7Qs9C+0LIgfCDQktC10YEg0YDRkdCx0LXRgCDiiaUgIiwgbWluX2VkZ2Vfd2VpZ2h0LCAiIHwgIiwKICAgICAgICAgICAgICAgICAgICAgICAgICLQnNC+0LTRg9C70Y/RgNC90L7RgdGC0YwgPSAiLCByb3VuZChtb2RfdmFsLCAzKSwgIiB8ICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAi0JLQtdGA0YjQuNC9OiAiLCB2Y291bnQoZ190b3BfZmlsdGVyZWQpLCAiIHwg0KDRkdCx0LXRgDogIiwgZWNvdW50KGdfdG9wX2ZpbHRlcmVkKSkpICsKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKCnByaW50KHBfbWFpbikKCmBgYAoKIyDQktC40LfRg9Cw0LvQuNC30LDRhtC40Y8g0L/QvtC00LPRgNCw0YTQsCAo0YLQvtC/LTE1KQoKYGBge3IgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEyfQpzZXQuc2VlZCg0MikKcF9jb3JlIDwtIGdncmFwaChnX2NvcmUsIGxheW91dCA9ICJzdHJlc3MiKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGFscGhhID0gd2VpZ2h0LCB3aWR0aCA9IHdlaWdodCksIAogICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXk1MCIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSB3ZWlnaHRlZF9kZWdyZWUsIAogICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFzLmZhY3Rvcihjb3JlbmVzcykpLAogICAgICAgICAgICAgICAgICBzaGFwZSA9IDIxLCBjb2xvciA9ICJncmV5MzAiLCBhbHBoYSA9IDAuOSkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLAogICAgICAgICAgICAgICAgIHJlcGVsID0gVFJVRSwgc2l6ZSA9IDQsIGZvbnRmYWNlID0gImJvbGQiKSArCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYyg1LCAxOCksIG5hbWUgPSAi0JrQvtC70LjRh9C10YHRgtCy0L4g0LTQuNCw0LvQvtCz0L7QsiIpICsKICBzY2FsZV9maWxsX2JyZXdlcigiSy1jb3JlIiwgcGFsZXR0ZSA9ICJSZFlsQnUiKSArCiAgbGFicyh0aXRsZSA9ICLQotC+0L8tMTUg0L/QtdGA0YHQvtC90LDQttC10Lkg0L/QviDQutC+0LvQuNGH0LXRgdGC0LLRgyDQtNC40LDQu9C+0LPQvtCyIiwKICAgICAgIHN1YnRpdGxlID0gcGFzdGUwKCLQktC10YDRiNC40L06ICIsIHZjb3VudChnX2NvcmUpLCAiIHwg0KDRkdCx0LXRgDogIiwgZWNvdW50KGdfY29yZSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIiB8INCf0LvQvtGC0L3QvtGB0YLRjDogIiwgcm91bmQoZWRnZV9kZW5zaXR5KGdfY29yZSksIDMpKSkgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKcHJpbnQocF9jb3JlKQpgYGAK