The Harry Potter R package (harrypotter) on GitHub contains the text for all seven books in the Harry Potter series, by JK Rowling. Below, I have included the code required to connect to the harrypotter package using devtools, as well as the other packages required for the basic text analytics I will cover in this workbook. I created this workbook in good fun (and for practice). A few code snips were taken from the excellent tutorial at http://uc-r.github.io/sentiment_analysis, and others were added by me. The methods used in this workbook are all based on the R tidyverse, and the following is an excellent (FREE) text for beginners:

http://tidytextmining.com/index.html

Let’s get started. Make sure all of the packages listed below are installed in R, and then run the following code to load them into your library.

library(wordcloud)
library(devtools)
library(tidyverse)      
library(stringr)        
library(tidytext)
library(dplyr)
library(reshape2)
library(igraph)
library(ggraph)
if (packageVersion("devtools") < 1.6) {
  install.packages("devtools")
}
devtools::install_github("bradleyboehmke/harrypotter")
library(harrypotter)

After downloading the data from the harrypotter package on github, we can do a bit of data shaping. The code below places all of the books in the Harry Potter series into a tibble. A tibble is kind of like a data frame, but it has special features that make it optimal for use in the tidyverse. After creating our tibble, we tokenize the text into single words, strip away all punctuation and capitalization, and add columns to the tibble for the book and chapter. In the resulting tibble, you can see each word from the Harry Potter series, and the book/chapter in which it appears.

titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")
books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
              goblet_of_fire, order_of_the_phoenix, half_blood_prince,
              deathly_hallows)
##Each book is an array in which each value in the array is a chapter 
series <- tibble()
for(i in seq_along(titles)) {
  
  temp <- tibble(chapter = seq_along(books[[i]]),
                  text = books[[i]]) %>%
    unnest_tokens(word, text) %>%
    ##Here we tokenize each chapter into words
    mutate(book = titles[i]) %>%
    select(book, everything())
  
  series <- rbind(series, temp)
}
# set factor to keep books in order of publication
series$book <- factor(series$book, levels = rev(titles))
series

We can get simple counts for each word using the count function. The word “the” occurs 51593 times in the Harry Potter series.

series %>% count(word, sort = TRUE)

Many of the words in the top ten most frequently appearing words are stop-words such as “the”, “and”, “to”, etc., so let’s discard those for now. Below, you can see a word cloud showing the most frequently occurring non-stop words in the series. The cloud contains the top 100 most frequently occurring words, and the larger a word appears in the cloud, the more frequently that word occurred in the text. It comes as no surprise to Harry Potter readers that most of the largest words in the cloud are names like “Harry”, “Ron” and “Hermione”.

series$book <- factor(series$book, levels = rev(titles))
series %>% 
  anti_join(stop_words) %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100))

Now we can begin the sentiment analysis. For this portion, we will continue working with the text that contains stop-words. The basic motivation behind sentiment analysis is to assess how positive or negative text is, based on a dictionary of words that have been previously classified as positive or negative. This type of dictionary is called a sentiment lexicon. The tidyverse has several built in lexicons for sentiment analysis, but for this example we will stick with ‘nrc’ and ‘bing’. The ‘nrc’ is a more advanced lexicon that categorizes words into several sentiment categories - sadness, anger, positive, negative, trust, etc. A single word in this lexicon may fall into multiple categories. Using the following code, we can get counts for the number of words in the Harry Potter series that fall into each of the categories addressed by ‘nrc’. In the output, you can see that there were 56579 words in the series that are classified as ‘negative’ by the ‘nrc’ lexicon. Overall, it looks like there are more negative words in the series than positive words. There are also a lot of words related to anger and sadness.

series %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentiment)) %>%
  count(sentiment, sort = TRUE)

The ‘bing’ lexicon only classifies words as positive or negative. Below you can see that this lexicon picked up 39503 negative words in the Harry Potter series, and 29066 positive words.

series %>%
  right_join(get_sentiments("bing")) %>%
  filter(!is.na(sentiment)) %>%
  count(sentiment, sort = TRUE)

Similarly to the word cloud we created above, we can use the ‘bing’ lexicon to make a comparison cloud. This cloud displays the 50 most frequently occurring words in the series that were categorized by ‘bing’, and color-codes them based on negative or positive sentiment. You’ll notice that words like “Harry”“,”Hermione" and “Ron” don’t appear in this cloud, because character names are not classified as positive or negative in ‘bing’.

series %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("#F8766D", "#00BFC4"),
                   max.words = 50)

Now, let’s see what the comparison cloud looks like with stop-words removed temporarily.

series %>%
  anti_join(stop_words) %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("#F8766D", "#00BFC4"),
                   max.words = 50)

In the following code chunk, each book text is split into groups of 500 words, and counts for the number of positive and negative words in each group (based on the ‘bing’ lexicon) are calculated. Then we subtract the number of negative words from the number of positive words in each group. For example, if there were 341 positive words in a group and 159 negative words in the same group, the sentiment score for the group would be 182 (a positive sentiment score). We calculate this sentiment score for each 500 word group within each book in the series. Using ggplot, we create bar charts for each book in the series that demonstrate how the sentiment score for the text groups changes as time passes in the series. Overall, the sentiment of the Harry Potter series appears to be negative.

Challenges: How do these plots change if you go back and leave all of the stop-words in the tibble? Does the size of the text groups (500 words vs. 1000 words) affect the analysis?

As a next step, one might look at the maximum sentiment score and the minimum sentiment score for each book to see what text groups produced the extreme scores.

series %>%
  group_by(book) %>% 
  mutate(word_count = 1:n(),
         index = word_count %/% 500 + 1) %>% 
  inner_join(get_sentiments("bing")) %>%
  count(book, index = index , sentiment) %>%
  ungroup() %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative,
         book = factor(book, levels = titles)) %>%
  ggplot(aes(index, sentiment, fill = book)) +
  geom_bar(alpha = 0.5, stat = "identity", show.legend = FALSE) +
  facet_wrap(~ book, ncol = 2, scales = "free_x")

Using single words as tokens for sentiment analysis can be less than ideal. This is because nearby words add context - in particular, negations make the analysis tricky. For example, the word “good” is a positive word. However, “My day was not good” has a negative sentiment, despite the presence of the word “good”. A better example for the Harry Potter series would be that “magic” is considered to be a positive word, but “dark magic” would be have a negative meaning. For further analysis of our Harry Potter text, let’s look at pairs of words (bigrams). A bigram is a pair of words that appear consecutively in a text. For example, if we look at the sentence “I ate purple grapes”, the bigrams we can extract would be (I, ate), (ate, purple), and (purple, grapes). In the following code chunk, I repeat the process of shaping the text data from the beginning of the document, but this time I specify that bigrams should be used to tokenize the text rather than single words.

series <- tibble()
for(i in seq_along(titles)) {
  
  temp <- tibble(chapter = seq_along(books[[i]]),
                  text = books[[i]]) %>%
    unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
    ##Here we tokenize each chapter into bigrams
    mutate(book = titles[i]) %>%
    select(book, everything())
  
  series <- rbind(series, temp)
}
# set factor to keep books in order of publication
series$book <- factor(series$book, levels = rev(titles))
series

Again, we can use the count function to find the most common bigrams in the series.

series %>%
  count(bigram, sort = TRUE)

As we saw with the single words, most of the most common bigrams contain stop-words. Let’s remove those from our bigram tibble.

bigrams_separated <- series %>%
  separate(bigram, c("word1", "word2"), sep = " ")
bigrams_filtered <- bigrams_separated %>%
  filter(!word1 %in% stop_words$word) %>%
  filter(!word2 %in% stop_words$word)
# new bigram counts:
bigrams_united <- bigrams_filtered %>%
  unite(bigram, word1, word2, sep = " ")
bigrams_united %>% 
    count(bigram, sort = TRUE)

Now that we have removed the stop-words, we can see that the most frequently occurring bigram in the series is “Professor McGonagall”. The only bigrams in the top ten that don’t contain character names are “Death Eaters”, “Invisibility Cloak” and “Dark Arts”.

Now, let’s use our bigrams to practice tf-idf (term frequency -inverse document frequency). In a nutshell, tf-idf is an analysis that seeks to identify how common a word is in a particular text, given how often it occurs in a group of texts. For example, Professor Lupin was a very prominent character in “The Prisoner of Azkaban”“, but not so much in the other books (and in the other books, he was not a professor). A person who had not read all of the books could determine this by simply counting the number of times the name”Professor Lupin" occurs in “The Prisoner of Azkaban” and comparing that number to the frequency of that bigram in the rest of the books in the series. To quantify this idea, the term frequency (the number of times a token appears in a document divided by the total number of tokens in the document) is multiplied by the inverse document frequency (the total number of documents divided by the number of documents containing the token). The chart below displays the ten bigrams with the highest tf-idf scores among the seven books in the series. “Professor Umbridge”, has the highest tf-idf score relative to “The Order of the Phoenix”. Any Harry Potter lover can tell you that we first meet Professor Umbridge in “The Order of the Phoenix”, in which and she plays a major role. In the other books in the series, her role ranges from small to non-existent. Thus, it makes sense that “Professor Umbridge” has a relatively high tf-idf score. Beneath the chart, I have created a visual for the bigrams with the highest tf-idf scores.

bigram_tf_idf <- bigrams_united %>%
  count(book, bigram) %>%
  bind_tf_idf(bigram, book, n) %>%
  arrange(desc(tf_idf))
bigram_tf_idf
plot_potter<- bigram_tf_idf %>%
  arrange(desc(tf_idf)) %>%
  mutate(bigram = factor(bigram, levels = rev(unique(bigram))))
plot_potter %>% 
  top_n(20) %>%
  ggplot(aes(bigram, tf_idf, fill = book)) +
  geom_col() +
  labs(x = NULL, y = "tf-idf") +
  coord_flip()

Now, to get an idea of how our sentiment analysis was affected by negations, let’s find all the bigrams that have the word “not” as the first word in the bigram.

bigrams_separated %>%
  filter(word1 == "not") %>%
  count(word1, word2, sort = TRUE)

The first ten bigrams with “not” as the first word are boring, so let’s remove stop-words from the “word2” column.

bigrams_separated <- bigrams_separated %>%
  filter(word1 == "not") %>%
  filter(!word2 %in% stop_words$word)%>%
  count(word1, word2, sort = TRUE)
bigrams_separated
BING <- get_sentiments("bing")
not_words <- bigrams_separated %>%
  filter(word1 == "not") %>%
  filter(!word2 %in% stop_words$word)%>%
  inner_join(BING, by = c(word2 = "word")) %>%
  ungroup()
not_words

Just looking at the top ten words in the list, we can see that most of the words that are preceded by “not” in the series, have negative sentiment. This means, we may be over estimating the negative sentiment present in the text. Of course, there are many other negation words such as “never”, “no”, etc. One could explore all of these possible negation words to get a better idea of how negation is affecting the sentiment analysis.

We can also create a graph that connects our most frequently occurring words with each other. Looking at the graph below, we can see a couple of larger clusters that give some context to what the series might be about. For example, there is a cluster with the word “professor” in the center, with several other words connected to it such as“McGonagall” and “Lupin”.

bigram_graph <- bigram_counts %>%
  filter(n > 70) %>%
  graph_from_data_frame()
bigram_graph
IGRAPH b62f31f DN-- 61 43 -- 
+ attr: name (v/c), n (e/n)
+ edges from b62f31f (vertex names):
 [1] professor   ->mcgonagall uncle       ->vernon     harry       ->potter     death       ->eaters     harry       ->looked    
 [6] harry       ->ron        aunt        ->petunia    invisibility->cloak      professor   ->trelawney  dark        ->arts      
[11] professor   ->umbridge   death       ->eater      entrance    ->hall       madam       ->pomfrey    dark        ->lord      
[16] professor   ->dumbledore daily       ->prophet    lord        ->voldemort  harry       ->heard      professor   ->lupin     
[21] mad         ->eye        hospital    ->wing       draco       ->malfoy     harry       ->harry      madame      ->maxime    
[26] prime       ->minister   house       ->elf        professor   ->snape      harry       ->stared     rita        ->skeeter   
[31] privet      ->drive      ron         ->looked     ron         ->hermione   hermione    ->looked     front       ->door      
[36] professor   ->flitwick   gryffindor  ->tower      albus       ->dumbledore harry       ->quickly    told        ->harry     
+ ... omitted several edges
set.seed(2017)
ggraph(bigram_graph, layout = "fr") +
  geom_edge_link() +
  geom_node_point() +
  geom_node_text(aes(label = name), vjust = 1, hjust = 1)

This concludes our tutorial on basic text analytics with the Harry Potter series. This is by no means a comprehensive analysis, but it should have demonstrated some of the basic facets of text mining with the tidyverse in R.

LS0tDQp0aXRsZTogIkhhcnJ5IFBvdHRlciBTZW50aW1lbnQgQW5hbHlzaXMgZm9yIEJlZ2lubmVycyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50OiBkZWZhdWx0DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICB3b3JkX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KVGhlIEhhcnJ5IFBvdHRlciBSIHBhY2thZ2UgKGhhcnJ5cG90dGVyKSBvbiBHaXRIdWIgY29udGFpbnMgdGhlIHRleHQgZm9yIGFsbCBzZXZlbiBib29rcyBpbiB0aGUgSGFycnkgUG90dGVyIHNlcmllcywgYnkgSksgUm93bGluZy4gIEJlbG93LCBJIGhhdmUgaW5jbHVkZWQgdGhlIGNvZGUgcmVxdWlyZWQgdG8gY29ubmVjdCB0byB0aGUgaGFycnlwb3R0ZXIgcGFja2FnZSB1c2luZyBkZXZ0b29scywgYXMgd2VsbCBhcyB0aGUgb3RoZXIgcGFja2FnZXMgcmVxdWlyZWQgZm9yIHRoZSBiYXNpYyB0ZXh0IGFuYWx5dGljcyBJICB3aWxsIGNvdmVyIGluIHRoaXMgd29ya2Jvb2suIEkgY3JlYXRlZCB0aGlzIHdvcmtib29rIGluIGdvb2QgZnVuIChhbmQgZm9yIHByYWN0aWNlKS4gIEEgZmV3IGNvZGUgc25pcHMgd2VyZSB0YWtlbiBmcm9tIHRoZSBleGNlbGxlbnQgdHV0b3JpYWwgYXQgaHR0cDovL3VjLXIuZ2l0aHViLmlvL3NlbnRpbWVudF9hbmFseXNpcywgYW5kIG90aGVycyB3ZXJlIGFkZGVkIGJ5IG1lLiAgVGhlIG1ldGhvZHMgdXNlZCBpbiB0aGlzIHdvcmtib29rIGFyZSBhbGwgYmFzZWQgb24gdGhlIFIgdGlkeXZlcnNlLCBhbmQgdGhlIGZvbGxvd2luZyBpcyBhbiBleGNlbGxlbnQgKEZSRUUpIHRleHQgZm9yIGJlZ2lubmVyczoNCg0KaHR0cDovL3RpZHl0ZXh0bWluaW5nLmNvbS9pbmRleC5odG1sDQoNCkxldCdzIGdldCBzdGFydGVkLiAgTWFrZSBzdXJlIGFsbCBvZiB0aGUgcGFja2FnZXMgbGlzdGVkIGJlbG93IGFyZSBpbnN0YWxsZWQgaW4gUiwgYW5kIHRoZW4gcnVuIHRoZSBmb2xsb3dpbmcgY29kZSB0byBsb2FkIHRoZW0gaW50byB5b3VyIGxpYnJhcnkuIA0KDQpgYGB7ciwgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpsaWJyYXJ5KGRldnRvb2xzKQ0KbGlicmFyeSh0aWR5dmVyc2UpICAgICAgDQpsaWJyYXJ5KHN0cmluZ3IpICAgICAgICANCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkoaWdyYXBoKQ0KbGlicmFyeShnZ3JhcGgpDQoNCmlmIChwYWNrYWdlVmVyc2lvbigiZGV2dG9vbHMiKSA8IDEuNikgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpDQp9DQoNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYnJhZGxleWJvZWhta2UvaGFycnlwb3R0ZXIiKQ0KbGlicmFyeShoYXJyeXBvdHRlcikNCmBgYA0KDQpBZnRlciBkb3dubG9hZGluZyB0aGUgZGF0YSBmcm9tIHRoZSBoYXJyeXBvdHRlciBwYWNrYWdlIG9uIGdpdGh1Yiwgd2UgY2FuIGRvIGEgYml0IG9mIGRhdGEgc2hhcGluZy4gIFRoZSBjb2RlIGJlbG93IHBsYWNlcyBhbGwgb2YgdGhlIGJvb2tzIGluIHRoZSBIYXJyeSBQb3R0ZXIgc2VyaWVzIGludG8gYSB0aWJibGUuIEEgdGliYmxlIGlzIGtpbmQgb2YgbGlrZSBhIGRhdGEgZnJhbWUsIGJ1dCBpdCBoYXMgc3BlY2lhbCBmZWF0dXJlcyB0aGF0IG1ha2UgaXQgb3B0aW1hbCBmb3IgdXNlIGluIHRoZSB0aWR5dmVyc2UuIEFmdGVyIGNyZWF0aW5nIG91ciB0aWJibGUsIHdlIHRva2VuaXplIHRoZSB0ZXh0IGludG8gc2luZ2xlIHdvcmRzLCBzdHJpcCBhd2F5IGFsbCBwdW5jdHVhdGlvbiBhbmQgY2FwaXRhbGl6YXRpb24sIGFuZCBhZGQgY29sdW1ucyB0byB0aGUgdGliYmxlIGZvciB0aGUgYm9vayBhbmQgY2hhcHRlci4gSW4gdGhlIHJlc3VsdGluZyB0aWJibGUsIHlvdSBjYW4gc2VlIGVhY2ggd29yZCBmcm9tIHRoZSBIYXJyeSBQb3R0ZXIgc2VyaWVzLCBhbmQgdGhlIGJvb2svY2hhcHRlciBpbiB3aGljaCBpdCBhcHBlYXJzLiANCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQp0aXRsZXMgPC0gYygiUGhpbG9zb3BoZXIncyBTdG9uZSIsICJDaGFtYmVyIG9mIFNlY3JldHMiLCAiUHJpc29uZXIgb2YgQXprYWJhbiIsDQogICAgICAgICAgICAiR29ibGV0IG9mIEZpcmUiLCAiT3JkZXIgb2YgdGhlIFBob2VuaXgiLCAiSGFsZi1CbG9vZCBQcmluY2UiLA0KICAgICAgICAgICAgIkRlYXRobHkgSGFsbG93cyIpDQoNCmJvb2tzIDwtIGxpc3QocGhpbG9zb3BoZXJzX3N0b25lLCBjaGFtYmVyX29mX3NlY3JldHMsIHByaXNvbmVyX29mX2F6a2FiYW4sDQogICAgICAgICAgICAgIGdvYmxldF9vZl9maXJlLCBvcmRlcl9vZl90aGVfcGhvZW5peCwgaGFsZl9ibG9vZF9wcmluY2UsDQogICAgICAgICAgICAgIGRlYXRobHlfaGFsbG93cykNCg0KIyNFYWNoIGJvb2sgaXMgYW4gYXJyYXkgaW4gd2hpY2ggZWFjaCB2YWx1ZSBpbiB0aGUgYXJyYXkgaXMgYSBjaGFwdGVyIA0Kc2VyaWVzIDwtIHRpYmJsZSgpDQoNCmZvcihpIGluIHNlcV9hbG9uZyh0aXRsZXMpKSB7DQogIA0KICB0ZW1wIDwtIHRpYmJsZShjaGFwdGVyID0gc2VxX2Fsb25nKGJvb2tzW1tpXV0pLA0KICAgICAgICAgICAgICAgICAgdGV4dCA9IGJvb2tzW1tpXV0pICU+JQ0KICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lDQogICAgIyNIZXJlIHdlIHRva2VuaXplIGVhY2ggY2hhcHRlciBpbnRvIHdvcmRzDQogICAgbXV0YXRlKGJvb2sgPSB0aXRsZXNbaV0pICU+JQ0KICAgIHNlbGVjdChib29rLCBldmVyeXRoaW5nKCkpDQogIA0KICBzZXJpZXMgPC0gcmJpbmQoc2VyaWVzLCB0ZW1wKQ0KfQ0KDQojIHNldCBmYWN0b3IgdG8ga2VlcCBib29rcyBpbiBvcmRlciBvZiBwdWJsaWNhdGlvbg0Kc2VyaWVzJGJvb2sgPC0gZmFjdG9yKHNlcmllcyRib29rLCBsZXZlbHMgPSByZXYodGl0bGVzKSkNCnNlcmllcw0KDQpgYGANCg0KV2UgY2FuIGdldCBzaW1wbGUgY291bnRzIGZvciBlYWNoIHdvcmQgdXNpbmcgdGhlIGNvdW50IGZ1bmN0aW9uLiAgVGhlIHdvcmQgInRoZSIgb2NjdXJzIDUxNTkzIHRpbWVzIGluIHRoZSBIYXJyeSBQb3R0ZXIgc2VyaWVzLg0KYGBge3J9DQpzZXJpZXMgJT4lIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNCk1hbnkgb2YgdGhlIHdvcmRzIGluIHRoZSB0b3AgdGVuIG1vc3QgZnJlcXVlbnRseSBhcHBlYXJpbmcgd29yZHMgYXJlIHN0b3Atd29yZHMgc3VjaCBhcyAidGhlIiwgImFuZCIsICJ0byIsIGV0Yy4sIHNvIGxldCdzIGRpc2NhcmQgdGhvc2UgZm9yIG5vdy4gIEJlbG93LCB5b3UgY2FuIHNlZSBhIHdvcmQgY2xvdWQgc2hvd2luZyB0aGUgbW9zdCBmcmVxdWVudGx5IG9jY3VycmluZyBub24tc3RvcCB3b3JkcyBpbiB0aGUgc2VyaWVzLiBUaGUgY2xvdWQgY29udGFpbnMgdGhlIHRvcCAxMDAgbW9zdCBmcmVxdWVudGx5IG9jY3VycmluZyB3b3JkcywgYW5kIHRoZSBsYXJnZXIgYSB3b3JkIGFwcGVhcnMgaW4gdGhlIGNsb3VkLCB0aGUgbW9yZSBmcmVxdWVudGx5IHRoYXQgd29yZCBvY2N1cnJlZCBpbiB0aGUgdGV4dC4gSXQgY29tZXMgYXMgbm8gc3VycHJpc2UgdG8gSGFycnkgUG90dGVyIHJlYWRlcnMgdGhhdCBtb3N0IG9mIHRoZSBsYXJnZXN0IHdvcmRzIGluIHRoZSBjbG91ZCBhcmUgbmFtZXMgbGlrZSAiSGFycnkiLCAiUm9uIiBhbmQgIkhlcm1pb25lIi4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzZXJpZXMkYm9vayA8LSBmYWN0b3Ioc2VyaWVzJGJvb2ssIGxldmVscyA9IHJldih0aXRsZXMpKQ0Kc2VyaWVzICU+JSANCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JQ0KICBjb3VudCh3b3JkKSAlPiUNCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMTAwKSkNCmBgYA0KDQpOb3cgd2UgY2FuIGJlZ2luIHRoZSBzZW50aW1lbnQgYW5hbHlzaXMuIEZvciB0aGlzIHBvcnRpb24sIHdlIHdpbGwgY29udGludWUgd29ya2luZyB3aXRoIHRoZSB0ZXh0IHRoYXQgY29udGFpbnMgc3RvcC13b3Jkcy4gIFRoZSBiYXNpYyBtb3RpdmF0aW9uIGJlaGluZCBzZW50aW1lbnQgYW5hbHlzaXMgaXMgdG8gYXNzZXNzIGhvdyBwb3NpdGl2ZSBvciBuZWdhdGl2ZSB0ZXh0IGlzLCBiYXNlZCBvbiBhIGRpY3Rpb25hcnkgb2Ygd29yZHMgdGhhdCBoYXZlIGJlZW4gcHJldmlvdXNseSBjbGFzc2lmaWVkIGFzIHBvc2l0aXZlIG9yIG5lZ2F0aXZlLiAgVGhpcyB0eXBlIG9mIGRpY3Rpb25hcnkgaXMgY2FsbGVkIGEgc2VudGltZW50IGxleGljb24uICBUaGUgdGlkeXZlcnNlIGhhcyBzZXZlcmFsIGJ1aWx0IGluIGxleGljb25zIGZvciBzZW50aW1lbnQgYW5hbHlzaXMsIGJ1dCBmb3IgdGhpcyBleGFtcGxlIHdlIHdpbGwgc3RpY2sgd2l0aCAnbnJjJyBhbmQgJ2JpbmcnLiAgVGhlICducmMnIGlzIGEgbW9yZSBhZHZhbmNlZCBsZXhpY29uIHRoYXQgY2F0ZWdvcml6ZXMgd29yZHMgaW50byBzZXZlcmFsIHNlbnRpbWVudCBjYXRlZ29yaWVzIC0gc2FkbmVzcywgYW5nZXIsIHBvc2l0aXZlLCBuZWdhdGl2ZSwgdHJ1c3QsIGV0Yy4gIEEgc2luZ2xlIHdvcmQgaW4gdGhpcyBsZXhpY29uIG1heSBmYWxsIGludG8gbXVsdGlwbGUgY2F0ZWdvcmllcy4gIFVzaW5nIHRoZSBmb2xsb3dpbmcgY29kZSwgd2UgY2FuIGdldCBjb3VudHMgZm9yIHRoZSBudW1iZXIgb2Ygd29yZHMgaW4gdGhlIEhhcnJ5IFBvdHRlciBzZXJpZXMgdGhhdCBmYWxsIGludG8gZWFjaCBvZiB0aGUgY2F0ZWdvcmllcyBhZGRyZXNzZWQgYnkgJ25yYycuICBJbiB0aGUgb3V0cHV0LCB5b3UgY2FuIHNlZSB0aGF0IHRoZXJlIHdlcmUgNTY1Nzkgd29yZHMgaW4gdGhlIHNlcmllcyB0aGF0IGFyZSBjbGFzc2lmaWVkIGFzICduZWdhdGl2ZScgYnkgdGhlICducmMnIGxleGljb24uIE92ZXJhbGwsIGl0IGxvb2tzIGxpa2UgdGhlcmUgYXJlIG1vcmUgbmVnYXRpdmUgd29yZHMgaW4gdGhlIHNlcmllcyB0aGFuIHBvc2l0aXZlIHdvcmRzLiAgVGhlcmUgYXJlIGFsc28gYSBsb3Qgb2Ygd29yZHMgcmVsYXRlZCB0byBhbmdlciBhbmQgc2FkbmVzcy4gIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNlcmllcyAlPiUNCiAgcmlnaHRfam9pbihnZXRfc2VudGltZW50cygibnJjIikpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHNlbnRpbWVudCkpICU+JQ0KICBjb3VudChzZW50aW1lbnQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNClRoZSAnYmluZycgbGV4aWNvbiBvbmx5IGNsYXNzaWZpZXMgd29yZHMgYXMgcG9zaXRpdmUgb3IgbmVnYXRpdmUuICBCZWxvdyB5b3UgY2FuIHNlZSB0aGF0IHRoaXMgbGV4aWNvbiBwaWNrZWQgdXAgMzk1MDMgbmVnYXRpdmUgd29yZHMgaW4gdGhlIEhhcnJ5IFBvdHRlciBzZXJpZXMsIGFuZCAyOTA2NiBwb3NpdGl2ZSB3b3Jkcy4gDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2VyaWVzICU+JQ0KICByaWdodF9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHNlbnRpbWVudCkpICU+JQ0KICBjb3VudChzZW50aW1lbnQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNClNpbWlsYXJseSB0byB0aGUgd29yZCBjbG91ZCB3ZSBjcmVhdGVkIGFib3ZlLCB3ZSBjYW4gdXNlIHRoZSAnYmluZycgbGV4aWNvbiB0byBtYWtlIGEgY29tcGFyaXNvbiBjbG91ZC4gIFRoaXMgY2xvdWQgZGlzcGxheXMgdGhlIDUwIG1vc3QgZnJlcXVlbnRseSBvY2N1cnJpbmcgd29yZHMgaW4gdGhlIHNlcmllcyB0aGF0IHdlcmUgY2F0ZWdvcml6ZWQgYnkgJ2JpbmcnLCBhbmQgY29sb3ItY29kZXMgdGhlbSBiYXNlZCBvbiBuZWdhdGl2ZSBvciBwb3NpdGl2ZSBzZW50aW1lbnQuICBZb3UnbGwgbm90aWNlIHRoYXQgd29yZHMgbGlrZSAiSGFycnkiIiwgIkhlcm1pb25lIiBhbmQgIlJvbiIgZG9uJ3QgYXBwZWFyIGluIHRoaXMgY2xvdWQsIGJlY2F1c2UgY2hhcmFjdGVyIG5hbWVzIGFyZSBub3QgY2xhc3NpZmllZCBhcyBwb3NpdGl2ZSBvciBuZWdhdGl2ZSBpbiAnYmluZycuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNlcmllcyAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQogIGFjYXN0KHdvcmQgfiBzZW50aW1lbnQsIHZhbHVlLnZhciA9ICJuIiwgZmlsbCA9IDApICU+JQ0KICBjb21wYXJpc29uLmNsb3VkKGNvbG9ycyA9IGMoIiNGODc2NkQiLCAiIzAwQkZDNCIpLA0KICAgICAgICAgICAgICAgICAgIG1heC53b3JkcyA9IDUwKQ0KYGBgDQpOb3csIGxldCdzIHNlZSB3aGF0IHRoZSBjb21wYXJpc29uIGNsb3VkIGxvb2tzIGxpa2Ugd2l0aCBzdG9wLXdvcmRzIHJlbW92ZWQgdGVtcG9yYXJpbHkuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNlcmllcyAlPiUNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lDQogIGNvbXBhcmlzb24uY2xvdWQoY29sb3JzID0gYygiI0Y4NzY2RCIsICIjMDBCRkM0IiksDQogICAgICAgICAgICAgICAgICAgbWF4LndvcmRzID0gNTApDQpgYGANCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCBlYWNoIGJvb2sgdGV4dCBpcyBzcGxpdCBpbnRvIGdyb3VwcyBvZiA1MDAgd29yZHMsIGFuZCBjb3VudHMgZm9yIHRoZSBudW1iZXIgb2YgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmRzIGluIGVhY2ggZ3JvdXAgKGJhc2VkIG9uIHRoZSAnYmluZycgbGV4aWNvbikgYXJlIGNhbGN1bGF0ZWQuICBUaGVuIHdlIHN1YnRyYWN0IHRoZSBudW1iZXIgb2YgbmVnYXRpdmUgd29yZHMgZnJvbSB0aGUgbnVtYmVyIG9mIHBvc2l0aXZlIHdvcmRzIGluIGVhY2ggZ3JvdXAuICBGb3IgZXhhbXBsZSwgaWYgdGhlcmUgd2VyZSAzNDEgcG9zaXRpdmUgd29yZHMgaW4gYSBncm91cCBhbmQgMTU5IG5lZ2F0aXZlIHdvcmRzIGluIHRoZSBzYW1lIGdyb3VwLCB0aGUgc2VudGltZW50IHNjb3JlIGZvciB0aGUgZ3JvdXAgd291bGQgYmUgMTgyIChhIHBvc2l0aXZlIHNlbnRpbWVudCBzY29yZSkuIFdlIGNhbGN1bGF0ZSB0aGlzIHNlbnRpbWVudCBzY29yZSBmb3IgZWFjaCA1MDAgd29yZCBncm91cCB3aXRoaW4gZWFjaCBib29rIGluIHRoZSBzZXJpZXMuICBVc2luZyBnZ3Bsb3QsIHdlIGNyZWF0ZSBiYXIgY2hhcnRzIGZvciBlYWNoIGJvb2sgaW4gdGhlIHNlcmllcyB0aGF0IGRlbW9uc3RyYXRlIGhvdyB0aGUgc2VudGltZW50IHNjb3JlIGZvciB0aGUgdGV4dCBncm91cHMgY2hhbmdlcyBhcyB0aW1lIHBhc3NlcyBpbiB0aGUgc2VyaWVzLiBPdmVyYWxsLCB0aGUgc2VudGltZW50IG9mIHRoZSBIYXJyeSBQb3R0ZXIgc2VyaWVzIGFwcGVhcnMgdG8gYmUgbmVnYXRpdmUuIA0KDQpDaGFsbGVuZ2VzOiBIb3cgZG8gdGhlc2UgcGxvdHMgY2hhbmdlIGlmIHlvdSBnbyBiYWNrIGFuZCBsZWF2ZSBhbGwgb2YgdGhlIHN0b3Atd29yZHMgaW4gdGhlIHRpYmJsZT8gRG9lcyB0aGUgc2l6ZSBvZiB0aGUgdGV4dCBncm91cHMgKDUwMCB3b3JkcyB2cy4gMTAwMCB3b3JkcykgYWZmZWN0IHRoZSBhbmFseXNpcz8gIA0KDQpBcyBhIG5leHQgc3RlcCwgb25lIG1pZ2h0IGxvb2sgYXQgdGhlIG1heGltdW0gc2VudGltZW50IHNjb3JlIGFuZCB0aGUgbWluaW11bSBzZW50aW1lbnQgc2NvcmUgZm9yIGVhY2ggYm9vayB0byBzZWUgd2hhdCB0ZXh0IGdyb3VwcyBwcm9kdWNlZCB0aGUgZXh0cmVtZSBzY29yZXMuIA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNlcmllcyAlPiUNCiAgZ3JvdXBfYnkoYm9vaykgJT4lIA0KICBtdXRhdGUod29yZF9jb3VudCA9IDE6bigpLA0KICAgICAgICAgaW5kZXggPSB3b3JkX2NvdW50ICUvJSA1MDAgKyAxKSAlPiUgDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lDQogIGNvdW50KGJvb2ssIGluZGV4ID0gaW5kZXggLCBzZW50aW1lbnQpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUsDQogICAgICAgICBib29rID0gZmFjdG9yKGJvb2ssIGxldmVscyA9IHRpdGxlcykpICU+JQ0KICBnZ3Bsb3QoYWVzKGluZGV4LCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKw0KICBnZW9tX2JhcihhbHBoYSA9IDAuNSwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+IGJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikNCg0KYGBgDQpVc2luZyBzaW5nbGUgd29yZHMgYXMgdG9rZW5zIGZvciBzZW50aW1lbnQgYW5hbHlzaXMgY2FuIGJlIGxlc3MgdGhhbiBpZGVhbC4gIFRoaXMgaXMgYmVjYXVzZSBuZWFyYnkgd29yZHMgYWRkIGNvbnRleHQgLSBpbiBwYXJ0aWN1bGFyLCBuZWdhdGlvbnMgbWFrZSB0aGUgYW5hbHlzaXMgdHJpY2t5LiBGb3IgZXhhbXBsZSwgdGhlIHdvcmQgImdvb2QiIGlzIGEgcG9zaXRpdmUgd29yZC4gIEhvd2V2ZXIsICJNeSBkYXkgd2FzIG5vdCBnb29kIiBoYXMgYSBuZWdhdGl2ZSBzZW50aW1lbnQsIGRlc3BpdGUgdGhlIHByZXNlbmNlIG9mIHRoZSB3b3JkICJnb29kIi4gQSBiZXR0ZXIgZXhhbXBsZSBmb3IgdGhlIEhhcnJ5IFBvdHRlciBzZXJpZXMgd291bGQgYmUgdGhhdCAibWFnaWMiIGlzIGNvbnNpZGVyZWQgdG8gYmUgYSBwb3NpdGl2ZSB3b3JkLCBidXQgImRhcmsgbWFnaWMiIHdvdWxkIGJlIGhhdmUgYSBuZWdhdGl2ZSBtZWFuaW5nLiAgRm9yIGZ1cnRoZXIgYW5hbHlzaXMgb2Ygb3VyIEhhcnJ5IFBvdHRlciB0ZXh0LCBsZXQncyBsb29rIGF0IHBhaXJzIG9mIHdvcmRzIChiaWdyYW1zKS4gQSBiaWdyYW0gaXMgYSBwYWlyIG9mIHdvcmRzIHRoYXQgYXBwZWFyIGNvbnNlY3V0aXZlbHkgaW4gYSB0ZXh0LiAgRm9yIGV4YW1wbGUsIGlmIHdlIGxvb2sgYXQgdGhlIHNlbnRlbmNlICJJIGF0ZSBwdXJwbGUgZ3JhcGVzIiwgdGhlIGJpZ3JhbXMgd2UgY2FuIGV4dHJhY3Qgd291bGQgYmUgKEksIGF0ZSksIChhdGUsIHB1cnBsZSksIGFuZCAocHVycGxlLCBncmFwZXMpLiBJbiB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmssIEkgcmVwZWF0IHRoZSBwcm9jZXNzIG9mIHNoYXBpbmcgdGhlIHRleHQgZGF0YSBmcm9tIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGRvY3VtZW50LCBidXQgdGhpcyB0aW1lIEkgc3BlY2lmeSB0aGF0IGJpZ3JhbXMgc2hvdWxkIGJlIHVzZWQgdG8gdG9rZW5pemUgdGhlIHRleHQgcmF0aGVyIHRoYW4gc2luZ2xlIHdvcmRzLiANCg0KYGBge3J9DQpzZXJpZXMgPC0gdGliYmxlKCkNCg0KZm9yKGkgaW4gc2VxX2Fsb25nKHRpdGxlcykpIHsNCiAgDQogIHRlbXAgPC0gdGliYmxlKGNoYXB0ZXIgPSBzZXFfYWxvbmcoYm9va3NbW2ldXSksDQogICAgICAgICAgICAgICAgICB0ZXh0ID0gYm9va3NbW2ldXSkgJT4lDQogICAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUNCiAgICAjI0hlcmUgd2UgdG9rZW5pemUgZWFjaCBjaGFwdGVyIGludG8gYmlncmFtcw0KICAgIG11dGF0ZShib29rID0gdGl0bGVzW2ldKSAlPiUNCiAgICBzZWxlY3QoYm9vaywgZXZlcnl0aGluZygpKQ0KICANCiAgc2VyaWVzIDwtIHJiaW5kKHNlcmllcywgdGVtcCkNCn0NCg0KIyBzZXQgZmFjdG9yIHRvIGtlZXAgYm9va3MgaW4gb3JkZXIgb2YgcHVibGljYXRpb24NCnNlcmllcyRib29rIDwtIGZhY3RvcihzZXJpZXMkYm9vaywgbGV2ZWxzID0gcmV2KHRpdGxlcykpDQpzZXJpZXMNCmBgYA0KDQpBZ2Fpbiwgd2UgY2FuIHVzZSB0aGUgY291bnQgZnVuY3Rpb24gdG8gZmluZCB0aGUgbW9zdCBjb21tb24gYmlncmFtcyBpbiB0aGUgc2VyaWVzLiAgDQoNCmBgYHtyfQ0Kc2VyaWVzICU+JQ0KICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKQ0KYGBgDQpBcyB3ZSBzYXcgd2l0aCB0aGUgc2luZ2xlIHdvcmRzLCBtb3N0IG9mIHRoZSBtb3N0IGNvbW1vbiBiaWdyYW1zIGNvbnRhaW4gc3RvcC13b3Jkcy4gTGV0J3MgcmVtb3ZlIHRob3NlIGZyb20gb3VyIGJpZ3JhbSB0aWJibGUuIA0KDQpgYGB7cn0NCmJpZ3JhbXNfc2VwYXJhdGVkIDwtIHNlcmllcyAlPiUNCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpDQoNCmJpZ3JhbXNfZmlsdGVyZWQgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lDQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQ0KICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKQ0KDQojIG5ldyBiaWdyYW0gY291bnRzOg0KYmlncmFtc191bml0ZWQgPC0gYmlncmFtc19maWx0ZXJlZCAlPiUNCiAgdW5pdGUoYmlncmFtLCB3b3JkMSwgd29yZDIsIHNlcCA9ICIgIikNCg0KYmlncmFtX2NvdW50cyA8LSBiaWdyYW1zX3VuaXRlZCAlPiUgDQogICAgY291bnQoYmlncmFtLCBzb3J0ID0gVFJVRSkNCmBgYA0KTm93IHRoYXQgd2UgaGF2ZSByZW1vdmVkIHRoZSBzdG9wLXdvcmRzLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIG1vc3QgZnJlcXVlbnRseSBvY2N1cnJpbmcgYmlncmFtIGluIHRoZSBzZXJpZXMgaXMgIlByb2Zlc3NvciBNY0dvbmFnYWxsIi4gIFRoZSBvbmx5IGJpZ3JhbXMgaW4gdGhlIHRvcCB0ZW4gdGhhdCBkb24ndCBjb250YWluIGNoYXJhY3RlciBuYW1lcyBhcmUgIkRlYXRoIEVhdGVycyIsICJJbnZpc2liaWxpdHkgQ2xvYWsiIGFuZCAiRGFyayBBcnRzIi4gDQoNCk5vdywgbGV0J3MgdXNlIG91ciBiaWdyYW1zIHRvIHByYWN0aWNlIHRmLWlkZiAodGVybSBmcmVxdWVuY3kgLWludmVyc2UgZG9jdW1lbnQgZnJlcXVlbmN5KS4gIEluIGEgbnV0c2hlbGwsIHRmLWlkZiBpcyBhbiBhbmFseXNpcyB0aGF0IHNlZWtzIHRvIGlkZW50aWZ5IGhvdyBjb21tb24gYSB3b3JkIGlzIGluIGEgcGFydGljdWxhciB0ZXh0LCBnaXZlbiBob3cgb2Z0ZW4gaXQgb2NjdXJzIGluIGEgZ3JvdXAgb2YgdGV4dHMuICBGb3IgZXhhbXBsZSwgUHJvZmVzc29yIEx1cGluIHdhcyBhIHZlcnkgcHJvbWluZW50IGNoYXJhY3RlciBpbiAiVGhlIFByaXNvbmVyIG9mIEF6a2FiYW4iIiwgYnV0IG5vdCBzbyBtdWNoIGluIHRoZSBvdGhlciBib29rcyAoYW5kIGluIHRoZSBvdGhlciBib29rcywgaGUgd2FzIG5vdCBhIHByb2Zlc3NvcikuICBBIHBlcnNvbiB3aG8gaGFkIG5vdCByZWFkIGFsbCBvZiB0aGUgYm9va3MgY291bGQgZGV0ZXJtaW5lIHRoaXMgYnkgc2ltcGx5IGNvdW50aW5nIHRoZSBudW1iZXIgb2YgdGltZXMgdGhlIG5hbWUgIlByb2Zlc3NvciBMdXBpbiIgb2NjdXJzIGluICJUaGUgUHJpc29uZXIgb2YgQXprYWJhbiIgYW5kIGNvbXBhcmluZyB0aGF0IG51bWJlciB0byB0aGUgZnJlcXVlbmN5IG9mIHRoYXQgYmlncmFtIGluIHRoZSByZXN0IG9mIHRoZSBib29rcyBpbiB0aGUgc2VyaWVzLiAgVG8gcXVhbnRpZnkgdGhpcyBpZGVhLCB0aGUgdGVybSBmcmVxdWVuY3kgKHRoZSBudW1iZXIgb2YgdGltZXMgYSB0b2tlbiBhcHBlYXJzIGluIGEgZG9jdW1lbnQgZGl2aWRlZCBieSB0aGUgdG90YWwgbnVtYmVyIG9mIHRva2VucyBpbiB0aGUgZG9jdW1lbnQpIGlzIG11bHRpcGxpZWQgYnkgdGhlIGludmVyc2UgZG9jdW1lbnQgZnJlcXVlbmN5ICh0aGUgdG90YWwgbnVtYmVyIG9mIGRvY3VtZW50cyBkaXZpZGVkIGJ5IHRoZSBudW1iZXIgb2YgZG9jdW1lbnRzIGNvbnRhaW5pbmcgdGhlIHRva2VuKS4gVGhlIGNoYXJ0IGJlbG93IGRpc3BsYXlzIHRoZSB0ZW4gYmlncmFtcyB3aXRoIHRoZSBoaWdoZXN0IHRmLWlkZiBzY29yZXMgYW1vbmcgdGhlIHNldmVuIGJvb2tzIGluIHRoZSBzZXJpZXMuICAiUHJvZmVzc29yIFVtYnJpZGdlIiwgaGFzIHRoZSBoaWdoZXN0IHRmLWlkZiBzY29yZSByZWxhdGl2ZSB0byAiVGhlIE9yZGVyIG9mIHRoZSBQaG9lbml4Ii4gIEFueSBIYXJyeSBQb3R0ZXIgbG92ZXIgY2FuIHRlbGwgeW91IHRoYXQgd2UgZmlyc3QgbWVldCBQcm9mZXNzb3IgVW1icmlkZ2UgaW4gIlRoZSBPcmRlciBvZiB0aGUgUGhvZW5peCIsIGluIHdoaWNoIGFuZCBzaGUgcGxheXMgYSBtYWpvciByb2xlLiAgSW4gdGhlIG90aGVyIGJvb2tzIGluIHRoZSBzZXJpZXMsIGhlciByb2xlIHJhbmdlcyBmcm9tIHNtYWxsIHRvIG5vbi1leGlzdGVudC4gVGh1cywgaXQgbWFrZXMgc2Vuc2UgdGhhdCAiUHJvZmVzc29yIFVtYnJpZGdlIiBoYXMgYSByZWxhdGl2ZWx5IGhpZ2ggdGYtaWRmIHNjb3JlLiAgQmVuZWF0aCB0aGUgY2hhcnQsIEkgaGF2ZSBjcmVhdGVkIGEgdmlzdWFsIGZvciB0aGUgYmlncmFtcyB3aXRoIHRoZSBoaWdoZXN0IHRmLWlkZiBzY29yZXMuDQoNCmBgYHtyfQ0KYmlncmFtX3RmX2lkZiA8LSBiaWdyYW1zX3VuaXRlZCAlPiUNCiAgY291bnQoYm9vaywgYmlncmFtKSAlPiUNCiAgYmluZF90Zl9pZGYoYmlncmFtLCBib29rLCBuKSAlPiUNCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpDQoNCmJpZ3JhbV90Zl9pZGYNCg0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGxvdF9wb3R0ZXI8LSBiaWdyYW1fdGZfaWRmICU+JQ0KICBhcnJhbmdlKGRlc2ModGZfaWRmKSkgJT4lDQogIG11dGF0ZShiaWdyYW0gPSBmYWN0b3IoYmlncmFtLCBsZXZlbHMgPSByZXYodW5pcXVlKGJpZ3JhbSkpKSkNCg0KcGxvdF9wb3R0ZXIgJT4lIA0KICB0b3BfbigyMCkgJT4lDQogIGdncGxvdChhZXMoYmlncmFtLCB0Zl9pZGYsIGZpbGwgPSBib29rKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQpOb3csIHRvIGdldCBhbiBpZGVhIG9mIGhvdyBvdXIgc2VudGltZW50IGFuYWx5c2lzIHdhcyBhZmZlY3RlZCBieSBuZWdhdGlvbnMsIGxldCdzIGZpbmQgYWxsIHRoZSBiaWdyYW1zIHRoYXQgaGF2ZSB0aGUgd29yZCAibm90IiBhcyB0aGUgZmlyc3Qgd29yZCBpbiB0aGUgYmlncmFtLiANCg0KYGBge3J9DQpiaWdyYW1zX3NlcGFyYXRlZCAlPiUNCiAgZmlsdGVyKHdvcmQxID09ICJub3QiKSAlPiUNCiAgY291bnQod29yZDEsIHdvcmQyLCBzb3J0ID0gVFJVRSkNCmBgYA0KVGhlIGZpcnN0IHRlbiBiaWdyYW1zIHdpdGggIm5vdCIgYXMgdGhlIGZpcnN0IHdvcmQgYXJlIGJvcmluZywgc28gbGV0J3MgcmVtb3ZlIHN0b3Atd29yZHMgZnJvbSB0aGUgIndvcmQyIiBjb2x1bW4uIA0KDQpgYGB7cn0NCmJpZ3JhbXNfc2VwYXJhdGVkIDwtIGJpZ3JhbXNfc2VwYXJhdGVkICU+JQ0KICBmaWx0ZXIod29yZDEgPT0gIm5vdCIpICU+JQ0KICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKSU+JQ0KICBjb3VudCh3b3JkMSwgd29yZDIsIHNvcnQgPSBUUlVFKQ0KDQpiaWdyYW1zX3NlcGFyYXRlZA0KYGBgDQoNCmBgYHtyfQ0KQklORyA8LSBnZXRfc2VudGltZW50cygiYmluZyIpDQoNCm5vdF93b3JkcyA8LSBiaWdyYW1zX3NlcGFyYXRlZCAlPiUNCiAgZmlsdGVyKHdvcmQxID09ICJub3QiKSAlPiUNCiAgZmlsdGVyKCF3b3JkMiAlaW4lIHN0b3Bfd29yZHMkd29yZCklPiUNCiAgaW5uZXJfam9pbihCSU5HLCBieSA9IGMod29yZDIgPSAid29yZCIpKSAlPiUNCiAgdW5ncm91cCgpDQoNCm5vdF93b3Jkcw0KYGBgDQoNCkp1c3QgbG9va2luZyBhdCB0aGUgdG9wIHRlbiB3b3JkcyBpbiB0aGUgbGlzdCwgd2UgY2FuIHNlZSB0aGF0IG1vc3Qgb2YgdGhlIHdvcmRzIHRoYXQgYXJlIHByZWNlZGVkIGJ5ICJub3QiIGluIHRoZSBzZXJpZXMsIGhhdmUgbmVnYXRpdmUgc2VudGltZW50LiAgVGhpcyBtZWFucywgd2UgbWF5IGJlIG92ZXIgZXN0aW1hdGluZyB0aGUgbmVnYXRpdmUgc2VudGltZW50IHByZXNlbnQgaW4gdGhlIHRleHQuICBPZiBjb3Vyc2UsIHRoZXJlIGFyZSBtYW55IG90aGVyIG5lZ2F0aW9uIHdvcmRzIHN1Y2ggYXMgIm5ldmVyIiwgIm5vIiwgZXRjLiBPbmUgY291bGQgZXhwbG9yZSBhbGwgb2YgdGhlc2UgcG9zc2libGUgbmVnYXRpb24gd29yZHMgdG8gZ2V0IGEgYmV0dGVyIGlkZWEgb2YgaG93IG5lZ2F0aW9uIGlzIGFmZmVjdGluZyB0aGUgc2VudGltZW50IGFuYWx5c2lzLiANCg0KV2UgY2FuIGFsc28gY3JlYXRlIGEgZ3JhcGggdGhhdCBjb25uZWN0cyBvdXIgbW9zdCBmcmVxdWVudGx5IG9jY3VycmluZyB3b3JkcyB3aXRoIGVhY2ggb3RoZXIuIExvb2tpbmcgYXQgdGhlIGdyYXBoIGJlbG93LCB3ZSBjYW4gc2VlIGEgY291cGxlIG9mIGxhcmdlciBjbHVzdGVycyB0aGF0IGdpdmUgc29tZSBjb250ZXh0IHRvIHdoYXQgdGhlIHNlcmllcyBtaWdodCBiZSBhYm91dC4gIEZvciBleGFtcGxlLCB0aGVyZSBpcyBhIGNsdXN0ZXIgd2l0aCB0aGUgd29yZCAicHJvZmVzc29yIiBpbiB0aGUgY2VudGVyLCB3aXRoIHNldmVyYWwgb3RoZXIgd29yZHMgY29ubmVjdGVkIHRvIGl0IHN1Y2ggYXMiTWNHb25hZ2FsbCIgYW5kICJMdXBpbiIuIA0KDQpgYGB7cn0NCmJpZ3JhbV9ncmFwaCA8LSBiaWdyYW1fY291bnRzICU+JQ0KICBmaWx0ZXIobiA+IDcwKSAlPiUNCiAgZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKCkNCg0KYmlncmFtX2dyYXBoDQpgYGANCmBgYHtyfQ0Kc2V0LnNlZWQoMjAxNykNCg0KZ2dyYXBoKGJpZ3JhbV9ncmFwaCwgbGF5b3V0ID0gImZyIikgKw0KICBnZW9tX2VkZ2VfbGluaygpICsNCiAgZ2VvbV9ub2RlX3BvaW50KCkgKw0KICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwgdmp1c3QgPSAxLCBoanVzdCA9IDEpDQpgYGANClRoaXMgY29uY2x1ZGVzIG91ciB0dXRvcmlhbCBvbiBiYXNpYyB0ZXh0IGFuYWx5dGljcyB3aXRoIHRoZSBIYXJyeSBQb3R0ZXIgc2VyaWVzLiAgVGhpcyBpcyBieSBubyBtZWFucyBhIGNvbXByZWhlbnNpdmUgYW5hbHlzaXMsIGJ1dCBpdCBzaG91bGQgaGF2ZSBkZW1vbnN0cmF0ZWQgc29tZSBvZiB0aGUgYmFzaWMgZmFjZXRzIG9mIHRleHQgbWluaW5nIHdpdGggdGhlIHRpZHl2ZXJzZSBpbiBSLiAgDQoNCg0KDQo=