Motivation

I’ve been interested in playing around with text mining in R now for a while. Specifically, I wanted to try out some of the methods outlined here.

The other week I checked my email and saw a new issue of Data is Plural that linked to the UN General Debate Corpus.

Every year since 1947, representatives of UN member states gather at the annual sessions of the United Nations General Assembly. The centrepiece of each session is the General Debate. This is a forum at which leaders and other senior officials deliver statements that present their government’s perspective on the major issues in world politics. These statements are akin to the annual legislative state-of-the-union addresses in domestic politics. This new dataset, the UN General Debate Corpus (UNGDC), introduces the corpus of texts of General Debate statements from 1970 (Session 25) to 2016 (Session 71).

I will use this data to perform [1] a term frequency analysis and [2] a sentiment analysis.

[1] Term frequency analysis

I am going to use this data to compare the content of UN Security council countries’ speeches via tf-idf (a statistic that shows how important a word is to a document in a corpus). Think of the countries as different documents and the corpus as the collection of all speeches. The Security Council countries are the US, Britain, France, China, and Russia.

The tf-idf measure has been used for looking into phrases used by GOP candidates. It has also been used to discover the most important words by character in the TV Show Seinfeld. Those are just a few examples.

I downloaded the txt files for those countries’ speeches in a folder sec_council. Let’s now import the data.

library(readtext)
speeches<-readtext("sec_council/*",
         docvarsfrom = "filenames", 
         docvarnames = c("country", "speech_num", "year"),
         dvsep = "_", 
         encoding = "ISO-8859-1")

Let’s make some basic changes to the text.

speeches$text<-gsub("'s", "", speeches$text)
speeches$text<-gsub("â", "", speeches$text)
speeches$text<-gsub("92s", "s", speeches$text)
speeches$text<-gsub("Prance", "France", speeches$text)

Now, we follow steps outlined in the Text Mining manual.

library(tidytext); library(dplyr); library(tidyr)
country_words <- speeches %>%
  unnest_tokens(word, text) %>%
  count(country, word, sort = TRUE) %>%
  ungroup()
total_words <- country_words %>% 
  group_by(country) %>% 
  summarize(total = sum(n))
country_words1 <- left_join(country_words, total_words)
country_words1
country_words2 <- country_words1 %>%
  bind_tf_idf(word, country, n)
country_words2
country_words2 %>%
  select(-total) %>%
  arrange(desc(tf_idf))

Call my theme so that I can then plot the tf-idf words for countries.

library(ggplot2);library(ggrepel); library(extrafont); library(ggthemes);library(reshape);library(grid);
library(scales);library(RColorBrewer);library(gridExtra);
my_theme <- function() {
  # Define colors for the chart
  palette <- brewer.pal("Greys", n=9)
  color.background = palette[2]
  color.grid.major = palette[4]
  color.panel = palette[3]
  color.axis.text = palette[9]
  color.axis.title = palette[9]
  color.title = palette[9]
  # Create basic construction of chart
  theme_bw(base_size=9, base_family="Palatino") + 
  # Set the entire chart region to a light gray color
  theme(panel.background=element_rect(fill=color.panel, color=color.background)) +
  theme(plot.background=element_rect(fill=color.background, color=color.background)) +
  theme(panel.border=element_rect(color=color.background)) +
  # Format grid
  theme(panel.grid.major=element_line(color=color.grid.major,size=.25)) +
  theme(panel.grid.minor=element_blank()) +
  theme(axis.ticks=element_blank()) +
  # Format legend
  theme(legend.position="right") +
  theme(legend.background = element_rect(fill=color.background)) +
  theme(legend.text = element_text(size=8,color=color.axis.title)) + 
  theme(legend.title = element_blank()) + 
  
  #Format facet labels
  theme(strip.text.x = element_text(size = 8, face="bold"))+
  # Format title and axes labels these and tick marks
  theme(plot.title=element_text(color=color.title, size=28)) +
  theme(axis.text.x=element_text(size=8)) +
  theme(axis.text.y=element_text(size=8)) +
  theme(axis.title.x=element_text(size=8)) +
  theme(axis.title.y=element_text(size=8)) +
  #Format title and facet_wrap title
  theme(strip.text = element_text(size=8), plot.title = element_text(size = 16, colour = "black", vjust = 1, hjust=0))+
    
  # Plot margins
  theme(plot.margin = unit(c(.2, .2, .2, .5), "cm"))
}

Plot top 20 tf-idf words

library(ggplot2)
plot_country <- country_words2 %>%
  arrange(desc(tf_idf)) %>%
  mutate(word = factor(word, levels = rev(unique(word))))
plot_country %>% 
  top_n(20) %>%
  ggplot(aes(word, tf_idf, fill = country)) +
  geom_col() +
  scale_fill_brewer(palette="Accent")+
  scale_y_continuous(labels = comma)+
  my_theme()+
  ggtitle("Most Important Words to National Security Council Countries", subtitle="As determined by tf-idf scores generated from 1970-2016 UN General Debate speeches")+
  labs(y = "tf-idf score", x=NULL, caption="Data Source: United Nations General Debate Corpus\nVisualization via Alex Albright (thelittledataset.com)") +
  coord_flip()+
  ggsave("tfidftotal.png", width = 8, height = 5, dpi = 800)
Selecting by tf_idf

These are the top 20 words in terms of tf-idf scores.

Plot top 5 words for each country

top5words<-plot_country %>% 
  group_by(country) %>% 
  arrange(desc(tf_idf)) %>%
  top_n(5) 
Selecting by tf_idf
top5words 
top5words %>% 
  arrange(desc(tf_idf)) %>%
  ggplot(aes(word, tf_idf, fill = country)) +
  geom_col(show.legend = FALSE) +
  scale_y_continuous(labels = comma)+
  my_theme()+
  labs(x = NULL, y = "tf-idf") +
  scale_fill_brewer(palette="Accent")+
  facet_wrap(~country, ncol = 2, scales = "free") +
  my_theme()+
  ggtitle("Top 5 Most Important Words to Each National Security Council Country", subtitle="As determined by tf-idf scores generated from 1970-2016 UN General Debate speeches")+
  labs(y = "tf-idf score", x=NULL, caption="Data Source: United Nations General Debate Corpus\nVisualization via Alex Albright (thelittledataset.com)") +
  coord_flip()+
  theme(plot.margin = unit(c(.2, .6, .2, .4), "cm"))+
  ggsave("tfidf_country.png", width = 8.33, height = 7, dpi = 800)

What’s with “twelve” for France? Well, I looked into some of the usages and “The Twelve” is used to refer to Europe! This makes sense as France and Great Britain use it throughout their speeches.

[2] Sentiment Analysis

Positivity scores (comparing 3 lexicons)

I want to know which country is the most positive. There are three dictionaries for this: bing, AFINN, and NRC. We will compare all three.

emo_words <- speeches %>%
  group_by(country) %>%
  ungroup() %>%
  unnest_tokens(word, text) 
emo_words
#find total number of words
wordstot <- emo_words %>%
  count(country)
wordstot$tot<-wordstot$n
wordstot<-wordstot[,c("country", "tot")]

Ok now let’s link these words and countries up with the three lexicon dictionaries of interest.

afinn <- emo_words %>% 
  inner_join(get_sentiments("afinn")) %>% 
  group_by(country) %>% 
  summarise(sentiment = sum(score)) %>% 
  mutate(method = "AFINN")
Joining, by = "word"
bing_and_nrc <- bind_rows(emo_words %>% 
                            inner_join(get_sentiments("bing")) %>%
                            mutate(method = "Bing"),
                          emo_words %>% 
                            inner_join(get_sentiments("nrc") %>% 
                                         filter(sentiment %in% c("positive", 
                                                                 "negative"))) %>%
                            mutate(method = "NRC")) %>%
  count(method, country, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
Joining, by = "word"
Joining, by = "word"

The above is an adapted chunk of text from this page.

bind_rows(afinn, 
          bing_and_nrc) %>%
  ggplot(aes(country, sentiment, fill = country)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~method, ncol = 3, scales = "free")+
  my_theme()+ theme(plot.margin = unit(c(.2, .4, .2, .4), "cm"))+
  ggtitle("Which Country is the Most Positive?", subtitle="AFINN, Bing, NRC positivity scores generated using 1970-2016 UN General Debate speeches")+
  labs(y = NULL, x=NULL, caption="\nData Source: United Nations General Debate Corpus\nVisualization via Alex Albright (thelittledataset.com)") +
  scale_fill_brewer(palette="Accent")+
  ggsave("country_pos.png", width = 8, height = 5, dpi = 800)

NRC scores

What if we looked more in depth at the NRC sentiment dictionary? It’s got a long list of sentiments! …way beyond positive and negative!

emo_words <- emo_words %>%
        inner_join(get_sentiments("nrc")) %>%
        filter(!is.na(sentiment)) %>%
        filter(!is.na(country)) %>%
        count(country, sentiment, sort = TRUE)
Joining, by = "word"
#bring in totals so we can make these percentages
emo_words <- merge(emo_words, wordstot, by="country")
emo_words$nperc <- emo_words$n/emo_words$tot
emo_words
my.cols <- brewer.pal(10, "Set3")
my.cols[9] <- "grey60"
emo_words %>% 
  ggplot(aes(x=country, y=nperc, fill = sentiment)) +
  geom_bar(stat="identity") +
  scale_y_continuous(labels = percent)+
  my_theme()+
  scale_fill_manual(values=my.cols)+
  my_theme()+ 
  ggtitle("Countries have feelings too!", subtitle="NRC sentiment word percentages generated from 1970-2016 UN General Debate speeches")+
  labs(y = "Percentage of emotion-related words", x=NULL, caption="\nData Source: United Nations General Debate Corpus\nVisualization via Alex Albright (thelittledataset.com)") +
  coord_flip()+
  theme(plot.margin = unit(c(.2, .2, .2, .2), "cm"))+
  ggsave("feelings1.png", width = 8, height = 5, dpi = 800)

emo_words %>% 
  ggplot(aes(x=country, y=nperc, fill = sentiment)) +
  geom_bar(stat="identity", position="dodge") +
  scale_y_continuous(labels = percent)+
  my_theme()+
  scale_fill_manual(values=my.cols)+
  my_theme()+
  ggtitle("Countries have feelings too!", subtitle="NRC sentiment word percentages generated from 1970-2016 UN General Debate Speeches")+
  labs(y = "Percentage of emotion-related words", x=NULL, caption="\nData Source: United Nations General Debate Corpus\nVisualization via Alex Albright (thelittledataset.com)") +
  coord_flip()+
  theme(plot.margin = unit(c(.2, .2, .2, .3), "cm"))+
  ggsave("feelings2.png", width = 8, height = 5, dpi = 800)

Interesting how similar the break-downs are by emotion for all the countries. I wonder how speeches like this compare to books or articles. I’d imagine in some ways they are more dramatic/emotional, as they are speeches on behalf of a country on an international stage. Perhaps that’s something to look into another day.

The End!

LS0tCnRpdGxlOiAiVGhlIFVuaXRlZCBOYXRpb25zIG9mIFdvcmRzIgphdXRob3I6IEFsZXggQWxicmlnaHQKZGF0ZTogOS0xMy0xNwpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCiMgTW90aXZhdGlvbgpJJ3ZlIGJlZW4gaW50ZXJlc3RlZCBpbiBwbGF5aW5nIGFyb3VuZCB3aXRoIHRleHQgbWluaW5nIGluIFIgbm93IGZvciBhIHdoaWxlLiBTcGVjaWZpY2FsbHksIEkgd2FudGVkIHRvIHRyeSBvdXQgc29tZSBvZiB0aGUgbWV0aG9kcyBvdXRsaW5lZCAgW2hlcmUuXShodHRwOi8vdGlkeXRleHRtaW5pbmcuY29tLykgCgpUaGUgb3RoZXIgd2VlayBJIGNoZWNrZWQgbXkgZW1haWwgYW5kIHNhdyBhIG5ldyBpc3N1ZSBvZiBbRGF0YSBpcyBQbHVyYWxdKGh0dHBzOi8vdGlueWxldHRlci5jb20vZGF0YS1pcy1wbHVyYWwpIHRoYXQgbGlua2VkIHRvIHRoZSBbVU4gR2VuZXJhbCBEZWJhdGUgQ29ycHVzLl0oaHR0cHM6Ly9kYXRhdmVyc2UuaGFydmFyZC5lZHUvZGF0YXNldC54aHRtbD9wZXJzaXN0ZW50SWQ9ZG9pOjEwLjc5MTAvRFZOLzBUSlg4WSkKCj4gRXZlcnkgeWVhciBzaW5jZSAxOTQ3LCByZXByZXNlbnRhdGl2ZXMgb2YgVU4gbWVtYmVyIHN0YXRlcyBnYXRoZXIgYXQgdGhlIGFubnVhbCBzZXNzaW9ucyBvZiB0aGUgVW5pdGVkIE5hdGlvbnMgR2VuZXJhbCBBc3NlbWJseS4gVGhlIGNlbnRyZXBpZWNlIG9mIGVhY2ggc2Vzc2lvbiBpcyB0aGUgR2VuZXJhbCBEZWJhdGUuIFRoaXMgaXMgYSBmb3J1bSBhdCB3aGljaCBsZWFkZXJzIGFuZCBvdGhlciBzZW5pb3Igb2ZmaWNpYWxzIGRlbGl2ZXIgc3RhdGVtZW50cyB0aGF0IHByZXNlbnQgdGhlaXIgZ292ZXJubWVudOKAmXMgcGVyc3BlY3RpdmUgb24gdGhlIG1ham9yIGlzc3VlcyBpbiB3b3JsZCBwb2xpdGljcy4gVGhlc2Ugc3RhdGVtZW50cyBhcmUgYWtpbiB0byB0aGUgYW5udWFsIGxlZ2lzbGF0aXZlIHN0YXRlLW9mLXRoZS11bmlvbiBhZGRyZXNzZXMgaW4gZG9tZXN0aWMgcG9saXRpY3MuIFRoaXMgbmV3IGRhdGFzZXQsIHRoZSBVTiBHZW5lcmFsIERlYmF0ZSBDb3JwdXMgKFVOR0RDKSwgaW50cm9kdWNlcyB0aGUgY29ycHVzIG9mIHRleHRzIG9mIEdlbmVyYWwgRGViYXRlIHN0YXRlbWVudHMgZnJvbSAxOTcwIChTZXNzaW9uIDI1KSB0byAyMDE2IChTZXNzaW9uIDcxKS4KCkkgd2lsbCB1c2UgdGhpcyBkYXRhIHRvIHBlcmZvcm0gWzFdIGEgdGVybSBmcmVxdWVuY3kgYW5hbHlzaXMgYW5kIFsyXSBhIHNlbnRpbWVudCBhbmFseXNpcy4KCiMgWzFdIFRlcm0gZnJlcXVlbmN5IGFuYWx5c2lzCgpJIGFtIGdvaW5nIHRvIHVzZSB0aGlzIGRhdGEgdG8gY29tcGFyZSB0aGUgY29udGVudCBvZiBVTiBTZWN1cml0eSBjb3VuY2lsIGNvdW50cmllcycgc3BlZWNoZXMgdmlhIFt0Zi1pZGZdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RmJUUyJTgwJTkzaWRmKSAoYSBzdGF0aXN0aWMgdGhhdCBzaG93cyBob3cgaW1wb3J0YW50IGEgd29yZCBpcyB0byBhIGRvY3VtZW50IGluIGEgY29ycHVzKS4gVGhpbmsgb2YgdGhlIGNvdW50cmllcyBhcyBkaWZmZXJlbnQgZG9jdW1lbnRzIGFuZCB0aGUgY29ycHVzIGFzIHRoZSBjb2xsZWN0aW9uIG9mIGFsbCBzcGVlY2hlcy4gVGhlIFNlY3VyaXR5IENvdW5jaWwgY291bnRyaWVzIGFyZSB0aGUgVVMsIEJyaXRhaW4sIEZyYW5jZSwgQ2hpbmEsIGFuZCBSdXNzaWEuCgpUaGUgdGYtaWRmIG1lYXN1cmUgaGFzIGJlZW4gdXNlZCBmb3IgbG9va2luZyBpbnRvIFtwaHJhc2VzIHVzZWQgYnkgR09QIGNhbmRpZGF0ZXNdKGh0dHBzOi8vZml2ZXRoaXJ0eWVpZ2h0LmNvbS9mZWF0dXJlcy90aGVzZS1hcmUtdGhlLXBocmFzZXMtZWFjaC1nb3AtY2FuZGlkYXRlLXJlcGVhdHMtbW9zdC8pLiBJdCBoYXMgYWxzbyBiZWVuIHVzZWQgdG8gZGlzY292ZXIgdGhlIFttb3N0IGltcG9ydGFudCB3b3JkcyBieSBjaGFyYWN0ZXIgaW4gdGhlIFRWIFNob3cgU2VpbmZlbGRdKGh0dHA6Ly9tZGdiZWNrLm5ldGxpZnkuY29tL3Bvc3QvdGlkeXRleHQtYW5hbHlzaXMtb2Ytc2VpbmZlbGQvKS4gVGhvc2UgYXJlIGp1c3QgYSBmZXcgZXhhbXBsZXMuCgpJIGRvd25sb2FkZWQgdGhlIGB0eHRgIGZpbGVzIGZvciB0aG9zZSBjb3VudHJpZXMnIHNwZWVjaGVzIGluIGEgZm9sZGVyIGBzZWNfY291bmNpbGAuIExldCdzIG5vdyBpbXBvcnQgdGhlIGRhdGEuCmBgYHtyfQpsaWJyYXJ5KHJlYWR0ZXh0KQpzcGVlY2hlczwtcmVhZHRleHQoInNlY19jb3VuY2lsLyoiLAogICAgICAgICBkb2N2YXJzZnJvbSA9ICJmaWxlbmFtZXMiLCAKICAgICAgICAgZG9jdmFybmFtZXMgPSBjKCJjb3VudHJ5IiwgInNwZWVjaF9udW0iLCAieWVhciIpLAogICAgICAgICBkdnNlcCA9ICJfIiwgCiAgICAgICAgIGVuY29kaW5nID0gIklTTy04ODU5LTEiKQpgYGAKCkxldCdzIG1ha2Ugc29tZSBiYXNpYyBjaGFuZ2VzIHRvIHRoZSB0ZXh0LiAKYGBge3J9CnNwZWVjaGVzJHRleHQ8LWdzdWIoIidzIiwgIiIsIHNwZWVjaGVzJHRleHQpCnNwZWVjaGVzJHRleHQ8LWdzdWIoIsOiIiwgIiIsIHNwZWVjaGVzJHRleHQpCnNwZWVjaGVzJHRleHQ8LWdzdWIoIjkycyIsICJzIiwgc3BlZWNoZXMkdGV4dCkKc3BlZWNoZXMkdGV4dDwtZ3N1YigiUHJhbmNlIiwgIkZyYW5jZSIsIHNwZWVjaGVzJHRleHQpCmBgYAoKTm93LCB3ZSBmb2xsb3cgc3RlcHMgb3V0bGluZWQgaW4gdGhlIFRleHQgTWluaW5nIG1hbnVhbC4gCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl0ZXh0KTsgbGlicmFyeShkcGx5cik7IGxpYnJhcnkodGlkeXIpCgpjb3VudHJ5X3dvcmRzIDwtIHNwZWVjaGVzICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgY291bnQoY291bnRyeSwgd29yZCwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQoKdG90YWxfd29yZHMgPC0gY291bnRyeV93b3JkcyAlPiUgCiAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSkKCmNvdW50cnlfd29yZHMxIDwtIGxlZnRfam9pbihjb3VudHJ5X3dvcmRzLCB0b3RhbF93b3JkcykKCmNvdW50cnlfd29yZHMxCmBgYAoKYGBge3J9CmNvdW50cnlfd29yZHMyIDwtIGNvdW50cnlfd29yZHMxICU+JQogIGJpbmRfdGZfaWRmKHdvcmQsIGNvdW50cnksIG4pCmNvdW50cnlfd29yZHMyCmBgYApgYGB7cn0KY291bnRyeV93b3JkczIgJT4lCiAgc2VsZWN0KC10b3RhbCkgJT4lCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpCmBgYApDYWxsIG15IHRoZW1lIHNvIHRoYXQgSSBjYW4gdGhlbiBwbG90IHRoZSB0Zi1pZGYgd29yZHMgZm9yIGNvdW50cmllcy4KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2dwbG90Mik7bGlicmFyeShnZ3JlcGVsKTsgbGlicmFyeShleHRyYWZvbnQpOyBsaWJyYXJ5KGdndGhlbWVzKTtsaWJyYXJ5KHJlc2hhcGUpO2xpYnJhcnkoZ3JpZCk7CmxpYnJhcnkoc2NhbGVzKTtsaWJyYXJ5KFJDb2xvckJyZXdlcik7bGlicmFyeShncmlkRXh0cmEpOwoKbXlfdGhlbWUgPC0gZnVuY3Rpb24oKSB7CgogICMgRGVmaW5lIGNvbG9ycyBmb3IgdGhlIGNoYXJ0CiAgcGFsZXR0ZSA8LSBicmV3ZXIucGFsKCJHcmV5cyIsIG49OSkKICBjb2xvci5iYWNrZ3JvdW5kID0gcGFsZXR0ZVsyXQogIGNvbG9yLmdyaWQubWFqb3IgPSBwYWxldHRlWzRdCiAgY29sb3IucGFuZWwgPSBwYWxldHRlWzNdCiAgY29sb3IuYXhpcy50ZXh0ID0gcGFsZXR0ZVs5XQogIGNvbG9yLmF4aXMudGl0bGUgPSBwYWxldHRlWzldCiAgY29sb3IudGl0bGUgPSBwYWxldHRlWzldCgogICMgQ3JlYXRlIGJhc2ljIGNvbnN0cnVjdGlvbiBvZiBjaGFydAogIHRoZW1lX2J3KGJhc2Vfc2l6ZT05LCBiYXNlX2ZhbWlseT0iUGFsYXRpbm8iKSArIAoKICAjIFNldCB0aGUgZW50aXJlIGNoYXJ0IHJlZ2lvbiB0byBhIGxpZ2h0IGdyYXkgY29sb3IKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kPWVsZW1lbnRfcmVjdChmaWxsPWNvbG9yLnBhbmVsLCBjb2xvcj1jb2xvci5iYWNrZ3JvdW5kKSkgKwogIHRoZW1lKHBsb3QuYmFja2dyb3VuZD1lbGVtZW50X3JlY3QoZmlsbD1jb2xvci5iYWNrZ3JvdW5kLCBjb2xvcj1jb2xvci5iYWNrZ3JvdW5kKSkgKwogIHRoZW1lKHBhbmVsLmJvcmRlcj1lbGVtZW50X3JlY3QoY29sb3I9Y29sb3IuYmFja2dyb3VuZCkpICsKCiAgIyBGb3JtYXQgZ3JpZAogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG9yPWNvbG9yLmdyaWQubWFqb3Isc2l6ZT0uMjUpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSkgKwoKICAjIEZvcm1hdCBsZWdlbmQKICB0aGVtZShsZWdlbmQucG9zaXRpb249InJpZ2h0IikgKwogIHRoZW1lKGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9Y29sb3IuYmFja2dyb3VuZCkpICsKICB0aGVtZShsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTgsY29sb3I9Y29sb3IuYXhpcy50aXRsZSkpICsgCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArIAogIAogICNGb3JtYXQgZmFjZXQgbGFiZWxzCiAgdGhlbWUoc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4LCBmYWNlPSJib2xkIikpKwoKICAjIEZvcm1hdCB0aXRsZSBhbmQgYXhlcyBsYWJlbHMgdGhlc2UgYW5kIHRpY2sgbWFya3MKICB0aGVtZShwbG90LnRpdGxlPWVsZW1lbnRfdGV4dChjb2xvcj1jb2xvci50aXRsZSwgc2l6ZT0yOCkpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSkgKwogIHRoZW1lKGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpKSArCiAgdGhlbWUoYXhpcy50aXRsZS54PWVsZW1lbnRfdGV4dChzaXplPTgpKSArCiAgdGhlbWUoYXhpcy50aXRsZS55PWVsZW1lbnRfdGV4dChzaXplPTgpKSArCgogICNGb3JtYXQgdGl0bGUgYW5kIGZhY2V0X3dyYXAgdGl0bGUKICB0aGVtZShzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9OCksIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE2LCBjb2xvdXIgPSAiYmxhY2siLCB2anVzdCA9IDEsIGhqdXN0PTApKSsKICAgIAogICMgUGxvdCBtYXJnaW5zCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoLjIsIC4yLCAuMiwgLjUpLCAiY20iKSkKfQpgYGAKCiMjIFBsb3QgdG9wIDIwIHRmLWlkZiB3b3JkcwpgYGB7ciwgZmlnLmhlaWdodD0yLCBmaWcud2lkdGg9NH0KbGlicmFyeShnZ3Bsb3QyKQoKcGxvdF9jb3VudHJ5IDwtIGNvdW50cnlfd29yZHMyICU+JQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUKICBtdXRhdGUod29yZCA9IGZhY3Rvcih3b3JkLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQpKSkpCgpwbG90X2NvdW50cnkgJT4lIAogIHRvcF9uKDIwKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQsIHRmX2lkZiwgZmlsbCA9IGNvdW50cnkpKSArCiAgZ2VvbV9jb2woKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iQWNjZW50IikrCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSsKICBteV90aGVtZSgpKwogIGdndGl0bGUoIk1vc3QgSW1wb3J0YW50IFdvcmRzIHRvIE5hdGlvbmFsIFNlY3VyaXR5IENvdW5jaWwgQ291bnRyaWVzIiwgc3VidGl0bGU9IkFzIGRldGVybWluZWQgYnkgdGYtaWRmIHNjb3JlcyBnZW5lcmF0ZWQgZnJvbSAxOTcwLTIwMTYgVU4gR2VuZXJhbCBEZWJhdGUgc3BlZWNoZXMiKSsKICBsYWJzKHkgPSAidGYtaWRmIHNjb3JlIiwgeD1OVUxMLCBjYXB0aW9uPSJEYXRhIFNvdXJjZTogVW5pdGVkIE5hdGlvbnMgR2VuZXJhbCBEZWJhdGUgQ29ycHVzXG5WaXN1YWxpemF0aW9uIHZpYSBBbGV4IEFsYnJpZ2h0ICh0aGVsaXR0bGVkYXRhc2V0LmNvbSkiKSArCiAgY29vcmRfZmxpcCgpKwogIGdnc2F2ZSgidGZpZGZ0b3RhbC5wbmciLCB3aWR0aCA9IDgsIGhlaWdodCA9IDUsIGRwaSA9IDgwMCkKYGBgClRoZXNlIGFyZSB0aGUgdG9wIDIwIHdvcmRzIGluIHRlcm1zIG9mIHRmLWlkZiBzY29yZXMuIAoKIyMgUGxvdCB0b3AgNSB3b3JkcyBmb3IgZWFjaCBjb3VudHJ5CmBgYHtyfQp0b3A1d29yZHM8LXBsb3RfY291bnRyeSAlPiUgCiAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUKICB0b3Bfbig1KSAKdG9wNXdvcmRzIApgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD00LjV9CnRvcDV3b3JkcyAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JQogIGdncGxvdChhZXMod29yZCwgdGZfaWRmLCBmaWxsID0gY291bnRyeSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSsKICBteV90aGVtZSgpKwogIGxhYnMoeCA9IE5VTEwsIHkgPSAidGYtaWRmIikgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IkFjY2VudCIpKwogIGZhY2V0X3dyYXAofmNvdW50cnksIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZSIpICsKICBteV90aGVtZSgpKwogIGdndGl0bGUoIlRvcCA1IE1vc3QgSW1wb3J0YW50IFdvcmRzIHRvIEVhY2ggTmF0aW9uYWwgU2VjdXJpdHkgQ291bmNpbCBDb3VudHJ5Iiwgc3VidGl0bGU9IkFzIGRldGVybWluZWQgYnkgdGYtaWRmIHNjb3JlcyBnZW5lcmF0ZWQgZnJvbSAxOTcwLTIwMTYgVU4gR2VuZXJhbCBEZWJhdGUgc3BlZWNoZXMiKSsKICBsYWJzKHkgPSAidGYtaWRmIHNjb3JlIiwgeD1OVUxMLCBjYXB0aW9uPSJEYXRhIFNvdXJjZTogVW5pdGVkIE5hdGlvbnMgR2VuZXJhbCBEZWJhdGUgQ29ycHVzXG5WaXN1YWxpemF0aW9uIHZpYSBBbGV4IEFsYnJpZ2h0ICh0aGVsaXR0bGVkYXRhc2V0LmNvbSkiKSArCiAgY29vcmRfZmxpcCgpKwogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKC4yLCAuNiwgLjIsIC40KSwgImNtIikpKwogIGdnc2F2ZSgidGZpZGZfY291bnRyeS5wbmciLCB3aWR0aCA9IDguMzMsIGhlaWdodCA9IDcsIGRwaSA9IDgwMCkKYGBgCldoYXQncyB3aXRoICJ0d2VsdmUiIGZvciBGcmFuY2U/IFdlbGwsIEkgbG9va2VkIGludG8gc29tZSBvZiB0aGUgdXNhZ2VzIGFuZCAiVGhlIFR3ZWx2ZSIgaXMgdXNlZCB0byByZWZlciB0byBFdXJvcGUhIFRoaXMgbWFrZXMgc2Vuc2UgYXMgRnJhbmNlIGFuZCBHcmVhdCBCcml0YWluIHVzZSBpdCB0aHJvdWdob3V0IHRoZWlyIHNwZWVjaGVzLgoKIyBbMl0gU2VudGltZW50IEFuYWx5c2lzCiMgUG9zaXRpdml0eSBzY29yZXMgKGNvbXBhcmluZyAzIGxleGljb25zKQpJIHdhbnQgdG8ga25vdyB3aGljaCBjb3VudHJ5IGlzIHRoZSBtb3N0IHBvc2l0aXZlLiBUaGVyZSBhcmUgdGhyZWUgZGljdGlvbmFyaWVzIGZvciB0aGlzOiBgYmluZ2AsIGBBRklOTmAsIGFuZCBgTlJDYC4gV2Ugd2lsbCBjb21wYXJlIGFsbCB0aHJlZS4KYGBge3J9CmVtb193b3JkcyA8LSBzcGVlY2hlcyAlPiUKICBncm91cF9ieShjb3VudHJ5KSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAKZW1vX3dvcmRzCgojZmluZCB0b3RhbCBudW1iZXIgb2Ygd29yZHMKd29yZHN0b3QgPC0gZW1vX3dvcmRzICU+JQogIGNvdW50KGNvdW50cnkpCndvcmRzdG90JHRvdDwtd29yZHN0b3Qkbgp3b3Jkc3RvdDwtd29yZHN0b3RbLGMoImNvdW50cnkiLCAidG90IildCmBgYApPayBub3cgbGV0J3MgbGluayB0aGVzZSB3b3JkcyBhbmQgY291bnRyaWVzIHVwIHdpdGggdGhlIHRocmVlIGxleGljb24gZGljdGlvbmFyaWVzIG9mIGludGVyZXN0LgoKYGBge3J9CmFmaW5uIDwtIGVtb193b3JkcyAlPiUgCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lIAogIGdyb3VwX2J5KGNvdW50cnkpICU+JSAKICBzdW1tYXJpc2Uoc2VudGltZW50ID0gc3VtKHNjb3JlKSkgJT4lIAogIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKQoKYmluZ19hbmRfbnJjIDwtIGJpbmRfcm93cyhlbW9fd29yZHMgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShtZXRob2QgPSAiQmluZyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGVtb193b3JkcyAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5lZ2F0aXZlIikpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShtZXRob2QgPSAiTlJDIikpICU+JQogIGNvdW50KG1ldGhvZCwgY291bnRyeSwgc2VudGltZW50KSAlPiUKICBzcHJlYWQoc2VudGltZW50LCBuLCBmaWxsID0gMCkgJT4lCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpCmBgYApUaGUgYWJvdmUgaXMgYW4gYWRhcHRlZCBjaHVuayBvZiB0ZXh0IGZyb20gW3RoaXMgcGFnZS5dKGh0dHA6Ly90aWR5dGV4dG1pbmluZy5jb20vc2VudGltZW50Lmh0bWwpCmBgYHtyLCBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD0zfQpiaW5kX3Jvd3MoYWZpbm4sIAogICAgICAgICAgYmluZ19hbmRfbnJjKSAlPiUKICBnZ3Bsb3QoYWVzKGNvdW50cnksIHNlbnRpbWVudCwgZmlsbCA9IGNvdW50cnkpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofm1ldGhvZCwgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlIikrCiAgbXlfdGhlbWUoKSsgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoLjIsIC40LCAuMiwgLjQpLCAiY20iKSkrCiAgZ2d0aXRsZSgiV2hpY2ggQ291bnRyeSBpcyB0aGUgTW9zdCBQb3NpdGl2ZT8iLCBzdWJ0aXRsZT0iQUZJTk4sIEJpbmcsIE5SQyBwb3NpdGl2aXR5IHNjb3JlcyBnZW5lcmF0ZWQgdXNpbmcgMTk3MC0yMDE2IFVOIEdlbmVyYWwgRGViYXRlIHNwZWVjaGVzIikrCiAgbGFicyh5ID0gTlVMTCwgeD1OVUxMLCBjYXB0aW9uPSJcbkRhdGEgU291cmNlOiBVbml0ZWQgTmF0aW9ucyBHZW5lcmFsIERlYmF0ZSBDb3JwdXNcblZpc3VhbGl6YXRpb24gdmlhIEFsZXggQWxicmlnaHQgKHRoZWxpdHRsZWRhdGFzZXQuY29tKSIpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJBY2NlbnQiKSsKICBnZ3NhdmUoImNvdW50cnlfcG9zLnBuZyIsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNSwgZHBpID0gODAwKQpgYGAKCiMgYE5SQ2Agc2NvcmVzCldoYXQgaWYgd2UgbG9va2VkIG1vcmUgaW4gZGVwdGggYXQgdGhlIGBOUkNgIHNlbnRpbWVudCBkaWN0aW9uYXJ5PyBJdCdzIGdvdCBhIGxvbmcgbGlzdCBvZiBzZW50aW1lbnRzISAuLi53YXkgYmV5b25kIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSEKYGBge3J9CmVtb193b3JkcyA8LSBlbW9fd29yZHMgJT4lCiAgICAgICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIikpICU+JQogICAgICAgIGZpbHRlcighaXMubmEoc2VudGltZW50KSkgJT4lCiAgICAgICAgZmlsdGVyKCFpcy5uYShjb3VudHJ5KSkgJT4lCiAgICAgICAgY291bnQoY291bnRyeSwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkKI2JyaW5nIGluIHRvdGFscyBzbyB3ZSBjYW4gbWFrZSB0aGVzZSBwZXJjZW50YWdlcwplbW9fd29yZHMgPC0gbWVyZ2UoZW1vX3dvcmRzLCB3b3Jkc3RvdCwgYnk9ImNvdW50cnkiKQplbW9fd29yZHMkbnBlcmMgPC0gZW1vX3dvcmRzJG4vZW1vX3dvcmRzJHRvdAplbW9fd29yZHMKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD0yLCBmaWcud2lkdGg9M30KbXkuY29scyA8LSBicmV3ZXIucGFsKDEwLCAiU2V0MyIpCm15LmNvbHNbOV0gPC0gImdyZXk2MCIKCmVtb193b3JkcyAlPiUgCiAgZ2dwbG90KGFlcyh4PWNvdW50cnksIHk9bnBlcmMsIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpKwogIG15X3RoZW1lKCkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW15LmNvbHMpKwogIG15X3RoZW1lKCkrIAogIGdndGl0bGUoIkNvdW50cmllcyBoYXZlIGZlZWxpbmdzIHRvbyEiLCBzdWJ0aXRsZT0iTlJDIHNlbnRpbWVudCB3b3JkIHBlcmNlbnRhZ2VzIGdlbmVyYXRlZCBmcm9tIDE5NzAtMjAxNiBVTiBHZW5lcmFsIERlYmF0ZSBzcGVlY2hlcyIpKwogIGxhYnMoeSA9ICJQZXJjZW50YWdlIG9mIGVtb3Rpb24tcmVsYXRlZCB3b3JkcyIsIHg9TlVMTCwgY2FwdGlvbj0iXG5EYXRhIFNvdXJjZTogVW5pdGVkIE5hdGlvbnMgR2VuZXJhbCBEZWJhdGUgQ29ycHVzXG5WaXN1YWxpemF0aW9uIHZpYSBBbGV4IEFsYnJpZ2h0ICh0aGVsaXR0bGVkYXRhc2V0LmNvbSkiKSArCiAgY29vcmRfZmxpcCgpKwogIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKC4yLCAuMiwgLjIsIC4yKSwgImNtIikpKwogIGdnc2F2ZSgiZmVlbGluZ3MxLnBuZyIsIHdpZHRoID0gOCwgaGVpZ2h0ID0gNSwgZHBpID0gODAwKQpgYGAKYGBge3IsIGZpZy5oZWlnaHQ9MiwgZmlnLndpZHRoPTN9CmVtb193b3JkcyAlPiUgCiAgZ2dwbG90KGFlcyh4PWNvdW50cnksIHk9bnBlcmMsIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbj0iZG9kZ2UiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHBlcmNlbnQpKwogIG15X3RoZW1lKCkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW15LmNvbHMpKwogIG15X3RoZW1lKCkrCiAgZ2d0aXRsZSgiQ291bnRyaWVzIGhhdmUgZmVlbGluZ3MgdG9vISIsIHN1YnRpdGxlPSJOUkMgc2VudGltZW50IHdvcmQgcGVyY2VudGFnZXMgZ2VuZXJhdGVkIGZyb20gMTk3MC0yMDE2IFVOIEdlbmVyYWwgRGViYXRlIFNwZWVjaGVzIikrCiAgbGFicyh5ID0gIlBlcmNlbnRhZ2Ugb2YgZW1vdGlvbi1yZWxhdGVkIHdvcmRzIiwgeD1OVUxMLCBjYXB0aW9uPSJcbkRhdGEgU291cmNlOiBVbml0ZWQgTmF0aW9ucyBHZW5lcmFsIERlYmF0ZSBDb3JwdXNcblZpc3VhbGl6YXRpb24gdmlhIEFsZXggQWxicmlnaHQgKHRoZWxpdHRsZWRhdGFzZXQuY29tKSIpICsKICBjb29yZF9mbGlwKCkrCiAgdGhlbWUocGxvdC5tYXJnaW4gPSB1bml0KGMoLjIsIC4yLCAuMiwgLjMpLCAiY20iKSkrCiAgZ2dzYXZlKCJmZWVsaW5nczIucG5nIiwgd2lkdGggPSA4LCBoZWlnaHQgPSA1LCBkcGkgPSA4MDApCmBgYApJbnRlcmVzdGluZyBob3cgc2ltaWxhciB0aGUgYnJlYWstZG93bnMgYXJlIGJ5IGVtb3Rpb24gZm9yIGFsbCB0aGUgY291bnRyaWVzLiBJIHdvbmRlciBob3cgc3BlZWNoZXMgbGlrZSB0aGlzIGNvbXBhcmUgdG8gYm9va3Mgb3IgYXJ0aWNsZXMuIEknZCBpbWFnaW5lIGluIHNvbWUgd2F5cyB0aGV5IGFyZSBtb3JlIGRyYW1hdGljL2Vtb3Rpb25hbCwgYXMgdGhleSBhcmUgc3BlZWNoZXMgb24gYmVoYWxmIG9mIGEgY291bnRyeSBvbiBhbiBpbnRlcm5hdGlvbmFsIHN0YWdlLiBQZXJoYXBzIHRoYXQncyBzb21ldGhpbmcgdG8gbG9vayBpbnRvIGFub3RoZXIgZGF5LgoKIyBUaGUgRW5kIQo=