Week 10 Assignment

Reproduce and extend Sentiment analysis with tidy data

library(tidytext)
library(janeaustenr)
library(dplyr)
library(stringr)
library(tidyr)

Starting analysis

Get Sentiment tables

  1. AFINN from Finn Årup Nielsen
## # A tibble: 2,477 x 2
##    word       value
##    <chr>      <dbl>
##  1 abandon       -2
##  2 abandoned     -2
##  3 abandons      -2
##  4 abducted      -2
##  5 abduction     -2
##  6 abductions    -2
##  7 abhor         -3
##  8 abhorred      -3
##  9 abhorrent     -3
## 10 abhors        -3
## # ... with 2,467 more rows
  1. bing from Bing Liu and collaborators
## # A tibble: 6,786 x 2
##    word        sentiment
##    <chr>       <chr>    
##  1 2-faces     negative 
##  2 abnormal    negative 
##  3 abolish     negative 
##  4 abominable  negative 
##  5 abominably  negative 
##  6 abominate   negative 
##  7 abomination negative 
##  8 abort       negative 
##  9 aborted     negative 
## 10 aborts      negative 
## # ... with 6,776 more rows
  1. NRC from Saif Mohammad and Peter Turney.
## # A tibble: 13,901 x 2
##    word        sentiment
##    <chr>       <chr>    
##  1 abacus      trust    
##  2 abandon     fear     
##  3 abandon     negative 
##  4 abandon     sadness  
##  5 abandoned   anger    
##  6 abandoned   fear     
##  7 abandoned   negative 
##  8 abandoned   sadness  
##  9 abandonment anger    
## 10 abandonment fear     
## # ... with 13,891 more rows

Sentiment analysis with inner join

With data in a tidy format, sentiment analysis can be done as an inner join. This is another of the great successes of viewing text mining as a tidy data analysis task; much as removing stop words is an antijoin operation, performing sentiment analysis is an inner join operation.

tidy_books <- austen_books() %>%
  group_by(book) %>%
  mutate(
    linenumber = row_number(),
    chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]",
      ignore_case = TRUE
    )))
  ) %>%
  ungroup() %>%
  unnest_tokens(word, text)

Performing the sentiment Analysis

Looking for words with joy sentiment within our data:

nrc_joy <- get_sentiments("nrc") %>%
  filter(sentiment == "joy")

tidy_books %>%
  filter(book == "Emma") %>%
  inner_join(nrc_joy) %>%
  count(word, sort = TRUE)
## Joining, by = "word"
## # A tibble: 303 x 2
##    word        n
##    <chr>   <int>
##  1 good      359
##  2 young     192
##  3 friend    166
##  4 hope      143
##  5 happy     125
##  6 love      117
##  7 deal       92
##  8 found      92
##  9 present    89
## 10 kind       82
## # ... with 293 more rows

Looking at the overall sentiment in Jane Austen’s books:

Small sections of text may not have enough words in them to get a good estimate of sentiment while really large sections can wash out narrative structure. For these books, using 80 lines works well, but this can vary depending on individual texts, how long the lines were to start with, etc. We then use spread() so that we have negative and positive sentiment in separate columns, and lastly calculate a net sentiment (positive - negative).

jane_austen_sentiment <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(book, index = linenumber %/% 80, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
## Joining, by = "word"

Now we can plot these sentiment scores across the plot trajectory of each novel. Notice that we are plotting against the index on the x-axis that keeps track of narrative time in sections of text.

library(ggplot2)

ggplot(jane_austen_sentiment, aes(index, sentiment, fill = book)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~book, ncol = 2, scales = "free_x")

Comparing the three sentiment dictionaries

pride_prejudice <- tidy_books %>%
  filter(book == "Pride & Prejudice")

knitr::kable(head(pride_prejudice))
book linenumber chapter word
Pride & Prejudice 1 0 pride
Pride & Prejudice 1 0 and
Pride & Prejudice 1 0 prejudice
Pride & Prejudice 3 0 by
Pride & Prejudice 3 0 jane
Pride & Prejudice 3 0 austen
afinn <- pride_prejudice %>%
  inner_join(get_sentiments("afinn")) %>%
  group_by(index = linenumber %/% 80) %>%
  summarise(sentiment = sum(value)) %>%
  mutate(method = "AFINN")
## Joining, by = "word"
## `summarise()` ungrouping output (override with `.groups` argument)
bing_and_nrc <- bind_rows(
  pride_prejudice %>%
    inner_join(get_sentiments("bing")) %>%
    mutate(method = "Bing et al."),
  pride_prejudice %>%
    inner_join(get_sentiments("nrc") %>%
      filter(sentiment %in% c(
        "positive",
        "negative"
      ))) %>%
    mutate(method = "NRC")
) %>%
  count(method, index = linenumber %/% 80, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
## Joining, by = "word"
## Joining, by = "word"

We now have an estimate of the net sentiment (positive - negative) in each chunk of the novel text for each sentiment lexicon. Let’s bind them together and visualize them next:

bind_rows(
  afinn,
  bing_and_nrc
) %>%
  ggplot(aes(index, sentiment, fill = method)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~method, ncol = 1, scales = "free_y")

Counting positive and negative words

bing_word_counts <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
## Joining, by = "word"
bing_word_counts
## # A tibble: 2,585 x 3
##    word     sentiment     n
##    <chr>    <chr>     <int>
##  1 miss     negative   1855
##  2 well     positive   1523
##  3 good     positive   1380
##  4 great    positive    981
##  5 like     positive    725
##  6 better   positive    639
##  7 enough   positive    613
##  8 happy    positive    534
##  9 love     positive    495
## 10 pleasure positive    462
## # ... with 2,575 more rows
bing_word_counts %>%
  group_by(sentiment) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(
    y = "Contribution to sentiment",
    x = NULL
  ) +
  coord_flip()
## Selecting by n

This image lets us spot an anomaly in the sentiment analysis; the word “miss” is coded as negative but it is used as a title for young, unmarried women in Jane Austen’s works. We could easily add “miss” to a custom stop-words list using bind_rows().

Wordclouds

library(wordcloud)
## Warning: package 'wordcloud' was built under R version 4.0.3
## Loading required package: RColorBrewer
custom_stop_words <- bind_rows(
  tibble(
    word = c("miss"),
    lexicon = c("custom")
  ),
  stop_words
)

suppressWarnings(tidy_books %>%
  anti_join(custom_stop_words) %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100)))
## Joining, by = "word"

Looking at units beyond just words

We can use unnest_tokens() to split into tokens using a regex pattern. We could use this, for example, to split the text of Jane Austen’s novels into a data frame by chapter.

austen_chapters <- austen_books() %>%
  group_by(book) %>%
  unnest_tokens(chapter, text,
    token = "regex",
    pattern = "Chapter|CHAPTER [\\dIVXLC]"
  ) %>%
  ungroup()

austen_chapters %>%
  group_by(book) %>%
  summarise(chapters = n())
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 6 x 2
##   book                chapters
##   <fct>                  <int>
## 1 Sense & Sensibility       51
## 2 Pride & Prejudice         62
## 3 Mansfield Park            49
## 4 Emma                      56
## 5 Northanger Abbey          32
## 6 Persuasion                25

We can use tidy text analysis to ask questions such as what are the most negative chapters in each of Jane Austen’s novels? First, let’s get the list of negative words from the Bing lexicon. Second, let’s make a data frame of how many words are in each chapter so we can normalize for the length of chapters. Then, let’s find the number of negative words in each chapter and divide by the total words in each chapter. For each book, which chapter has the highest proportion of negative words?

bingnegative <- get_sentiments("bing") %>%
  filter(sentiment == "negative")

wordcounts <- tidy_books %>%
  group_by(book, chapter) %>%
  summarize(words = n())
## `summarise()` regrouping output by 'book' (override with `.groups` argument)
tidy_books %>%
  semi_join(bingnegative) %>%
  group_by(book, chapter) %>%
  summarize(negativewords = n()) %>%
  left_join(wordcounts, by = c("book", "chapter")) %>%
  mutate(ratio = negativewords / words) %>%
  filter(chapter != 0) %>%
  top_n(1) %>%
  ungroup()
## Joining, by = "word"
## `summarise()` regrouping output by 'book' (override with `.groups` argument)
## Selecting by ratio
## # A tibble: 6 x 5
##   book                chapter negativewords words  ratio
##   <fct>                 <int>         <int> <int>  <dbl>
## 1 Sense & Sensibility      43           161  3405 0.0473
## 2 Pride & Prejudice        34           111  2104 0.0528
## 3 Mansfield Park           46           173  3685 0.0469
## 4 Emma                     15           151  3340 0.0452
## 5 Northanger Abbey         21           149  2982 0.0500
## 6 Persuasion                4            62  1807 0.0343

Summary

Sentiment analysis provides a way to understand the attitudes and opinions expressed in texts. In this analysis, we explored how to approach sentiment analysis using tidy data principles; when text data is in a tidy data structure, sentiment analysis can be implemented as an inner join. We can use sentiment analysis to understand how a narrative arc changes throughout its course or what words with emotional and opinion content are important for a particular text.

Self - Exploration

Harry Potter - Sentiment Analysis

We will extend this analysis by using the same techniques explored before and applying them to the Harry Potter books.

I identified this library: Harry Potter Books which allows us access to the whole Harry Potter texts.

To Install use:

if (packageVersion("devtools") < 1.6) {
  install.packages("devtools")
}

devtools::install_github("bradleyboehmke/harrypotter")

Start Analysis

library(harrypotter)

The books are stored as character vectors so the first step is to get them as data frames. I got them into separate dataframes, then used rbind to make a singular data frame.

# The books are stored as character vectors so 
# we need to get them into dataframes
 
hp1 <- as.data.frame(philosophers_stone) %>%
 mutate(
   book = "1_philosophers_stone",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, philosophers_stone)

hp2 <- as.data.frame(chamber_of_secrets) %>%
 mutate(
   book = "2_chamber_of_secrets",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, chamber_of_secrets)

hp3 <- as.data.frame(prisoner_of_azkaban) %>%
 mutate(
   book = "3_prisoner_of_azkaban",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, prisoner_of_azkaban)

hp4 <- as.data.frame(goblet_of_fire) %>%
 mutate(
   book = "4_goblet_of_fire",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, goblet_of_fire)

hp5 <- as.data.frame(order_of_the_phoenix) %>%
 mutate(
   book = "5_order_of_the_phoenix",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, order_of_the_phoenix)

hp6 <- as.data.frame(half_blood_prince) %>%
 mutate(
   book = "6_half_blood_prince",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, half_blood_prince)

hp7 <- as.data.frame(deathly_hallows) %>%
 mutate(
   book = "7_deathly_hallows",
  chapter = row_number(),
  ) %>%
  unnest_tokens(word, deathly_hallows)

hp_books<-rbind(hp1, hp2, hp3, hp4, hp5, hp6, hp7)

** Analyze the sentiments by using bing**

hp_sentiment <- hp_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(book, chapter, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
## Joining, by = "word"

Using the package viridis for styling.

library(viridis)
## Warning: package 'viridis' was built under R version 4.0.3
## Loading required package: viridisLite
ggplot(hp_sentiment, aes(chapter, sentiment, fill = book)) +
        geom_bar(stat = "identity", show.legend = FALSE) +
        facet_wrap(~book, ncol = 2, scales = "free_x") +
        theme_minimal(base_size = 13) +
        labs(title = "Sentiment in Harry Potter Novels",
             y = "Sentiment") +
        scale_fill_viridis(end = 0.75, discrete=TRUE, direction = -1) +
        scale_x_discrete(expand=c(0.02,0)) +
        theme(strip.text=element_text(hjust=0)) +
        theme(strip.text = element_text(face = "italic")) +
        theme(axis.title.x=element_blank()) +
        theme(axis.ticks.x=element_blank()) +
        theme(axis.text.x=element_blank())

Based on this graph, it would seem the Harry Potter book overall sentiment is negative.

Finding the most positive chapters in the books

With the Jane Austen novels, which were mostly positive, we tried to take a look at the mostly negative chapters. For the Harry Potter books, we’ll try to find the most positive chapters.

bingpositive <- get_sentiments("bing") %>%
        filter(sentiment == "positive")

wordcounts <- hp_books %>%
        group_by(book, chapter) %>%
        summarize(words = n())
## `summarise()` regrouping output by 'book' (override with `.groups` argument)
hp_books %>%
        semi_join(bingpositive) %>%
        group_by(book, chapter) %>%
        summarize(positivewords = n()) %>%
        left_join(wordcounts, by = c("book", "chapter")) %>%
        mutate(ratio = positivewords/words) %>%
        filter(chapter != 0) %>%
        top_n(1)
## Joining, by = "word"
## `summarise()` regrouping output by 'book' (override with `.groups` argument)
## Selecting by ratio
## # A tibble: 7 x 5
## # Groups:   book [7]
##   book                   chapter positivewords words  ratio
##   <chr>                    <int>         <int> <int>  <dbl>
## 1 1_philosophers_stone         5           214  6613 0.0324
## 2 2_chamber_of_secrets        19           265  8568 0.0309
## 3 3_prisoner_of_azkaban       12           156  4797 0.0325
## 4 4_goblet_of_fire             8           201  5860 0.0343
## 5 5_order_of_the_phoenix      15           225  6897 0.0326
## 6 6_half_blood_prince          9           237  5888 0.0403
## 7 7_deathly_hallows           35           180  5008 0.0359

BY looking at this table, we see which the most positive chapters of each book are. Chapter 5 on book 1 is when Harry discovers the wonderful world of magic and travels with Hagrid to Diagon Alley. In Deathly Hallows, chapter 35, King’s Cross is the calm before the storm. After Voldemort “kills” Harry, he wakes at King’s Cross station and has one last meeting with Dumbledore.

WordCloud

Let’s generate a word cloud from Harry Potter’s books.

#eliminate the most common names from the wordcloud
custom_stop_words <- bind_rows(
  tibble(
    word = c("harry", "potter", "hermione", "ron", "dumbledore", "voldemort"),
    lexicon = c("custom")
  ),
  stop_words
)

suppressWarnings(hp_books %>%
  anti_join(custom_stop_words) %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100)))
## Joining, by = "word"

Conclusion

We can see some of the words like dark, hard, fell and night be some of the most common ones. No wonder the overall sentiment of Harry Potter is negative!

hp_word_counts <- hp_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
## Joining, by = "word"
hp_word_counts %>%
  group_by(sentiment) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(
    y = "Contribution to sentiment",
    x = NULL
  ) +
  coord_flip()
## Selecting by n

If we perform a loop at the most used positive and negative words, we see that, even though the overall sentiment of the books is negative, the most used words have a positive charge. This might have something to do with the book’s popularity and sense of uplifting messages.

Even though the Harry Potter series target audience is teenagers and young adults, some of the themes it deals with: prejudice, murder, mistreatment of children, death and loss, can be really hard and dark. It comes as no surprise that the overall sentiment of the books is deemed as negative.

LS0tDQp0aXRsZTogIldlZWsgMTAgLSBBc3NpZ25tZW50Ig0KYXV0aG9yOiAiR2VvcmdlIENydXoiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6IG9wZW5pbnRybzo6bGFiX3JlcG9ydA0KLS0tDQojIFdlZWsgMTAgQXNzaWdubWVudCANCiMjIFJlcHJvZHVjZSBhbmQgZXh0ZW5kIFNlbnRpbWVudCBhbmFseXNpcyB3aXRoIHRpZHkgZGF0YQ0KDQpgYGB7ciBsb2FkLXBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeSh0aWR5dGV4dCkNCmxpYnJhcnkoamFuZWF1c3RlbnIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeSh0aWR5cikNCg0KYGBgDQoNCiMjIyBTdGFydGluZyBhbmFseXNpcw0KDQoqKkdldCBTZW50aW1lbnQgdGFibGVzKioNCg0KMS4gQUZJTk4gZnJvbSBbRmlubiDDhXJ1cCBOaWVsc2VuXShodHRwOi8vd3d3Mi5pbW0uZHR1LmRrL3B1YmRiL3ZpZXdzL3B1YmxpY2F0aW9uX2RldGFpbHMucGhwP2lkPTYwMTApDQoNCmBgYHtyIGVjaG89RkFMU0V9DQpnZXRfc2VudGltZW50cygiYWZpbm4iKQ0KYGBgDQoNCjIuIGJpbmcgZnJvbSBbQmluZyBMaXUgYW5kIGNvbGxhYm9yYXRvcnNdKGh0dHBzOi8vd3d3LmNzLnVpYy5lZHUvfmxpdWIvRkJTL3NlbnRpbWVudC1hbmFseXNpcy5odG1sKQ0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KZ2V0X3NlbnRpbWVudHMoImJpbmciKQ0KYGBgDQoNCjMuIE5SQyBmcm9tIFtTYWlmIE1vaGFtbWFkIGFuZCBQZXRlciBUdXJuZXldKGh0dHA6Ly9zYWlmbW9oYW1tYWQuY29tL1dlYlBhZ2VzL05SQy1FbW90aW9uLUxleGljb24uaHRtKS4NCmBgYHtyIGVjaG89RkFMU0V9DQpnZXRfc2VudGltZW50cygibnJjIikNCmBgYA0KDQoNCiMjIyBTZW50aW1lbnQgYW5hbHlzaXMgd2l0aCBpbm5lciBqb2luDQoNCldpdGggZGF0YSBpbiBhIHRpZHkgZm9ybWF0LCBzZW50aW1lbnQgYW5hbHlzaXMgY2FuIGJlIGRvbmUgYXMgYW4gaW5uZXIgam9pbi4gVGhpcyBpcyBhbm90aGVyIG9mIHRoZSBncmVhdCBzdWNjZXNzZXMgb2Ygdmlld2luZyB0ZXh0IG1pbmluZyBhcyBhIHRpZHkgZGF0YSBhbmFseXNpcyB0YXNrOyBtdWNoIGFzIHJlbW92aW5nIHN0b3Agd29yZHMgaXMgYW4gYW50aWpvaW4gb3BlcmF0aW9uLCBwZXJmb3JtaW5nIHNlbnRpbWVudCBhbmFseXNpcyBpcyBhbiBpbm5lciBqb2luIG9wZXJhdGlvbi4NCg0KYGBge3J9DQp0aWR5X2Jvb2tzIDwtIGF1c3Rlbl9ib29rcygpICU+JQ0KICBncm91cF9ieShib29rKSAlPiUNCiAgbXV0YXRlKA0KICAgIGxpbmVudW1iZXIgPSByb3dfbnVtYmVyKCksDQogICAgY2hhcHRlciA9IGN1bXN1bShzdHJfZGV0ZWN0KHRleHQsIHJlZ2V4KCJeY2hhcHRlciBbXFxkaXZ4bGNdIiwNCiAgICAgIGlnbm9yZV9jYXNlID0gVFJVRQ0KICAgICkpKQ0KICApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkNCmBgYA0KDQoqKlBlcmZvcm1pbmcgdGhlIHNlbnRpbWVudCBBbmFseXNpcyAqKg0KDQpMb29raW5nIGZvciB3b3JkcyB3aXRoICpqb3kqIHNlbnRpbWVudCB3aXRoaW4gb3VyIGRhdGE6DQoNCmBgYHtyfQ0KbnJjX2pveSA8LSBnZXRfc2VudGltZW50cygibnJjIikgJT4lDQogIGZpbHRlcihzZW50aW1lbnQgPT0gImpveSIpDQoNCnRpZHlfYm9va3MgJT4lDQogIGZpbHRlcihib29rID09ICJFbW1hIikgJT4lDQogIGlubmVyX2pvaW4obnJjX2pveSkgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNCioqTG9va2luZyBhdCB0aGUgb3ZlcmFsbCBzZW50aW1lbnQgaW4gSmFuZSBBdXN0ZW4ncyBib29rczogKioNCg0KU21hbGwgc2VjdGlvbnMgb2YgdGV4dCBtYXkgbm90IGhhdmUgZW5vdWdoIHdvcmRzIGluIHRoZW0gdG8gZ2V0IGEgZ29vZCBlc3RpbWF0ZSBvZiBzZW50aW1lbnQgd2hpbGUgcmVhbGx5IGxhcmdlIHNlY3Rpb25zIGNhbiB3YXNoIG91dCBuYXJyYXRpdmUgc3RydWN0dXJlLiBGb3IgdGhlc2UgYm9va3MsIHVzaW5nIDgwIGxpbmVzIHdvcmtzIHdlbGwsIGJ1dCB0aGlzIGNhbiB2YXJ5IGRlcGVuZGluZyBvbiBpbmRpdmlkdWFsIHRleHRzLCBob3cgbG9uZyB0aGUgbGluZXMgd2VyZSB0byBzdGFydCB3aXRoLCBldGMuIFdlIHRoZW4gdXNlIHNwcmVhZCgpIHNvIHRoYXQgd2UgaGF2ZSBuZWdhdGl2ZSBhbmQgcG9zaXRpdmUgc2VudGltZW50IGluIHNlcGFyYXRlIGNvbHVtbnMsIGFuZCBsYXN0bHkgY2FsY3VsYXRlIGEgbmV0IHNlbnRpbWVudCAocG9zaXRpdmUgLSBuZWdhdGl2ZSkuDQoNCmBgYHtyfQ0KamFuZV9hdXN0ZW5fc2VudGltZW50IDwtIHRpZHlfYm9va3MgJT4lDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lDQogIGNvdW50KGJvb2ssIGluZGV4ID0gbGluZW51bWJlciAlLyUgODAsIHNlbnRpbWVudCkgJT4lDQogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQoNCmBgYA0KDQpOb3cgd2UgY2FuIHBsb3QgdGhlc2Ugc2VudGltZW50IHNjb3JlcyBhY3Jvc3MgdGhlIHBsb3QgdHJhamVjdG9yeSBvZiBlYWNoIG5vdmVsLiBOb3RpY2UgdGhhdCB3ZSBhcmUgcGxvdHRpbmcgYWdhaW5zdCB0aGUgaW5kZXggb24gdGhlIHgtYXhpcyB0aGF0IGtlZXBzIHRyYWNrIG9mIG5hcnJhdGl2ZSB0aW1lIGluIHNlY3Rpb25zIG9mIHRleHQuDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpnZ3Bsb3QoamFuZV9hdXN0ZW5fc2VudGltZW50LCBhZXMoaW5kZXgsIHNlbnRpbWVudCwgZmlsbCA9IGJvb2spKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+Ym9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKQ0KYGBgDQoNCiMjIyBDb21wYXJpbmcgdGhlIHRocmVlIHNlbnRpbWVudCBkaWN0aW9uYXJpZXMNCg0KYGBge3J9DQpwcmlkZV9wcmVqdWRpY2UgPC0gdGlkeV9ib29rcyAlPiUNCiAgZmlsdGVyKGJvb2sgPT0gIlByaWRlICYgUHJlanVkaWNlIikNCg0Ka25pdHI6OmthYmxlKGhlYWQocHJpZGVfcHJlanVkaWNlKSkNCmBgYA0KDQpgYGB7cn0NCmFmaW5uIDwtIHByaWRlX3ByZWp1ZGljZSAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lDQogIGdyb3VwX2J5KGluZGV4ID0gbGluZW51bWJlciAlLyUgODApICU+JQ0KICBzdW1tYXJpc2Uoc2VudGltZW50ID0gc3VtKHZhbHVlKSkgJT4lDQogIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKQ0KDQpiaW5nX2FuZF9ucmMgPC0gYmluZF9yb3dzKA0KICBwcmlkZV9wcmVqdWRpY2UgJT4lDQogICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgICBtdXRhdGUobWV0aG9kID0gIkJpbmcgZXQgYWwuIiksDQogIHByaWRlX3ByZWp1ZGljZSAlPiUNCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSAlPiUNCiAgICAgIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKA0KICAgICAgICAicG9zaXRpdmUiLA0KICAgICAgICAibmVnYXRpdmUiDQogICAgICApKSkgJT4lDQogICAgbXV0YXRlKG1ldGhvZCA9ICJOUkMiKQ0KKSAlPiUNCiAgY291bnQobWV0aG9kLCBpbmRleCA9IGxpbmVudW1iZXIgJS8lIDgwLCBzZW50aW1lbnQpICU+JQ0KICBzcHJlYWQoc2VudGltZW50LCBuLCBmaWxsID0gMCkgJT4lDQogIG11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlKQ0KYGBgDQoNCldlIG5vdyBoYXZlIGFuIGVzdGltYXRlIG9mIHRoZSBuZXQgc2VudGltZW50IChwb3NpdGl2ZSAtIG5lZ2F0aXZlKSBpbiBlYWNoIGNodW5rIG9mIHRoZSBub3ZlbCB0ZXh0IGZvciBlYWNoIHNlbnRpbWVudCBsZXhpY29uLiBMZXTigJlzIGJpbmQgdGhlbSB0b2dldGhlciBhbmQgdmlzdWFsaXplIHRoZW0gbmV4dDogDQoNCmBgYHtyfQ0KYmluZF9yb3dzKA0KICBhZmlubiwNCiAgYmluZ19hbmRfbnJjDQopICU+JQ0KICBnZ3Bsb3QoYWVzKGluZGV4LCBzZW50aW1lbnQsIGZpbGwgPSBtZXRob2QpKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+bWV0aG9kLCBuY29sID0gMSwgc2NhbGVzID0gImZyZWVfeSIpDQpgYGANCg0KIyMjIENvdW50aW5nIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3Jkcw0KDQpgYGB7cn0NCmJpbmdfd29yZF9jb3VudHMgPC0gdGlkeV9ib29rcyAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQpiaW5nX3dvcmRfY291bnRzDQpgYGANCg0KYGBge3J9DQpiaW5nX3dvcmRfY291bnRzICU+JQ0KICBncm91cF9ieShzZW50aW1lbnQpICU+JQ0KICB0b3BfbigxMCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUNCiAgZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gc2VudGltZW50KSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofnNlbnRpbWVudCwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgbGFicygNCiAgICB5ID0gIkNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiLA0KICAgIHggPSBOVUxMDQogICkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQpUaGlzIGltYWdlIGxldHMgdXMgc3BvdCBhbiBhbm9tYWx5IGluIHRoZSBzZW50aW1lbnQgYW5hbHlzaXM7IHRoZSB3b3JkIOKAnG1pc3PigJ0gaXMgY29kZWQgYXMgbmVnYXRpdmUgYnV0IGl0IGlzIHVzZWQgYXMgYSB0aXRsZSBmb3IgeW91bmcsIHVubWFycmllZCB3b21lbiBpbiBKYW5lIEF1c3RlbuKAmXMgd29ya3MuIFdlIGNvdWxkIGVhc2lseSBhZGQg4oCcbWlzc+KAnSB0byBhIGN1c3RvbSBzdG9wLXdvcmRzIGxpc3QgdXNpbmcgYmluZF9yb3dzKCkuIA0KDQojIyMgV29yZGNsb3Vkcw0KDQpgYGB7cn0NCmxpYnJhcnkod29yZGNsb3VkKQ0KY3VzdG9tX3N0b3Bfd29yZHMgPC0gYmluZF9yb3dzKA0KICB0aWJibGUoDQogICAgd29yZCA9IGMoIm1pc3MiKSwNCiAgICBsZXhpY29uID0gYygiY3VzdG9tIikNCiAgKSwNCiAgc3RvcF93b3Jkcw0KKQ0KDQpzdXBwcmVzc1dhcm5pbmdzKHRpZHlfYm9va3MgJT4lDQogIGFudGlfam9pbihjdXN0b21fc3RvcF93b3JkcykgJT4lDQogIGNvdW50KHdvcmQpICU+JQ0KICB3aXRoKHdvcmRjbG91ZCh3b3JkLCBuLCBtYXgud29yZHMgPSAxMDApKSkNCmBgYA0KDQojIyMgTG9va2luZyBhdCB1bml0cyBiZXlvbmQganVzdCB3b3JkcyANCg0KV2UgY2FuIHVzZSBgdW5uZXN0X3Rva2VucygpYCB0byBzcGxpdCBpbnRvIHRva2VucyB1c2luZyBhICpyZWdleCogcGF0dGVybi4gV2UgY291bGQgdXNlIHRoaXMsIGZvciBleGFtcGxlLCB0byBzcGxpdCB0aGUgdGV4dCBvZiBKYW5lIEF1c3RlbuKAmXMgbm92ZWxzIGludG8gYSBkYXRhIGZyYW1lIGJ5IGNoYXB0ZXIuDQoNCmBgYHtyfQ0KYXVzdGVuX2NoYXB0ZXJzIDwtIGF1c3Rlbl9ib29rcygpICU+JQ0KICBncm91cF9ieShib29rKSAlPiUNCiAgdW5uZXN0X3Rva2VucyhjaGFwdGVyLCB0ZXh0LA0KICAgIHRva2VuID0gInJlZ2V4IiwNCiAgICBwYXR0ZXJuID0gIkNoYXB0ZXJ8Q0hBUFRFUiBbXFxkSVZYTENdIg0KICApICU+JQ0KICB1bmdyb3VwKCkNCg0KYXVzdGVuX2NoYXB0ZXJzICU+JQ0KICBncm91cF9ieShib29rKSAlPiUNCiAgc3VtbWFyaXNlKGNoYXB0ZXJzID0gbigpKQ0KYGBgDQoNCldlIGNhbiB1c2UgdGlkeSB0ZXh0IGFuYWx5c2lzIHRvIGFzayBxdWVzdGlvbnMgc3VjaCBhcyB3aGF0IGFyZSB0aGUgbW9zdCBuZWdhdGl2ZSBjaGFwdGVycyBpbiBlYWNoIG9mIEphbmUgQXVzdGVu4oCZcyBub3ZlbHM/IEZpcnN0LCBsZXTigJlzIGdldCB0aGUgbGlzdCBvZiBuZWdhdGl2ZSB3b3JkcyBmcm9tIHRoZSBCaW5nIGxleGljb24uIFNlY29uZCwgbGV04oCZcyBtYWtlIGEgZGF0YSBmcmFtZSBvZiBob3cgbWFueSB3b3JkcyBhcmUgaW4gZWFjaCBjaGFwdGVyIHNvIHdlIGNhbiBub3JtYWxpemUgZm9yIHRoZSBsZW5ndGggb2YgY2hhcHRlcnMuIFRoZW4sIGxldOKAmXMgZmluZCB0aGUgbnVtYmVyIG9mIG5lZ2F0aXZlIHdvcmRzIGluIGVhY2ggY2hhcHRlciBhbmQgZGl2aWRlIGJ5IHRoZSB0b3RhbCB3b3JkcyBpbiBlYWNoIGNoYXB0ZXIuIEZvciBlYWNoIGJvb2ssIHdoaWNoIGNoYXB0ZXIgaGFzIHRoZSBoaWdoZXN0IHByb3BvcnRpb24gb2YgbmVnYXRpdmUgd29yZHM/DQoNCmBgYHtyfQ0KYmluZ25lZ2F0aXZlIDwtIGdldF9zZW50aW1lbnRzKCJiaW5nIikgJT4lDQogIGZpbHRlcihzZW50aW1lbnQgPT0gIm5lZ2F0aXZlIikNCg0Kd29yZGNvdW50cyA8LSB0aWR5X2Jvb2tzICU+JQ0KICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUNCiAgc3VtbWFyaXplKHdvcmRzID0gbigpKQ0KDQp0aWR5X2Jvb2tzICU+JQ0KICBzZW1pX2pvaW4oYmluZ25lZ2F0aXZlKSAlPiUNCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lDQogIHN1bW1hcml6ZShuZWdhdGl2ZXdvcmRzID0gbigpKSAlPiUNCiAgbGVmdF9qb2luKHdvcmRjb3VudHMsIGJ5ID0gYygiYm9vayIsICJjaGFwdGVyIikpICU+JQ0KICBtdXRhdGUocmF0aW8gPSBuZWdhdGl2ZXdvcmRzIC8gd29yZHMpICU+JQ0KICBmaWx0ZXIoY2hhcHRlciAhPSAwKSAlPiUNCiAgdG9wX24oMSkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNCiMjIyBTdW1tYXJ5IA0KDQpTZW50aW1lbnQgYW5hbHlzaXMgcHJvdmlkZXMgYSB3YXkgdG8gdW5kZXJzdGFuZCB0aGUgYXR0aXR1ZGVzIGFuZCBvcGluaW9ucyBleHByZXNzZWQgaW4gdGV4dHMuIEluIHRoaXMgYW5hbHlzaXMsIHdlIGV4cGxvcmVkIGhvdyB0byBhcHByb2FjaCBzZW50aW1lbnQgYW5hbHlzaXMgdXNpbmcgdGlkeSBkYXRhIHByaW5jaXBsZXM7IHdoZW4gdGV4dCBkYXRhIGlzIGluIGEgdGlkeSBkYXRhIHN0cnVjdHVyZSwgc2VudGltZW50IGFuYWx5c2lzIGNhbiBiZSBpbXBsZW1lbnRlZCBhcyBhbiBpbm5lciBqb2luLiBXZSBjYW4gdXNlIHNlbnRpbWVudCBhbmFseXNpcyB0byB1bmRlcnN0YW5kIGhvdyBhIG5hcnJhdGl2ZSBhcmMgY2hhbmdlcyB0aHJvdWdob3V0IGl0cyBjb3Vyc2Ugb3Igd2hhdCB3b3JkcyB3aXRoIGVtb3Rpb25hbCBhbmQgb3BpbmlvbiBjb250ZW50IGFyZSBpbXBvcnRhbnQgZm9yIGEgcGFydGljdWxhciB0ZXh0LiANCg0KDQojIyBTZWxmIC0gRXhwbG9yYXRpb24NCg0KIyMjIEhhcnJ5IFBvdHRlciAtIFNlbnRpbWVudCBBbmFseXNpcw0KDQpXZSB3aWxsIGV4dGVuZCB0aGlzIGFuYWx5c2lzIGJ5IHVzaW5nIHRoZSBzYW1lIHRlY2huaXF1ZXMgZXhwbG9yZWQgYmVmb3JlIGFuZCBhcHBseWluZyB0aGVtIHRvIHRoZSBIYXJyeSBQb3R0ZXIgYm9va3MuDQoNCkkgaWRlbnRpZmllZCB0aGlzIGxpYnJhcnk6IFtIYXJyeSBQb3R0ZXIgQm9va3NdKGh0dHBzOi8vZ2l0aHViLmNvbS9icmFkbGV5Ym9laG1rZS9oYXJyeXBvdHRlcikgd2hpY2ggYWxsb3dzIHVzIGFjY2VzcyB0byB0aGUgd2hvbGUgSGFycnkgUG90dGVyIHRleHRzLg0KDQoqKlRvIEluc3RhbGwgdXNlOiAqKg0KYGBge3IgZXZhbD1GQUxTRX0NCmlmIChwYWNrYWdlVmVyc2lvbigiZGV2dG9vbHMiKSA8IDEuNikgew0KICBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpDQp9DQoNCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYnJhZGxleWJvZWhta2UvaGFycnlwb3R0ZXIiKQ0KYGBgDQoNCioqU3RhcnQgQW5hbHlzaXMqKg0KYGBge3J9DQpsaWJyYXJ5KGhhcnJ5cG90dGVyKQ0KYGBgDQoNClRoZSBib29rcyBhcmUgc3RvcmVkIGFzIGNoYXJhY3RlciB2ZWN0b3JzIHNvIHRoZSBmaXJzdCBzdGVwIGlzIHRvIGdldCB0aGVtIGFzIGRhdGEgZnJhbWVzLiAgSSBnb3QgdGhlbSBpbnRvIHNlcGFyYXRlIGRhdGFmcmFtZXMsIHRoZW4gdXNlZCBgcmJpbmRgIHRvIG1ha2UgYSBzaW5ndWxhciBkYXRhIGZyYW1lLiANCg0KYGBge3J9DQojIFRoZSBib29rcyBhcmUgc3RvcmVkIGFzIGNoYXJhY3RlciB2ZWN0b3JzIHNvIA0KIyB3ZSBuZWVkIHRvIGdldCB0aGVtIGludG8gZGF0YWZyYW1lcw0KIA0KaHAxIDwtIGFzLmRhdGEuZnJhbWUocGhpbG9zb3BoZXJzX3N0b25lKSAlPiUNCiBtdXRhdGUoDQogICBib29rID0gIjFfcGhpbG9zb3BoZXJzX3N0b25lIiwNCiAgY2hhcHRlciA9IHJvd19udW1iZXIoKSwNCiAgKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCBwaGlsb3NvcGhlcnNfc3RvbmUpDQoNCmhwMiA8LSBhcy5kYXRhLmZyYW1lKGNoYW1iZXJfb2Zfc2VjcmV0cykgJT4lDQogbXV0YXRlKA0KICAgYm9vayA9ICIyX2NoYW1iZXJfb2Zfc2VjcmV0cyIsDQogIGNoYXB0ZXIgPSByb3dfbnVtYmVyKCksDQogICkgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgY2hhbWJlcl9vZl9zZWNyZXRzKQ0KDQpocDMgPC0gYXMuZGF0YS5mcmFtZShwcmlzb25lcl9vZl9hemthYmFuKSAlPiUNCiBtdXRhdGUoDQogICBib29rID0gIjNfcHJpc29uZXJfb2ZfYXprYWJhbiIsDQogIGNoYXB0ZXIgPSByb3dfbnVtYmVyKCksDQogICkgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgcHJpc29uZXJfb2ZfYXprYWJhbikNCg0KaHA0IDwtIGFzLmRhdGEuZnJhbWUoZ29ibGV0X29mX2ZpcmUpICU+JQ0KIG11dGF0ZSgNCiAgIGJvb2sgPSAiNF9nb2JsZXRfb2ZfZmlyZSIsDQogIGNoYXB0ZXIgPSByb3dfbnVtYmVyKCksDQogICkgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgZ29ibGV0X29mX2ZpcmUpDQoNCmhwNSA8LSBhcy5kYXRhLmZyYW1lKG9yZGVyX29mX3RoZV9waG9lbml4KSAlPiUNCiBtdXRhdGUoDQogICBib29rID0gIjVfb3JkZXJfb2ZfdGhlX3Bob2VuaXgiLA0KICBjaGFwdGVyID0gcm93X251bWJlcigpLA0KICApICU+JQ0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIG9yZGVyX29mX3RoZV9waG9lbml4KQ0KDQpocDYgPC0gYXMuZGF0YS5mcmFtZShoYWxmX2Jsb29kX3ByaW5jZSkgJT4lDQogbXV0YXRlKA0KICAgYm9vayA9ICI2X2hhbGZfYmxvb2RfcHJpbmNlIiwNCiAgY2hhcHRlciA9IHJvd19udW1iZXIoKSwNCiAgKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCBoYWxmX2Jsb29kX3ByaW5jZSkNCg0KaHA3IDwtIGFzLmRhdGEuZnJhbWUoZGVhdGhseV9oYWxsb3dzKSAlPiUNCiBtdXRhdGUoDQogICBib29rID0gIjdfZGVhdGhseV9oYWxsb3dzIiwNCiAgY2hhcHRlciA9IHJvd19udW1iZXIoKSwNCiAgKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCBkZWF0aGx5X2hhbGxvd3MpDQoNCmhwX2Jvb2tzPC1yYmluZChocDEsIGhwMiwgaHAzLCBocDQsIGhwNSwgaHA2LCBocDcpDQoNCmBgYA0KDQoqKiBBbmFseXplIHRoZSBzZW50aW1lbnRzIGJ5IHVzaW5nIGJpbmcqKg0KYGBge3J9DQpocF9zZW50aW1lbnQgPC0gaHBfYm9va3MgJT4lDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lDQogIGNvdW50KGJvb2ssIGNoYXB0ZXIsIHNlbnRpbWVudCkgJT4lDQogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQpgYGANCg0KVXNpbmcgdGhlIHBhY2thZ2UgdmlyaWRpcyBmb3Igc3R5bGluZy4gDQoNCmBgYHtyfQ0KbGlicmFyeSh2aXJpZGlzKQ0KZ2dwbG90KGhwX3NlbnRpbWVudCwgYWVzKGNoYXB0ZXIsIHNlbnRpbWVudCwgZmlsbCA9IGJvb2spKSArDQogICAgICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICAgICAgIGZhY2V0X3dyYXAofmJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKw0KICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEzKSArDQogICAgICAgIGxhYnModGl0bGUgPSAiU2VudGltZW50IGluIEhhcnJ5IFBvdHRlciBOb3ZlbHMiLA0KICAgICAgICAgICAgIHkgPSAiU2VudGltZW50IikgKw0KICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXMoZW5kID0gMC43NSwgZGlzY3JldGU9VFJVRSwgZGlyZWN0aW9uID0gLTEpICsNCiAgICAgICAgc2NhbGVfeF9kaXNjcmV0ZShleHBhbmQ9YygwLjAyLDApKSArDQogICAgICAgIHRoZW1lKHN0cmlwLnRleHQ9ZWxlbWVudF90ZXh0KGhqdXN0PTApKSArDQogICAgICAgIHRoZW1lKHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJpdGFsaWMiKSkgKw0KICAgICAgICB0aGVtZShheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpKSArDQogICAgICAgIHRoZW1lKGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpICsNCiAgICAgICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpKQ0KDQpgYGANCg0KQmFzZWQgb24gdGhpcyBncmFwaCwgaXQgd291bGQgc2VlbSB0aGUgSGFycnkgUG90dGVyIGJvb2sgb3ZlcmFsbCBzZW50aW1lbnQgaXMgbmVnYXRpdmUuIA0KDQoqKkZpbmRpbmcgdGhlIG1vc3QgcG9zaXRpdmUgY2hhcHRlcnMgaW4gdGhlIGJvb2tzKioNCg0KV2l0aCB0aGUgSmFuZSBBdXN0ZW4gbm92ZWxzLCB3aGljaCB3ZXJlIG1vc3RseSBwb3NpdGl2ZSwgd2UgdHJpZWQgdG8gdGFrZSBhIGxvb2sgYXQgdGhlIG1vc3RseSBuZWdhdGl2ZSBjaGFwdGVycy4gIEZvciB0aGUgSGFycnkgUG90dGVyIGJvb2tzLCB3ZSdsbCB0cnkgdG8gZmluZCB0aGUgbW9zdCBwb3NpdGl2ZSBjaGFwdGVycy4gDQoNCmBgYHtyfQ0KYmluZ3Bvc2l0aXZlIDwtIGdldF9zZW50aW1lbnRzKCJiaW5nIikgJT4lDQogICAgICAgIGZpbHRlcihzZW50aW1lbnQgPT0gInBvc2l0aXZlIikNCg0Kd29yZGNvdW50cyA8LSBocF9ib29rcyAlPiUNCiAgICAgICAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lDQogICAgICAgIHN1bW1hcml6ZSh3b3JkcyA9IG4oKSkNCg0KaHBfYm9va3MgJT4lDQogICAgICAgIHNlbWlfam9pbihiaW5ncG9zaXRpdmUpICU+JQ0KICAgICAgICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUNCiAgICAgICAgc3VtbWFyaXplKHBvc2l0aXZld29yZHMgPSBuKCkpICU+JQ0KICAgICAgICBsZWZ0X2pvaW4od29yZGNvdW50cywgYnkgPSBjKCJib29rIiwgImNoYXB0ZXIiKSkgJT4lDQogICAgICAgIG11dGF0ZShyYXRpbyA9IHBvc2l0aXZld29yZHMvd29yZHMpICU+JQ0KICAgICAgICBmaWx0ZXIoY2hhcHRlciAhPSAwKSAlPiUNCiAgICAgICAgdG9wX24oMSkNCmBgYA0KDQpCWSBsb29raW5nIGF0IHRoaXMgdGFibGUsIHdlIHNlZSB3aGljaCB0aGUgbW9zdCBwb3NpdGl2ZSBjaGFwdGVycyBvZiBlYWNoIGJvb2sgYXJlLiAgQ2hhcHRlciA1IG9uIGJvb2sgMSBpcyB3aGVuIEhhcnJ5IGRpc2NvdmVycyB0aGUgd29uZGVyZnVsIHdvcmxkIG9mIG1hZ2ljIGFuZCB0cmF2ZWxzIHdpdGggSGFncmlkIHRvIERpYWdvbiBBbGxleS4gIEluIERlYXRobHkgSGFsbG93cywgY2hhcHRlciAzNSwgS2luZydzIENyb3NzIGlzIHRoZSBjYWxtIGJlZm9yZSB0aGUgc3Rvcm0uICBBZnRlciBWb2xkZW1vcnQgImtpbGxzIiBIYXJyeSwgaGUgd2FrZXMgYXQgS2luZydzIENyb3NzIHN0YXRpb24gYW5kIGhhcyBvbmUgbGFzdCBtZWV0aW5nIHdpdGggRHVtYmxlZG9yZS4gDQoNCiMjIyBXb3JkQ2xvdWQNCkxldCdzIGdlbmVyYXRlIGEgd29yZCBjbG91ZCBmcm9tIEhhcnJ5IFBvdHRlcidzIGJvb2tzLg0KDQpgYGB7cn0NCiNlbGltaW5hdGUgdGhlIG1vc3QgY29tbW9uIG5hbWVzIGZyb20gdGhlIHdvcmRjbG91ZA0KY3VzdG9tX3N0b3Bfd29yZHMgPC0gYmluZF9yb3dzKA0KICB0aWJibGUoDQogICAgd29yZCA9IGMoImhhcnJ5IiwgInBvdHRlciIsICJoZXJtaW9uZSIsICJyb24iLCAiZHVtYmxlZG9yZSIsICJ2b2xkZW1vcnQiKSwNCiAgICBsZXhpY29uID0gYygiY3VzdG9tIikNCiAgKSwNCiAgc3RvcF93b3Jkcw0KKQ0KDQpzdXBwcmVzc1dhcm5pbmdzKGhwX2Jvb2tzICU+JQ0KICBhbnRpX2pvaW4oY3VzdG9tX3N0b3Bfd29yZHMpICU+JQ0KICBjb3VudCh3b3JkKSAlPiUNCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMTAwKSkpDQpgYGANCg0KDQojIyBDb25jbHVzaW9uDQoNCldlIGNhbiBzZWUgc29tZSBvZiB0aGUgd29yZHMgbGlrZSAqKmRhcmsqKiwgKipoYXJkKiosICoqZmVsbCoqIGFuZCAqKm5pZ2h0KiogYmUgc29tZSBvZiB0aGUgbW9zdCBjb21tb24gb25lcy4gIE5vIHdvbmRlciB0aGUgb3ZlcmFsbCBzZW50aW1lbnQgb2YgSGFycnkgUG90dGVyIGlzIG5lZ2F0aXZlISANCg0KYGBge3J9DQpocF93b3JkX2NvdW50cyA8LSBocF9ib29rcyAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQpocF93b3JkX2NvdW50cyAlPiUNCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCiAgdG9wX24oMTApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQogIGdncGxvdChhZXMod29yZCwgbiwgZmlsbCA9IHNlbnRpbWVudCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGxhYnMoDQogICAgeSA9ICJDb250cmlidXRpb24gdG8gc2VudGltZW50IiwNCiAgICB4ID0gTlVMTA0KICApICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KSWYgd2UgcGVyZm9ybSBhIGxvb3AgYXQgdGhlIG1vc3QgdXNlZCBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgd29yZHMsIHdlIHNlZSB0aGF0LCBldmVuIHRob3VnaCB0aGUgb3ZlcmFsbCBzZW50aW1lbnQgb2YgdGhlIGJvb2tzIGlzIG5lZ2F0aXZlLCB0aGUgbW9zdCB1c2VkIHdvcmRzIGhhdmUgYSBwb3NpdGl2ZSBjaGFyZ2UuICBUaGlzIG1pZ2h0IGhhdmUgc29tZXRoaW5nIHRvIGRvIHdpdGggdGhlIGJvb2sncyBwb3B1bGFyaXR5IGFuZCBzZW5zZSBvZiB1cGxpZnRpbmcgbWVzc2FnZXMuIA0KDQpFdmVuIHRob3VnaCB0aGUgSGFycnkgUG90dGVyIHNlcmllcyB0YXJnZXQgYXVkaWVuY2UgaXMgdGVlbmFnZXJzIGFuZCB5b3VuZyBhZHVsdHMsIHNvbWUgb2YgdGhlIHRoZW1lcyBpdCBkZWFscyB3aXRoOiBwcmVqdWRpY2UsIG11cmRlciwgbWlzdHJlYXRtZW50IG9mIGNoaWxkcmVuLCBkZWF0aCBhbmQgbG9zcywgY2FuIGJlIHJlYWxseSBoYXJkIGFuZCBkYXJrLiAgSXQgY29tZXMgYXMgbm8gc3VycHJpc2UgdGhhdCB0aGUgb3ZlcmFsbCBzZW50aW1lbnQgb2YgdGhlIGJvb2tzIGlzIGRlZW1lZCBhcyBuZWdhdGl2ZS4gDQoNCg0KLi4u