Student ID: 1A182901-2
- Set up
rm(list=ls(all=TRUE))
setwd("~/Desktop/R/polimetrics")
library(quanteda)
library(readtext)
library(gridExtra)
library(ggplot2)
library(reshape2)
library(manifestoR)
library(e1071)
library(caTools)
library(randomForest)
library(caret)
library(stringr)
library(cowplot)
library(knitr)
library(kableExtra)
- Dictionaries
mp_setapikey(key.file = NULL, key = "e51dc314a21ce75bd8221fcb338e2ee5")
cmp <- mp_maindataset()
uk <- cmp[ which(cmp$countryname=="United Kingdom" & cmp$date == 201505 ),]
available_uk <- mp_availability( countryname=="United Kingdom" & date == 201505 )
uk2015 <- mp_corpus(available_uk )
qt_uk2015 <- corpus(uk2015)
1.1. Sentiment
dfm_uk1 <- dfm(qt_uk2015, tolower=TRUE,remove_punct = TRUE,remove_numbers=TRUE, remove = stopwords("english"))
dfm_uk2 <- dfm_group(dfm_uk1, 'party')
tokens <- ntoken(dfm_uk2)
dfm_uk <- dfm(qt_uk2015,
tolower=TRUE,
remove_punct = TRUE,
remove_numbers=TRUE,
dictionary = data_dictionary_LSD2015[1:2],
group=c("party"))
dfm_uk@Dimnames$docs <- uk$partyname
df_uk <- convert(dfm_uk,to="data.frame")
colnames(df_uk) <- c("party","n","p")
df_uk$tokens <- tokens
df_uk$rel <- df_uk$p-df_uk$n
df_uk$relp <- df_uk$rel/df_uk$tokens
p1 <- ggplot(df_uk,aes(x=reorder(party,rel),y=rel,fill=party))+
geom_bar(position="dodge",stat="identity",width = 0.5)+
xlab("")+
ylab("Absolute terms")+
theme(legend.position="none")+
scale_fill_brewer(palette="Set3")+
coord_flip()
p2 <- ggplot(df_uk,aes(x=reorder(party,relp),y=relp,fill=party))+
geom_bar(position="dodge",stat="identity",width = 0.5)+
xlab("")+
ylab("Relative terms")+
theme(legend.position="none")+
scale_fill_brewer(palette="Set3")+
coord_flip()
plot_grid(p1,p2,nrow = 2)

- This graph is the combination of the absolute and relative terms of the sentiment in the UK 2015 manifestoes.
- As the graph above, there’re several observations,
- All the manifestoes had more positive terms than negative terms.
- The ruling party - Conservative Party, used more positive words than its opposition, which was Labor Party.
- According two results, the Liberal Democrats’ manifesto were the most positive one.
- The relative positions did change if we considered the length of manifestoes.
1.2. Liberal versus Conservative
##1-2
dictfile <- tempfile()
download.file("https://provalisresearch.com/Download/LaverGarry.zip", dictfile, mode = "wb")
unzip(dictfile, exdir = (td <- tempdir()))
lgdict <- dictionary(file = paste(td, "LaverGarry.cat", sep = "/"))
lg_dfm <- dfm(qt_uk2015,
tolower=TRUE,
remove_punct = TRUE,
remove_numbers=TRUE,
group=c("party"),
dictionary = lgdict)
Dictionary <-convert(lg_dfm, to="data.frame")
names(Dictionary )[20] <- "Conservative"
names(Dictionary )[21] <- "Liberal"
Dictionary_res <- data.frame(Party=uk$partyname,
Conservative=Dictionary$Conservative,
Liberal=Dictionary$Liberal)
Dictionary_res$tokens <- tokens
Dictionary_res$absolute <- Dictionary_res$Liberal-Dictionary_res$Conservative
Dictionary_res$relative <- Dictionary_res$absolute/Dictionary_res$tokens
p5 <- ggplot(Dictionary_res,aes(x=Liberal,y=Conservative))+
geom_point()+
geom_text(aes(label = Party),hjust=.5, vjust=-1,size = 2)
p5

- This graph showed the liberal and conservative terms used by the manifestoes.
- Suprisingly, it did not have negative relationship. Contrarily, there were even some positive relationships.
- One possible explanation was that the speaker needed to compare in the manifesto, no matter which value he/she held.
- The major parties in UK, Conservative Party and Labor Party used many liberal-conservative terms.
- Unsurprisingly, Liberal Democrats and Green Party used more liberal terms in their manifestoes.
p3 <- ggplot(Dictionary_res,aes(x=reorder(Party,absolute),y=absolute,fill=Party))+
geom_bar(position="dodge",stat="identity",width = 0.5)+
geom_hline(yintercept=0)+
xlab("")+
ylab("Absolute terms")+
theme(legend.position="none")+
scale_fill_brewer(palette="Set3")+
coord_flip()
p4 <- ggplot(Dictionary_res,aes(x=reorder(Party,relative),y=relative,fill=Party))+
geom_bar(position="dodge",stat="identity",width = 0.5)+
geom_hline(yintercept=0)+
xlab("")+
ylab("Relative terms")+
theme(legend.position="none")+
scale_fill_brewer(palette="Set3")+
coord_flip()
plot_grid(p3,p4,nrow = 2)

- This graph is the absolute and relative terms of liberal-conservative scale in UK 2015 election.
- The positive value meant the party used more liberal words, and vice versa.
- There were several conclusions,
- Several positions changed if we considered the length.
- The two major parties in UK both used more conservative terms than liberal terms, especially the Conservative Party.
- UK Independence Party used lots of conservative terms. This result revealed the characteristics of Brexit.
- Classifiers
#2
trump1 <- read.csv("trump-orig3.csv", stringsAsFactors=FALSE)
trump2 <- read.csv("trump_tweets2.csv", stringsAsFactors=FALSE)
trump1_1 <- read.csv("trump-orig3.csv", stringsAsFactors=FALSE)
trump2_1 <- read.csv("trump_tweets2.csv", stringsAsFactors=FALSE)
2.1. 2-fold validation of RandomForest
trump1$text <- str_replace_all(trump1_1$text, "[^[:alnum:]]", " ")
trump1$setv <- as.integer(as.factor(trump1_1$Sentiment))
trump1$Sentiment[trump1$setv==1] <- "negative"
trump1$Sentiment[trump1$setv==2] <- "neutral"
trump1$Sentiment[trump1$setv==3] <- "positive"
trump1$Sentiment <- as.factor(trump1$Sentiment)
cv <- corpus(trump1)
dfm_trump_cv <- dfm(cv,
remove = c(stopwords("english"), ("amp"), ("rt") ,("tco"), ("co"), ("u"), ("t"), ("s"), ("ed"), ("https")),
remove_punct = TRUE,
remove_numbers=TRUE,
tolower = TRUE,
remove_symbols=TRUE,
remove_twitter = TRUE,
remove_separators=TRUE,
remove_url = TRUE)
dfm_trump_n_cv <- dfm_trim(dfm_trump_cv , min_docfreq= 0.05)
data_cv <- as.data.frame(as.matrix(dfm_trump_n_cv))
colnames(data_cv ) <- make.names(colnames(data_cv ))
data_cv$sentiment<- trump1$Sentiment
set.seed(123)
to_train <- sample(1:472, 236, replace = FALSE)
train_cv <- data_cv[to_train, ]
test_cv <- data_cv[-to_train, ]
set.seed(123)
system.time(RF_cv <- randomForest(sentiment~ ., data=train_cv, type="classification"))
user system elapsed
49.104 0.830 51.288
predictRF_cv <- predict(RF_cv, newdata=test_cv)
x2_cv <- as.matrix(table("Predictions"= predictRF_cv, "Actual"=test_cv$sentiment))
acc_cv <- sum(diag(x2_cv)) / sum(x2_cv)
conf.mat_cv <- confusionMatrix( predictRF_cv, test_cv$sentiment)
set.seed(123)
system.time(RF2_cv <- randomForest(sentiment~ ., data=test_cv, type="classification"))
user system elapsed
48.756 0.887 51.046
predictRF2_cv <- predict(RF2_cv, newdata=train_cv)
x2_2_cv <- as.matrix(table("Predictions"= predictRF2_cv, "Actual"=train_cv$sentiment))
acc_alt_cv <- sum(diag(x2_2_cv)) / sum(x2_2_cv)
conf.mat2_cv <- confusionMatrix( predictRF2_cv, train_cv$sentiment)
res1_cv <- as.data.frame(conf.mat_cv$byClass)
res2_cv <- as.data.frame(conf.mat2_cv$byClass)
2.2. Prediction: RandomForest
trump <- select(trump1_1,text)
trump_t <- select(trump2_1,text)
trump <- as.data.frame(rbind(trump,trump_t))
trump$text <- str_replace_all(trump$text, "[^[:alnum:]]", " ")
tt <- corpus(trump)
dfm_trump_n <- dfm(tt,
remove = c(stopwords("english"), ("amp"), ("rt") ,("tco"), ("co"), ("u"), ("t"), ("s"), ("ed"), ("https")),
remove_punct = TRUE,
remove_numbers=TRUE,
tolower = TRUE,
remove_symbols=TRUE,
remove_twitter = TRUE,
remove_separators=TRUE,
remove_url = TRUE)
dfm_trump_n_t <- dfm_trim(dfm_trump_n , min_docfreq= 0.05)
data_t <- as.data.frame(as.matrix(dfm_trump_n_t))
colnames(data_t ) <- make.names(colnames(data_t ))
train <- data_t[1:472, ]
trump1$setv <- as.integer(as.factor(trump1_1$Sentiment))
trump1$Sentiment[trump1$setv==1] <- "negative"
trump1$Sentiment[trump1$setv==2] <- "neutral"
trump1$Sentiment[trump1$setv==3] <- "positive"
trump1$Sentiment <- as.factor(trump1$Sentiment)
train$sentiment <- trump1$Sentiment
test <- data_t[473:1472, ]
set.seed(123)
system.time(RF <- randomForest(sentiment~ ., data=train, type="classification"))
user system elapsed
385.031 5.918 425.355
predictRF <- predict(RF, newdata=test)
res_rf <- as.data.frame(table(predictRF))
res_rf$Prob <- res_rf$Freq/sum(res_rf$Freq)
2.3. Prediction: Naive Bayes
ttc2 <- corpus(trump2)
dfm_trump2_n <- dfm(ttc2,
remove = c(stopwords("english"), ("amp"), ("rt") ,("tco"), ("co"), ("u"), ("t"), ("s"), ("ed"), ("https")),
remove_punct = TRUE,
remove_numbers=TRUE,
tolower = TRUE,
remove_symbols=TRUE,
remove_twitter = TRUE,
remove_separators=TRUE,
remove_url = TRUE)
dfm_trump2_n_t <- dfm_trim(dfm_trump2_n , min_docfreq= 0.05)
trump1$setv <- as.integer(as.factor(trump1_1$Sentiment))
trump1$Sentiment[trump1$setv==1] <- "negative"
trump1$Sentiment[trump1$setv==2] <- "neutral"
trump1$Sentiment[trump1$setv==3] <- "positive"
trump1$Sentiment <- as.factor(trump1$Sentiment)
trump1$text <- str_replace_all(trump1$text, "[^[:alnum:]]", " ")
tto <- corpus(trump1)
dfm_trump1 <- dfm(tto,
remove = c(stopwords("english"), ("amp"), ("rt") ,("tco"), ("co"), ("u"), ("t"), ("s"), ("ed"), ("https")),
remove_punct = TRUE,
remove_numbers=TRUE,
tolower = TRUE,
remove_symbols=TRUE,
remove_twitter = TRUE,
remove_separators=TRUE,
remove_url = TRUE)
dfm_trump1_t <- dfm_trim(dfm_trump1, min_docfreq= 0.05)
data_t1 <- as.data.frame(as.matrix(dfm_trump1_t))
colnames(data_t1) <- make.names(colnames(data_t1 ))
data_t1$sentiment<- trump1$Sentiment
nb <- textmodel_nb(dfm_trump1_t ,docvars(dfm_trump1_t, "Sentiment"), distribution = c("multinomial"))
dfm_trump2_n_t <- dfm_select(dfm_trump2_n_t, dfm_trump1_t)
predicted_nb <- predict(nb, dfm_trump2_n_t)
res_nb <- as.data.frame(table(predicted_nb))
res_nb$Prob <- res_nb$Freq/sum(res_nb$Freq)
2.4. Results 2.4.1. Accuracy of validation
A. Table 1
table(test_cv$sentiment, predictRF_cv) %>%
kable() %>%
kable_styling()
| negative |
81 |
11 |
0 |
| neutral |
61 |
27 |
1 |
| positive |
38 |
16 |
1 |
- This is the first result of cross-validation.
B. Table 2
table(train_cv$sentiment, predictRF2_cv)%>%
kable() %>%
kable_styling()
| negative |
70 |
28 |
1 |
| neutral |
42 |
47 |
0 |
| positive |
25 |
20 |
3 |
- This is the second result of cross-validation.
C. Accuracy
ac_cv <- (acc_cv+acc_alt_cv)/2
ac_cv
[1] 0.4851695
D. Precision and Recall
res_all_cv <- data.frame(
precision1=res1_cv$Precision,
recall1=res1_cv$Recall,
precision2=res2_cv$Precision,
recall2=res2_cv$Recall
)
rownames(res_all_cv) <- rownames(res1_cv)
res_all_cv %>%
kable() %>%
kable_styling()
| Class: negative |
0.45 |
0.8804348 |
0.5109489 |
0.7070707 |
| Class: neutral |
0.50 |
0.3033708 |
0.4947368 |
0.5280899 |
| Class: positive |
0.50 |
0.0181818 |
0.7500000 |
0.0625000 |
- As the accuracy, precision, recall results of 2-fold cross validation, there’re some conclusions.
- This model can not succesfully predicted the positive tweet.
- The recall of negative was the highest, the model performanced better on identified the negative tweet.
2.4.2. Prediction of RandomForest
res_rf%>%
kable() %>%
kable_styling()
| negative |
944 |
0.944 |
| neutral |
56 |
0.056 |
| positive |
0 |
0.000 |
- As the cross-validation in 2.4.1, the Random Forest model could not identified the positive tweet again in prediction.
2.4.3. Prediction of Naive Bayes
res_nb%>%
kable() %>%
kable_styling()
| negative |
459 |
0.459 |
| neutral |
304 |
0.304 |
| positive |
237 |
0.237 |
- The Naive Bayes model showed largely different result, comparing to the RF model.
- Since I did not test the cross validation of this model. The accuracy and model choice needs further discussion.
LS0tCnRpdGxlOiAiSG9tZSBBc3NpZ25tZW50IDUiCm91dHB1dDogaHRtbF9ub3RlYm9vawphdXRob3I6IFllbiBDaGVuZyBIc3VhbgotLS0KIyMjI1N0dWRlbnQgSUQ6IDFBMTgyOTAxLTIKKioqCj4wLiBTZXQgdXAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpybShsaXN0PWxzKGFsbD1UUlVFKSkKc2V0d2QoIn4vRGVza3RvcC9SL3BvbGltZXRyaWNzIikKbGlicmFyeShxdWFudGVkYSkKbGlicmFyeShyZWFkdGV4dCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeShtYW5pZmVzdG9SKQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KGNhVG9vbHMpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQpgYGAKPjEuIERpY3Rpb25hcmllcwoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cm1wX3NldGFwaWtleShrZXkuZmlsZSA9IE5VTEwsIGtleSA9ICJlNTFkYzMxNGEyMWNlNzViZDgyMjFmY2IzMzhlMmVlNSIpCgpjbXAgPC0gbXBfbWFpbmRhdGFzZXQoKQp1ayA8LSBjbXBbIHdoaWNoKGNtcCRjb3VudHJ5bmFtZT09IlVuaXRlZCBLaW5nZG9tIiAmIGNtcCRkYXRlID09IDIwMTUwNSApLF0KCmF2YWlsYWJsZV91ayA8LSBtcF9hdmFpbGFiaWxpdHkoIGNvdW50cnluYW1lPT0iVW5pdGVkIEtpbmdkb20iICYgZGF0ZSA9PSAyMDE1MDUgKQp1azIwMTUgPC0gbXBfY29ycHVzKGF2YWlsYWJsZV91ayApCnF0X3VrMjAxNSA8LSBjb3JwdXModWsyMDE1KQpgYGAKPjEuMS4gU2VudGltZW50CgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGZtX3VrMSA8LSBkZm0ocXRfdWsyMDE1LCAgdG9sb3dlcj1UUlVFLHJlbW92ZV9wdW5jdCA9IFRSVUUscmVtb3ZlX251bWJlcnM9VFJVRSwgcmVtb3ZlID0gc3RvcHdvcmRzKCJlbmdsaXNoIikpCmRmbV91azIgPC0gZGZtX2dyb3VwKGRmbV91azEsICdwYXJ0eScpIAp0b2tlbnMgPC0gbnRva2VuKGRmbV91azIpCgpkZm1fdWsgPC0gZGZtKHF0X3VrMjAxNSwgCiAgICAgICAgICAgICAgdG9sb3dlcj1UUlVFLCAKICAgICAgICAgICAgICByZW1vdmVfcHVuY3QgPSBUUlVFLAogICAgICAgICAgICAgIHJlbW92ZV9udW1iZXJzPVRSVUUsCiAgICAgICAgICAgICAgZGljdGlvbmFyeSA9IGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1WzE6Ml0sCiAgICAgICAgICAgICAgZ3JvdXA9YygicGFydHkiKSkKZGZtX3VrQERpbW5hbWVzJGRvY3MgPC0gdWskcGFydHluYW1lIApkZl91ayA8LSBjb252ZXJ0KGRmbV91ayx0bz0iZGF0YS5mcmFtZSIpCmNvbG5hbWVzKGRmX3VrKSA8LSBjKCJwYXJ0eSIsIm4iLCJwIikKZGZfdWskdG9rZW5zIDwtIHRva2VucwpkZl91ayRyZWwgPC0gZGZfdWskcC1kZl91ayRuCmRmX3VrJHJlbHAgPC0gZGZfdWskcmVsL2RmX3VrJHRva2VucwpwMSA8LSBnZ3Bsb3QoZGZfdWssYWVzKHg9cmVvcmRlcihwYXJ0eSxyZWwpLHk9cmVsLGZpbGw9cGFydHkpKSsKICBnZW9tX2Jhcihwb3NpdGlvbj0iZG9kZ2UiLHN0YXQ9ImlkZW50aXR5Iix3aWR0aCA9IDAuNSkrCiAgeGxhYigiIikrCiAgeWxhYigiQWJzb2x1dGUgdGVybXMiKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQzIikrCiAgY29vcmRfZmxpcCgpCnAyIDwtIGdncGxvdChkZl91ayxhZXMoeD1yZW9yZGVyKHBhcnR5LHJlbHApLHk9cmVscCxmaWxsPXBhcnR5KSkrCiAgZ2VvbV9iYXIocG9zaXRpb249ImRvZGdlIixzdGF0PSJpZGVudGl0eSIsd2lkdGggPSAwLjUpKwogIHhsYWIoIiIpKwogIHlsYWIoIlJlbGF0aXZlIHRlcm1zIikrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MyIpKwogIGNvb3JkX2ZsaXAoKQpwbG90X2dyaWQocDEscDIsbnJvdyA9IDIpCgpgYGAKKiBUaGlzIGdyYXBoIGlzIHRoZSBjb21iaW5hdGlvbiBvZiB0aGUgYWJzb2x1dGUgYW5kIHJlbGF0aXZlIHRlcm1zIG9mIHRoZSBzZW50aW1lbnQgaW4gdGhlIFVLIDIwMTUgbWFuaWZlc3RvZXMuCiogQXMgdGhlIGdyYXBoIGFib3ZlLCB0aGVyZSdyZSBzZXZlcmFsIG9ic2VydmF0aW9ucywKICAgIDEuIEFsbCB0aGUgbWFuaWZlc3RvZXMgaGFkIG1vcmUgcG9zaXRpdmUgdGVybXMgdGhhbiBuZWdhdGl2ZSB0ZXJtcy4KICAgIDIuIFRoZSBydWxpbmcgcGFydHkgLSBDb25zZXJ2YXRpdmUgUGFydHksIHVzZWQgbW9yZSBwb3NpdGl2ZSB3b3JkcyB0aGFuIGl0cyBvcHBvc2l0aW9uLCB3aGljaCB3YXMgTGFib3IgUGFydHkuCiAgICAzLiBBY2NvcmRpbmcgdHdvIHJlc3VsdHMsIHRoZSBMaWJlcmFsIERlbW9jcmF0cycgbWFuaWZlc3RvIHdlcmUgdGhlIG1vc3QgcG9zaXRpdmUgb25lLgogICAgNC4gVGhlIHJlbGF0aXZlIHBvc2l0aW9ucyBkaWQgY2hhbmdlIGlmIHdlIGNvbnNpZGVyZWQgdGhlIGxlbmd0aCBvZiBtYW5pZmVzdG9lcy4KICAgIAoKPjEuMi4gTGliZXJhbCB2ZXJzdXMgQ29uc2VydmF0aXZlCgpgYGB7ciByZXN1bHRzPSJoaWRlIn0KZGljdGZpbGUgPC0gdGVtcGZpbGUoKQpkb3dubG9hZC5maWxlKCJodHRwczovL3Byb3ZhbGlzcmVzZWFyY2guY29tL0Rvd25sb2FkL0xhdmVyR2FycnkuemlwIiwgZGljdGZpbGUsIG1vZGUgPSAid2IiKQp1bnppcChkaWN0ZmlsZSwgZXhkaXIgPSAodGQgPC0gdGVtcGRpcigpKSkKbGdkaWN0IDwtIGRpY3Rpb25hcnkoZmlsZSA9IHBhc3RlKHRkLCAiTGF2ZXJHYXJyeS5jYXQiLCBzZXAgPSAiLyIpKQoKYGBgCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKbGdfZGZtIDwtIGRmbShxdF91azIwMTUsICAKICAgICAgICAgICAgICB0b2xvd2VyPVRSVUUsIAogICAgICAgICAgICAgIHJlbW92ZV9wdW5jdCA9IFRSVUUsCiAgICAgICAgICAgICAgcmVtb3ZlX251bWJlcnM9VFJVRSwKICAgICAgICAgICAgICBncm91cD1jKCJwYXJ0eSIpLCAKICAgICAgICAgICAgICBkaWN0aW9uYXJ5ID0gbGdkaWN0KQpEaWN0aW9uYXJ5IDwtY29udmVydChsZ19kZm0sIHRvPSJkYXRhLmZyYW1lIikKbmFtZXMoRGljdGlvbmFyeSApWzIwXSA8LSAiQ29uc2VydmF0aXZlIgpuYW1lcyhEaWN0aW9uYXJ5IClbMjFdIDwtICJMaWJlcmFsIgpEaWN0aW9uYXJ5X3JlcyA8LSBkYXRhLmZyYW1lKFBhcnR5PXVrJHBhcnR5bmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb25zZXJ2YXRpdmU9RGljdGlvbmFyeSRDb25zZXJ2YXRpdmUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTGliZXJhbD1EaWN0aW9uYXJ5JExpYmVyYWwpCkRpY3Rpb25hcnlfcmVzJHRva2VucyA8LSB0b2tlbnMKRGljdGlvbmFyeV9yZXMkYWJzb2x1dGUgPC0gRGljdGlvbmFyeV9yZXMkTGliZXJhbC1EaWN0aW9uYXJ5X3JlcyRDb25zZXJ2YXRpdmUKRGljdGlvbmFyeV9yZXMkcmVsYXRpdmUgPC0gRGljdGlvbmFyeV9yZXMkYWJzb2x1dGUvRGljdGlvbmFyeV9yZXMkdG9rZW5zCgpwNSA8LSBnZ3Bsb3QoRGljdGlvbmFyeV9yZXMsYWVzKHg9TGliZXJhbCx5PUNvbnNlcnZhdGl2ZSkpKwogIGdlb21fcG9pbnQoKSsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gUGFydHkpLGhqdXN0PS41LCB2anVzdD0tMSxzaXplID0gMikKcDUKYGBgCgoqIFRoaXMgZ3JhcGggc2hvd2VkIHRoZSBsaWJlcmFsIGFuZCBjb25zZXJ2YXRpdmUgdGVybXMgdXNlZCBieSB0aGUgbWFuaWZlc3RvZXMuCjEuIFN1cHJpc2luZ2x5LCBpdCBkaWQgbm90IGhhdmUgbmVnYXRpdmUgcmVsYXRpb25zaGlwLiBDb250cmFyaWx5LCB0aGVyZSB3ZXJlIGV2ZW4gc29tZSBwb3NpdGl2ZSByZWxhdGlvbnNoaXBzLgogICAgKyBPbmUgcG9zc2libGUgZXhwbGFuYXRpb24gd2FzIHRoYXQgdGhlIHNwZWFrZXIgbmVlZGVkIHRvIGNvbXBhcmUgaW4gdGhlIG1hbmlmZXN0bywgbm8gbWF0dGVyIHdoaWNoIHZhbHVlIGhlL3NoZSBoZWxkLgoyLiBUaGUgbWFqb3IgcGFydGllcyBpbiBVSywgQ29uc2VydmF0aXZlIFBhcnR5IGFuZCBMYWJvciBQYXJ0eSB1c2VkIG1hbnkgbGliZXJhbC1jb25zZXJ2YXRpdmUgdGVybXMuCjMuIFVuc3VycHJpc2luZ2x5LCBMaWJlcmFsIERlbW9jcmF0cyBhbmQgR3JlZW4gUGFydHkgdXNlZCBtb3JlIGxpYmVyYWwgdGVybXMgaW4gdGhlaXIgbWFuaWZlc3RvZXMuCgpgYGB7cn0KcDMgPC0gZ2dwbG90KERpY3Rpb25hcnlfcmVzLGFlcyh4PXJlb3JkZXIoUGFydHksYWJzb2x1dGUpLHk9YWJzb2x1dGUsZmlsbD1QYXJ0eSkpKwogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsc3RhdD0iaWRlbnRpdHkiLHdpZHRoID0gMC41KSsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCkrCiAgeGxhYigiIikrCiAgeWxhYigiQWJzb2x1dGUgdGVybXMiKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQzIikrCiAgY29vcmRfZmxpcCgpCnA0IDwtIGdncGxvdChEaWN0aW9uYXJ5X3JlcyxhZXMoeD1yZW9yZGVyKFBhcnR5LHJlbGF0aXZlKSx5PXJlbGF0aXZlLGZpbGw9UGFydHkpKSsKICBnZW9tX2Jhcihwb3NpdGlvbj0iZG9kZ2UiLHN0YXQ9ImlkZW50aXR5Iix3aWR0aCA9IDAuNSkrCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTApKwogIHhsYWIoIiIpKwogIHlsYWIoIlJlbGF0aXZlIHRlcm1zIikrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MyIpKwogIGNvb3JkX2ZsaXAoKQoKcGxvdF9ncmlkKHAzLHA0LG5yb3cgPSAyKQpgYGAKKiBUaGlzIGdyYXBoIGlzIHRoZSBhYnNvbHV0ZSBhbmQgcmVsYXRpdmUgdGVybXMgb2YgbGliZXJhbC1jb25zZXJ2YXRpdmUgc2NhbGUgaW4gVUsgMjAxNSBlbGVjdGlvbi4KKiBUaGUgcG9zaXRpdmUgdmFsdWUgbWVhbnQgdGhlIHBhcnR5IHVzZWQgbW9yZSBsaWJlcmFsIHdvcmRzLCBhbmQgdmljZSB2ZXJzYS4KKiBUaGVyZSB3ZXJlIHNldmVyYWwgY29uY2x1c2lvbnMsCiAgICAxLiBTZXZlcmFsIHBvc2l0aW9ucyBjaGFuZ2VkIGlmIHdlIGNvbnNpZGVyZWQgdGhlIGxlbmd0aC4KICAgIDIuIFRoZSB0d28gbWFqb3IgcGFydGllcyBpbiBVSyBib3RoIHVzZWQgbW9yZSBjb25zZXJ2YXRpdmUgdGVybXMgdGhhbiBsaWJlcmFsIHRlcm1zLCBlc3BlY2lhbGx5IHRoZSBDb25zZXJ2YXRpdmUgUGFydHkuCiAgICAzLiBVSyBJbmRlcGVuZGVuY2UgUGFydHkgdXNlZCBsb3RzIG9mIGNvbnNlcnZhdGl2ZSB0ZXJtcy4gVGhpcyByZXN1bHQgcmV2ZWFsZWQgdGhlIGNoYXJhY3RlcmlzdGljcyBvZiBCcmV4aXQuCgoqKioKPjIuIENsYXNzaWZpZXJzCgpgYGB7cn0KIzIKdHJ1bXAxIDwtIHJlYWQuY3N2KCJ0cnVtcC1vcmlnMy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQp0cnVtcDIgPC0gcmVhZC5jc3YoInRydW1wX3R3ZWV0czIuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKdHJ1bXAxXzEgPC0gcmVhZC5jc3YoInRydW1wLW9yaWczLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCnRydW1wMl8xIDwtIHJlYWQuY3N2KCJ0cnVtcF90d2VldHMyLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCmBgYAoKPjIuMS4gMi1mb2xkIHZhbGlkYXRpb24gb2YgUmFuZG9tRm9yZXN0CgpgYGB7cn0KdHJ1bXAxJHRleHQgPC0gc3RyX3JlcGxhY2VfYWxsKHRydW1wMV8xJHRleHQsICJbXls6YWxudW06XV0iLCAiICIpCgp0cnVtcDEkc2V0diA8LSBhcy5pbnRlZ2VyKGFzLmZhY3Rvcih0cnVtcDFfMSRTZW50aW1lbnQpKQp0cnVtcDEkU2VudGltZW50W3RydW1wMSRzZXR2PT0xXSA8LSAibmVnYXRpdmUiCnRydW1wMSRTZW50aW1lbnRbdHJ1bXAxJHNldHY9PTJdIDwtICJuZXV0cmFsIgp0cnVtcDEkU2VudGltZW50W3RydW1wMSRzZXR2PT0zXSA8LSAicG9zaXRpdmUiCnRydW1wMSRTZW50aW1lbnQgPC0gYXMuZmFjdG9yKHRydW1wMSRTZW50aW1lbnQpCgpjdiAgPC0gY29ycHVzKHRydW1wMSkKZGZtX3RydW1wX2N2IDwtIGRmbShjdiwgCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlID0gYyhzdG9wd29yZHMoImVuZ2xpc2giKSwgKCJhbXAiKSwgKCJydCIpICwoInRjbyIpLCAoImNvIiksICgidSIpLCAoInQiKSwgKCJzIiksICgiZWQiKSwgKCJodHRwcyIpKSwKICAgICAgICAgICAgICAgICAgICByZW1vdmVfcHVuY3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIHJlbW92ZV9udW1iZXJzPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgdG9sb3dlciA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zeW1ib2xzPVRSVUUsIAogICAgICAgICAgICAgICAgICAgIHJlbW92ZV90d2l0dGVyID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX3NlcGFyYXRvcnM9VFJVRSwKICAgICAgICAgICAgICAgICAgICByZW1vdmVfdXJsID0gVFJVRSkKCmRmbV90cnVtcF9uX2N2IDwtIGRmbV90cmltKGRmbV90cnVtcF9jdiAsIG1pbl9kb2NmcmVxPSAwLjA1KQpkYXRhX2N2IDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KGRmbV90cnVtcF9uX2N2KSkKY29sbmFtZXMoZGF0YV9jdiApIDwtIG1ha2UubmFtZXMoY29sbmFtZXMoZGF0YV9jdiApKQpkYXRhX2N2JHNlbnRpbWVudDwtIHRydW1wMSRTZW50aW1lbnQKCnNldC5zZWVkKDEyMykKdG9fdHJhaW4gPC0gc2FtcGxlKDE6NDcyLCAyMzYsIHJlcGxhY2UgPSBGQUxTRSkKdHJhaW5fY3YgPC0gZGF0YV9jdlt0b190cmFpbiwgXQp0ZXN0X2N2ICA8LSBkYXRhX2N2Wy10b190cmFpbiwgXQoKc2V0LnNlZWQoMTIzKQpzeXN0ZW0udGltZShSRl9jdiA8LSByYW5kb21Gb3Jlc3Qoc2VudGltZW50fiAuLCBkYXRhPXRyYWluX2N2LCB0eXBlPSJjbGFzc2lmaWNhdGlvbiIpKQpwcmVkaWN0UkZfY3YgPC0gcHJlZGljdChSRl9jdiwgbmV3ZGF0YT10ZXN0X2N2KQp4Ml9jdiA8LSBhcy5tYXRyaXgodGFibGUoIlByZWRpY3Rpb25zIj0gcHJlZGljdFJGX2N2LCAiQWN0dWFsIj10ZXN0X2N2JHNlbnRpbWVudCkpCmFjY19jdiA8LSBzdW0oZGlhZyh4Ml9jdikpIC8gc3VtKHgyX2N2KQoKY29uZi5tYXRfY3YgPC0gY29uZnVzaW9uTWF0cml4KCBwcmVkaWN0UkZfY3YsIHRlc3RfY3Ykc2VudGltZW50KQoKc2V0LnNlZWQoMTIzKQpzeXN0ZW0udGltZShSRjJfY3YgPC0gcmFuZG9tRm9yZXN0KHNlbnRpbWVudH4gLiwgZGF0YT10ZXN0X2N2LCB0eXBlPSJjbGFzc2lmaWNhdGlvbiIpKQpwcmVkaWN0UkYyX2N2IDwtIHByZWRpY3QoUkYyX2N2LCBuZXdkYXRhPXRyYWluX2N2KQp4Ml8yX2N2IDwtIGFzLm1hdHJpeCh0YWJsZSgiUHJlZGljdGlvbnMiPSBwcmVkaWN0UkYyX2N2LCAiQWN0dWFsIj10cmFpbl9jdiRzZW50aW1lbnQpKQphY2NfYWx0X2N2IDwtIHN1bShkaWFnKHgyXzJfY3YpKSAvIHN1bSh4Ml8yX2N2KQoKY29uZi5tYXQyX2N2IDwtIGNvbmZ1c2lvbk1hdHJpeCggcHJlZGljdFJGMl9jdiwgdHJhaW5fY3Ykc2VudGltZW50KQoKcmVzMV9jdiA8LSBhcy5kYXRhLmZyYW1lKGNvbmYubWF0X2N2JGJ5Q2xhc3MpCnJlczJfY3YgPC0gYXMuZGF0YS5mcmFtZShjb25mLm1hdDJfY3YkYnlDbGFzcykKYGBgCj4yLjIuIFByZWRpY3Rpb246IFJhbmRvbUZvcmVzdAoKYGBge3J9CnRydW1wIDwtIHNlbGVjdCh0cnVtcDFfMSx0ZXh0KQp0cnVtcF90IDwtIHNlbGVjdCh0cnVtcDJfMSx0ZXh0KQp0cnVtcCA8LSBhcy5kYXRhLmZyYW1lKHJiaW5kKHRydW1wLHRydW1wX3QpKQp0cnVtcCR0ZXh0IDwtIHN0cl9yZXBsYWNlX2FsbCh0cnVtcCR0ZXh0LCAiW15bOmFsbnVtOl1dIiwgIiAiKQp0dCAgPC0gY29ycHVzKHRydW1wKQoKZGZtX3RydW1wX24gPC0gZGZtKHR0LCAKICAgICAgICAgICAgICAgICAgIHJlbW92ZSA9IGMoc3RvcHdvcmRzKCJlbmdsaXNoIiksICgiYW1wIiksICgicnQiKSAsKCJ0Y28iKSwgKCJjbyIpLCAoInUiKSwgKCJ0IiksICgicyIpLCAoImVkIiksICgiaHR0cHMiKSksCiAgICAgICAgICAgICAgICAgICByZW1vdmVfcHVuY3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgcmVtb3ZlX251bWJlcnM9VFJVRSwKICAgICAgICAgICAgICAgICAgIHRvbG93ZXIgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zeW1ib2xzPVRSVUUsIAogICAgICAgICAgICAgICAgICAgcmVtb3ZlX3R3aXR0ZXIgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zZXBhcmF0b3JzPVRSVUUsCiAgICAgICAgICAgICAgICAgICByZW1vdmVfdXJsID0gVFJVRSkKCmRmbV90cnVtcF9uX3QgPC0gZGZtX3RyaW0oZGZtX3RydW1wX24gLCBtaW5fZG9jZnJlcT0gMC4wNSkKZGF0YV90IDwtIGFzLmRhdGEuZnJhbWUoYXMubWF0cml4KGRmbV90cnVtcF9uX3QpKQoKY29sbmFtZXMoZGF0YV90ICkgPC0gbWFrZS5uYW1lcyhjb2xuYW1lcyhkYXRhX3QgKSkKCnRyYWluIDwtIGRhdGFfdFsxOjQ3MiwgXQoKdHJ1bXAxJHNldHYgPC0gYXMuaW50ZWdlcihhcy5mYWN0b3IodHJ1bXAxXzEkU2VudGltZW50KSkKdHJ1bXAxJFNlbnRpbWVudFt0cnVtcDEkc2V0dj09MV0gPC0gIm5lZ2F0aXZlIgp0cnVtcDEkU2VudGltZW50W3RydW1wMSRzZXR2PT0yXSA8LSAibmV1dHJhbCIKdHJ1bXAxJFNlbnRpbWVudFt0cnVtcDEkc2V0dj09M10gPC0gInBvc2l0aXZlIgp0cnVtcDEkU2VudGltZW50IDwtIGFzLmZhY3Rvcih0cnVtcDEkU2VudGltZW50KQoKdHJhaW4kc2VudGltZW50IDwtIHRydW1wMSRTZW50aW1lbnQKCnRlc3QgIDwtIGRhdGFfdFs0NzM6MTQ3MiwgXQoKc2V0LnNlZWQoMTIzKQpzeXN0ZW0udGltZShSRiA8LSByYW5kb21Gb3Jlc3Qoc2VudGltZW50fiAuLCBkYXRhPXRyYWluLCB0eXBlPSJjbGFzc2lmaWNhdGlvbiIpKQpwcmVkaWN0UkYgPC0gcHJlZGljdChSRiwgbmV3ZGF0YT10ZXN0KQpyZXNfcmYgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShwcmVkaWN0UkYpKQpyZXNfcmYkUHJvYiA8LSByZXNfcmYkRnJlcS9zdW0ocmVzX3JmJEZyZXEpCmBgYAo+Mi4zLiBQcmVkaWN0aW9uOiBOYWl2ZSBCYXllcwpgYGB7cn0KCnR0YzIgIDwtIGNvcnB1cyh0cnVtcDIpCmRmbV90cnVtcDJfbiA8LSBkZm0odHRjMiwgCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlID0gYyhzdG9wd29yZHMoImVuZ2xpc2giKSwgKCJhbXAiKSwgKCJydCIpICwoInRjbyIpLCAoImNvIiksICgidSIpLCAoInQiKSwgKCJzIiksICgiZWQiKSwgKCJodHRwcyIpKSwKICAgICAgICAgICAgICAgICAgICByZW1vdmVfcHVuY3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIHJlbW92ZV9udW1iZXJzPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgdG9sb3dlciA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgIHJlbW92ZV9zeW1ib2xzPVRSVUUsIAogICAgICAgICAgICAgICAgICAgIHJlbW92ZV90d2l0dGVyID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlX3NlcGFyYXRvcnM9VFJVRSwKICAgICAgICAgICAgICAgICAgICByZW1vdmVfdXJsID0gVFJVRSkKZGZtX3RydW1wMl9uX3QgPC0gZGZtX3RyaW0oZGZtX3RydW1wMl9uICwgbWluX2RvY2ZyZXE9IDAuMDUpCgp0cnVtcDEkc2V0diA8LSBhcy5pbnRlZ2VyKGFzLmZhY3Rvcih0cnVtcDFfMSRTZW50aW1lbnQpKQp0cnVtcDEkU2VudGltZW50W3RydW1wMSRzZXR2PT0xXSA8LSAibmVnYXRpdmUiCnRydW1wMSRTZW50aW1lbnRbdHJ1bXAxJHNldHY9PTJdIDwtICJuZXV0cmFsIgp0cnVtcDEkU2VudGltZW50W3RydW1wMSRzZXR2PT0zXSA8LSAicG9zaXRpdmUiCnRydW1wMSRTZW50aW1lbnQgPC0gYXMuZmFjdG9yKHRydW1wMSRTZW50aW1lbnQpCgoKdHJ1bXAxJHRleHQgPC0gc3RyX3JlcGxhY2VfYWxsKHRydW1wMSR0ZXh0LCAiW15bOmFsbnVtOl1dIiwgIiAiKQp0dG8gPC0gY29ycHVzKHRydW1wMSkKZGZtX3RydW1wMSA8LSBkZm0odHRvLCAKICAgICAgICAgICAgICAgICAgcmVtb3ZlID0gYyhzdG9wd29yZHMoImVuZ2xpc2giKSwgKCJhbXAiKSwgKCJydCIpICwoInRjbyIpLCAoImNvIiksICgidSIpLCAoInQiKSwgKCJzIiksICgiZWQiKSwgKCJodHRwcyIpKSwKICAgICAgICAgICAgICAgICAgcmVtb3ZlX3B1bmN0ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgcmVtb3ZlX251bWJlcnM9VFJVRSwKICAgICAgICAgICAgICAgICAgdG9sb3dlciA9IFRSVUUsIAogICAgICAgICAgICAgICAgICByZW1vdmVfc3ltYm9scz1UUlVFLCAKICAgICAgICAgICAgICAgICAgcmVtb3ZlX3R3aXR0ZXIgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgcmVtb3ZlX3NlcGFyYXRvcnM9VFJVRSwKICAgICAgICAgICAgICAgICAgcmVtb3ZlX3VybCA9IFRSVUUpCmRmbV90cnVtcDFfdCA8LSBkZm1fdHJpbShkZm1fdHJ1bXAxLCBtaW5fZG9jZnJlcT0gMC4wNSkKZGF0YV90MSA8LSBhcy5kYXRhLmZyYW1lKGFzLm1hdHJpeChkZm1fdHJ1bXAxX3QpKQpjb2xuYW1lcyhkYXRhX3QxKSA8LSBtYWtlLm5hbWVzKGNvbG5hbWVzKGRhdGFfdDEgKSkKZGF0YV90MSRzZW50aW1lbnQ8LSB0cnVtcDEkU2VudGltZW50CgpuYiA8LSB0ZXh0bW9kZWxfbmIoZGZtX3RydW1wMV90ICxkb2N2YXJzKGRmbV90cnVtcDFfdCwgIlNlbnRpbWVudCIpLCBkaXN0cmlidXRpb24gPSBjKCJtdWx0aW5vbWlhbCIpKQpkZm1fdHJ1bXAyX25fdCA8LSBkZm1fc2VsZWN0KGRmbV90cnVtcDJfbl90LCBkZm1fdHJ1bXAxX3QpCgpwcmVkaWN0ZWRfbmIgPC0gcHJlZGljdChuYiwgZGZtX3RydW1wMl9uX3QpCnJlc19uYiA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHByZWRpY3RlZF9uYikpCnJlc19uYiRQcm9iIDwtIHJlc19uYiRGcmVxL3N1bShyZXNfbmIkRnJlcSkKYGBgCj4yLjQuIFJlc3VsdHMKPjIuNC4xLiBBY2N1cmFjeSBvZiB2YWxpZGF0aW9uCgo+QS4gVGFibGUgMQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnRhYmxlKHRlc3RfY3Ykc2VudGltZW50LCBwcmVkaWN0UkZfY3YpICU+JQogIGthYmxlKCkgJT4lCiAga2FibGVfc3R5bGluZygpCgpgYGAKKiBUaGlzIGlzIHRoZSBmaXJzdCByZXN1bHQgb2YgY3Jvc3MtdmFsaWRhdGlvbi4KCj5CLiBUYWJsZSAyCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0YWJsZSh0cmFpbl9jdiRzZW50aW1lbnQsIHByZWRpY3RSRjJfY3YpJT4lCiAga2FibGUoKSAlPiUKICBrYWJsZV9zdHlsaW5nKCkKYGBgCiogVGhpcyBpcyB0aGUgc2Vjb25kIHJlc3VsdCBvZiBjcm9zcy12YWxpZGF0aW9uLgoKPkMuIEFjY3VyYWN5CmBgYHtyfQphY19jdiA8LSAoYWNjX2N2K2FjY19hbHRfY3YpLzIKYWNfY3YKYGBgCj5ELiBQcmVjaXNpb24gYW5kIFJlY2FsbApgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnJlc19hbGxfY3YgPC0gZGF0YS5mcmFtZSgKICBwcmVjaXNpb24xPXJlczFfY3YkUHJlY2lzaW9uLAogIHJlY2FsbDE9cmVzMV9jdiRSZWNhbGwsCiAgcHJlY2lzaW9uMj1yZXMyX2N2JFByZWNpc2lvbiwKICByZWNhbGwyPXJlczJfY3YkUmVjYWxsCikKCnJvd25hbWVzKHJlc19hbGxfY3YpIDwtIHJvd25hbWVzKHJlczFfY3YpCgpyZXNfYWxsX2N2ICU+JQogIGthYmxlKCkgJT4lCiAga2FibGVfc3R5bGluZygpCmBgYAoKKiBBcyB0aGUgYWNjdXJhY3ksIHByZWNpc2lvbiwgcmVjYWxsIHJlc3VsdHMgb2YgMi1mb2xkIGNyb3NzIHZhbGlkYXRpb24sIHRoZXJlJ3JlIHNvbWUgY29uY2x1c2lvbnMuCiAgICAxLiBUaGlzIG1vZGVsIGNhbiBub3Qgc3VjY2VzZnVsbHkgcHJlZGljdGVkIHRoZSBwb3NpdGl2ZSB0d2VldC4KICAgIDIuIFRoZSByZWNhbGwgb2YgbmVnYXRpdmUgd2FzIHRoZSBoaWdoZXN0LCB0aGUgbW9kZWwgcGVyZm9ybWFuY2VkIGJldHRlciBvbiBpZGVudGlmaWVkIHRoZSBuZWdhdGl2ZSB0d2VldC4KCj4yLjQuMi4gUHJlZGljdGlvbiBvZiBSYW5kb21Gb3Jlc3QKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlc19yZiU+JQogIGthYmxlKCkgJT4lCiAga2FibGVfc3R5bGluZygpCmBgYAoqIEFzIHRoZSBjcm9zcy12YWxpZGF0aW9uIGluIDIuNC4xLCB0aGUgUmFuZG9tIEZvcmVzdCBtb2RlbCBjb3VsZCBub3QgaWRlbnRpZmllZCB0aGUgcG9zaXRpdmUgdHdlZXQgYWdhaW4gaW4gcHJlZGljdGlvbi4KCgo+Mi40LjMuIFByZWRpY3Rpb24gb2YgTmFpdmUgQmF5ZXMKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlc19uYiU+JQogIGthYmxlKCkgJT4lCiAga2FibGVfc3R5bGluZygpCmBgYAoKKiBUaGUgTmFpdmUgQmF5ZXMgbW9kZWwgc2hvd2VkIGxhcmdlbHkgZGlmZmVyZW50IHJlc3VsdCwgY29tcGFyaW5nIHRvIHRoZSBSRiBtb2RlbC4KKiBTaW5jZSBJIGRpZCBub3QgdGVzdCB0aGUgY3Jvc3MgdmFsaWRhdGlvbiBvZiB0aGlzIG1vZGVsLiBUaGUgYWNjdXJhY3kgYW5kIG1vZGVsIGNob2ljZSBuZWVkcyBmdXJ0aGVyIGRpc2N1c3Npb24uCgoqKio=