Objective

In Text Mining with R, Chapter 2 looks at Sentiment Analysis. In this assignment, you should start by getting the primary example code from chapter 2 working in an R Markdown document. You should provide a citation to this base code.

You’re then asked to extend the code in two ways: Work with a different corpus of your choosing, and Incorporate at least one additional sentiment lexicon (possibly from another R package that you’ve found through research).

Citation: Text Mining with R, Chapter 2: https://www.tidytextmining.com/sentiment.html

Load Packages

library(tidyverse)
library(openintro)

2.1 The sentiments datasets

#install.packages('tidytext')
#install.packages('textdata')
library(tidytext)
## Warning: package 'tidytext' was built under R version 4.3.3
get_sentiments("afinn")
## # A tibble: 2,477 × 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
## # ℹ 2,467 more rows
get_sentiments("bing")
## # A tibble: 6,786 × 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 
## # ℹ 6,776 more rows
get_sentiments("nrc")
## # A tibble: 13,872 × 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     
## # ℹ 13,862 more rows

2.2 Sentiment analysis with inner join

library(janeaustenr)
## Warning: package 'janeaustenr' was built under R version 4.3.3
library(dplyr)
library(stringr)

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)
nrc_joy <- get_sentiments("nrc") %>% 
  filter(sentiment == "joy")

tidy_books %>%
  filter(book == "Emma") %>%
  inner_join(nrc_joy) %>%
  count(word, sort = TRUE)
## Joining with `by = join_by(word)`
## # A tibble: 301 × 2
##    word          n
##    <chr>     <int>
##  1 good        359
##  2 friend      166
##  3 hope        143
##  4 happy       125
##  5 love        117
##  6 deal         92
##  7 found        92
##  8 present      89
##  9 kind         82
## 10 happiness    76
## # ℹ 291 more rows
library(tidyr)

jane_austen_sentiment <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(book, index = linenumber %/% 80, sentiment) %>%
  pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>% 
  mutate(sentiment = positive - negative)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 435434 of `x` matches multiple rows in `y`.
## ℹ Row 5051 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
library(ggplot2)

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

### 2.3 Comparing the three sentiment dictionaries

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

pride_prejudice
## # A tibble: 122,204 × 4
##    book              linenumber chapter word     
##    <fct>                  <int>   <int> <chr>    
##  1 Pride & Prejudice          1       0 pride    
##  2 Pride & Prejudice          1       0 and      
##  3 Pride & Prejudice          1       0 prejudice
##  4 Pride & Prejudice          3       0 by       
##  5 Pride & Prejudice          3       0 jane     
##  6 Pride & Prejudice          3       0 austen   
##  7 Pride & Prejudice          7       1 chapter  
##  8 Pride & Prejudice          7       1 1        
##  9 Pride & Prejudice         10       1 it       
## 10 Pride & Prejudice         10       1 is       
## # ℹ 122,194 more rows
afinn <- pride_prejudice %>% 
  inner_join(get_sentiments("afinn")) %>% 
  group_by(index = linenumber %/% 80) %>% 
  summarise(sentiment = sum(value)) %>% 
  mutate(method = "AFINN")
## Joining with `by = join_by(word)`
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) %>%
  pivot_wider(names_from = sentiment,
              values_from = n,
              values_fill = 0) %>% 
  mutate(sentiment = positive - negative)
## Joining with `by = join_by(word)`
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("nrc") %>% filter(sentiment %in% : Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 215 of `x` matches multiple rows in `y`.
## ℹ Row 5178 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
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")

get_sentiments("nrc") %>% 
  filter(sentiment %in% c("positive", "negative")) %>% 
  count(sentiment)
## # A tibble: 2 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   3316
## 2 positive   2308
get_sentiments("bing") %>% 
  count(sentiment)
## # A tibble: 2 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   4781
## 2 positive   2005

2.4 Most common positive and negative words

bing_word_counts <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 435434 of `x` matches multiple rows in `y`.
## ℹ Row 5051 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
bing_word_counts
## # A tibble: 2,585 × 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
## # ℹ 2,575 more rows
bing_word_counts %>%
  group_by(sentiment) %>%
  slice_max(n, n = 10) %>% 
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(n, word, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(x = "Contribution to sentiment",
       y = NULL)

custom_stop_words <- bind_rows(tibble(word = c("miss"),  
                                      lexicon = c("custom")), 
                               stop_words)

custom_stop_words
## # A tibble: 1,150 × 2
##    word        lexicon
##    <chr>       <chr>  
##  1 miss        custom 
##  2 a           SMART  
##  3 a's         SMART  
##  4 able        SMART  
##  5 about       SMART  
##  6 above       SMART  
##  7 according   SMART  
##  8 accordingly SMART  
##  9 across      SMART  
## 10 actually    SMART  
## # ℹ 1,140 more rows

2.5 Wordclouds

library(wordcloud)
## Warning: package 'wordcloud' was built under R version 4.3.3
## Loading required package: RColorBrewer
tidy_books %>%
  anti_join(stop_words) %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100))
## Joining with `by = join_by(word)`

library(reshape2)
## Warning: package 'reshape2' was built under R version 4.3.3
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:openintro':
## 
##     tips
## The following object is masked from 'package:tidyr':
## 
##     smiths
tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("gray20", "gray80"),
                   max.words = 100)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 435434 of `x` matches multiple rows in `y`.
## ℹ Row 5051 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

### 2.6 Looking at units beyond just words

p_and_p_sentences <- tibble(text = prideprejudice) %>% 
  unnest_tokens(sentence, text, token = "sentences")
p_and_p_sentences$sentence[2]
## [1] "by jane austen"
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())
## # A tibble: 6 × 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
bingnegative <- get_sentiments("bing") %>% 
  filter(sentiment == "negative")

wordcounts <- tidy_books %>%
  group_by(book, chapter) %>%
  summarize(words = n())
## `summarise()` has grouped output by 'book'. You can override using the
## `.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) %>%
  slice_max(ratio, n = 1) %>% 
  ungroup()
## Joining with `by = join_by(word)`
## `summarise()` has grouped output by 'book'. You can override using the
## `.groups` argument.
## # A tibble: 6 × 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

Selecting a Different Corpus

I selected the corpus: The Beautiful and Damned from the gutenbergr library.

library(gutenbergr)
## Warning: package 'gutenbergr' was built under R version 4.3.3
# Get metadata for all available works in the Gutenberg corpus
gutenberg_metadata <- gutenberg_works()
# Download the text of "The Beautiful and Damned" using its Gutenberg ID
beautiful_and_damned_text <- gutenberg_download(9830)
## Determining mirror for Project Gutenberg from https://www.gutenberg.org/robot/harvest
## Using mirror http://aleph.gutenberg.org
# View the first few lines of the text
head(beautiful_and_damned_text)
## # A tibble: 6 × 2
##   gutenberg_id text                      
##          <int> <chr>                     
## 1         9830 "THE BEAUTIFUL AND DAMNED"
## 2         9830 ""                        
## 3         9830 "BY F. SCOTT FITZGERALD"  
## 4         9830 ""                        
## 5         9830 "1922"                    
## 6         9830 ""
# Now you can proceed with your sentiment analysis using the downloaded text

Filtering the Loughran Lexicon

Lexicon Filtering: I filtered the Loughran sentiment lexicon to include only positive and negative words. This lexicon is used for sentiment analysis on the selected corpus.

#filtering the loughran lexicon only for positive an negative words
loughran_posneg <- get_sentiments("loughran") %>% 
  filter(sentiment == "positive" | sentiment =="negative")

Sentiment Analysis

Tokenization: The text is tokenized into individual words, making it easier to analyze sentiment on a word-by-word basis.

# Tokenize the text into words
beautiful_and_damned_words <- beautiful_and_damned_text %>%
  unnest_tokens(word, text)
beautiful_and_damned_words
## # A tibble: 125,941 × 2
##    gutenberg_id word      
##           <int> <chr>     
##  1         9830 the       
##  2         9830 beautiful 
##  3         9830 and       
##  4         9830 damned    
##  5         9830 by        
##  6         9830 f         
##  7         9830 scott     
##  8         9830 fitzgerald
##  9         9830 1922      
## 10         9830 _novels_  
## # ℹ 125,931 more rows

Joining with Lexicon:

In this part, the tokenized words are joined with the filtered Loughran sentiment lexicon to associate sentiment scores with each word in the corpus.

# Join with the Loughran sentiment lexicon
beautiful_and_damned_sentiments <- beautiful_and_damned_words %>%
  inner_join(loughran_posneg)
## Joining with `by = join_by(word)`
beautiful_and_damned_sentiments
## # A tibble: 3,089 × 3
##    gutenberg_id word          sentiment
##           <int> <chr>         <chr>    
##  1         9830 beautiful     positive 
##  2         9830 great         positive 
##  3         9830 beautiful     positive 
##  4         9830 encouragement positive 
##  5         9830 broken        negative 
##  6         9830 honor         positive 
##  7         9830 obscene       negative 
##  8         9830 exceptional   positive 
##  9         9830 pleasant      positive 
## 10         9830 attractive    positive 
## # ℹ 3,079 more rows

Sentiment Counting:

Count the occurrences of positive and negative words in the corpus to understand the overall sentiment distribution.

# Count the occurrences of positive and negative words
sentiment_counts <- beautiful_and_damned_sentiments %>%
  count(sentiment)
sentiment_counts
## # A tibble: 2 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   2008
## 2 positive   1081

Sentiment Visualization:

The sentiment distribution is visualized using a bar plot, where each sentiment (positive/negative) is represented along with its frequency count

# Visualize the sentiment distribution with value counts
ggplot(sentiment_counts, aes(x = sentiment, y = n, fill = sentiment)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = n), vjust = -0.5, color = "black", size = 3) + # Add value counts
  labs(title = "Sentiment Distribution in 'The Beautiful and Damned'",
       x = "Sentiment",
       y = "Frequency") +
  theme_minimal() +
  theme(legend.position = "none")

Word Clouds

Positive Sentiment Word Cloud:

I created a word cloud visualization for words associated with positive sentiment in the corpus, showing the most frequently occurring positive words.

# Create wordclouds for positive and negative sentiments
positive_words <- beautiful_and_damned_sentiments %>%
  filter(sentiment == "positive") %>%
  count(word, sort = TRUE)
print(positive_words)
## # A tibble: 147 × 2
##    word           n
##    <chr>      <int>
##  1 good         120
##  2 great         99
##  3 beautiful     52
##  4 better        47
##  5 best          42
##  6 pleasant      26
##  7 strong        22
##  8 tremendous    22
##  9 happy         21
## 10 success       20
## # ℹ 137 more rows
# Plot wordcloud for positive sentiment
wordcloud(positive_words$word, positive_words$n,
          max.words = 100, scale=c(3,0.5),
          colors = brewer.pal(8, "Dark2"),
          random.order = FALSE,
          rot.per = 0.35,
          main = "Wordcloud for Positive Sentiment")

Negative Sentiment Word Cloud:

Similarly, I created a word cloud visualization for words associated with negative sentiment in the corpus, showing the most frequently occurring negative words.

negative_words <- beautiful_and_damned_sentiments %>%
  filter(sentiment == "negative") %>%
  count(word, sort = TRUE)
print(negative_words)
## # A tibble: 530 × 2
##    word            n
##    <chr>       <int>
##  1 late           63
##  2 against        59
##  3 broken         40
##  4 lost           31
##  5 poor           29
##  6 question       28
##  7 bad            26
##  8 closed         24
##  9 interrupted    24
## 10 miss           22
## # ℹ 520 more rows
# Plot wordcloud for negative sentiment
wordcloud(negative_words$word, negative_words$n,
          max.words = 100, scale=c(3,0.5),
          colors = brewer.pal(8, "Dark2"),
          random.order = FALSE,
          rot.per = 0.35,
          main = "Wordcloud for Negative Sentiment")

Conclusion

In this extended R Markdown document, I have retained the base code for sentiment analysis with Jane Austen’s texts and incorporated additional analysis with “The Beautiful and Damned” by F. Scott Fitzgerald. Additionally, I have included sentiment analysis using the Loughran lexicon for “The Beautiful and Damned” text. The base code used in this R markdown document is from the “Text Mining with R” book, Chapter 2.

LS0tDQp0aXRsZTogIk5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyINCmF1dGhvcjogIlB1amEgUm95Ig0KZGF0ZTogIjMvMzEvMjQiDQpvdXRwdXQ6IG9wZW5pbnRybzo6bGFiX3JlcG9ydA0KLS0tDQoNCiMjIyBPYmplY3RpdmUNCg0KSW4gVGV4dCBNaW5pbmcgd2l0aCBSLCBDaGFwdGVyIDIgbG9va3MgYXQgU2VudGltZW50IEFuYWx5c2lzLiAgSW4gdGhpcyBhc3NpZ25tZW50LCB5b3Ugc2hvdWxkIHN0YXJ0IGJ5IGdldHRpbmcgdGhlIHByaW1hcnkgZXhhbXBsZSBjb2RlIGZyb20gY2hhcHRlciAyIHdvcmtpbmcgaW4gYW4gUiBNYXJrZG93biBkb2N1bWVudC4gIFlvdSBzaG91bGQgcHJvdmlkZSBhIGNpdGF0aW9uIHRvIHRoaXMgYmFzZSBjb2RlLiAgDQoNCllvdeKAmXJlIHRoZW4gYXNrZWQgdG8gZXh0ZW5kIHRoZSBjb2RlIGluIHR3byB3YXlzOg0KV29yayB3aXRoIGEgZGlmZmVyZW50IGNvcnB1cyBvZiB5b3VyIGNob29zaW5nLCBhbmQNCkluY29ycG9yYXRlIGF0IGxlYXN0IG9uZSBhZGRpdGlvbmFsIHNlbnRpbWVudCBsZXhpY29uIChwb3NzaWJseSBmcm9tIGFub3RoZXIgUiBwYWNrYWdlIHRoYXQgeW914oCZdmUgZm91bmQgdGhyb3VnaCByZXNlYXJjaCkuDQoNCkNpdGF0aW9uOiBUZXh0IE1pbmluZyB3aXRoIFIsIENoYXB0ZXIgMjogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3NlbnRpbWVudC5odG1sDQoNCg0KIyMjIExvYWQgUGFja2FnZXMNCmBgYHtyIGxvYWQtcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkob3BlbmludHJvKQ0KYGBgDQoNCiMjIyAyLjEgVGhlIHNlbnRpbWVudHMgZGF0YXNldHMNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoJ3RpZHl0ZXh0JykNCiNpbnN0YWxsLnBhY2thZ2VzKCd0ZXh0ZGF0YScpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KDQpnZXRfc2VudGltZW50cygiYWZpbm4iKQ0KYGBgDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJiaW5nIikNCmBgYA0KYGBge3J9DQpnZXRfc2VudGltZW50cygibnJjIikNCmBgYA0KDQojIyMgMi4yIFNlbnRpbWVudCBhbmFseXNpcyB3aXRoIGlubmVyIGpvaW4NCmBgYHtyfQ0KbGlicmFyeShqYW5lYXVzdGVucikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHN0cmluZ3IpDQoNCnRpZHlfYm9va3MgPC0gYXVzdGVuX2Jvb2tzKCkgJT4lDQogIGdyb3VwX2J5KGJvb2spICU+JQ0KICBtdXRhdGUoDQogICAgbGluZW51bWJlciA9IHJvd19udW1iZXIoKSwNCiAgICBjaGFwdGVyID0gY3Vtc3VtKHN0cl9kZXRlY3QodGV4dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4KCJeY2hhcHRlciBbXFxkaXZ4bGNdIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gVFJVRSkpKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KQ0KYGBgDQoNCmBgYHtyfQ0KbnJjX2pveSA8LSBnZXRfc2VudGltZW50cygibnJjIikgJT4lIA0KICBmaWx0ZXIoc2VudGltZW50ID09ICJqb3kiKQ0KDQp0aWR5X2Jvb2tzICU+JQ0KICBmaWx0ZXIoYm9vayA9PSAiRW1tYSIpICU+JQ0KICBpbm5lcl9qb2luKG5yY19qb3kpICU+JQ0KICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCmBgYA0KYGBge3J9DQpsaWJyYXJ5KHRpZHlyKQ0KDQpqYW5lX2F1c3Rlbl9zZW50aW1lbnQgPC0gdGlkeV9ib29rcyAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgY291bnQoYm9vaywgaW5kZXggPSBsaW5lbnVtYmVyICUvJSA4MCwgc2VudGltZW50KSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNlbnRpbWVudCwgdmFsdWVzX2Zyb20gPSBuLCB2YWx1ZXNfZmlsbCA9IDApICU+JSANCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQpgYGANCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpnZ3Bsb3QoamFuZV9hdXN0ZW5fc2VudGltZW50LCBhZXMoaW5kZXgsIHNlbnRpbWVudCwgZmlsbCA9IGJvb2spKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+Ym9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKQ0KYGBgDQojIyMgMi4zIENvbXBhcmluZyB0aGUgdGhyZWUgc2VudGltZW50IGRpY3Rpb25hcmllcw0KYGBge3J9DQpwcmlkZV9wcmVqdWRpY2UgPC0gdGlkeV9ib29rcyAlPiUgDQogIGZpbHRlcihib29rID09ICJQcmlkZSAmIFByZWp1ZGljZSIpDQoNCnByaWRlX3ByZWp1ZGljZQ0KYGBgDQpgYGB7cn0NCmFmaW5uIDwtIHByaWRlX3ByZWp1ZGljZSAlPiUgDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpICU+JSANCiAgZ3JvdXBfYnkoaW5kZXggPSBsaW5lbnVtYmVyICUvJSA4MCkgJT4lIA0KICBzdW1tYXJpc2Uoc2VudGltZW50ID0gc3VtKHZhbHVlKSkgJT4lIA0KICBtdXRhdGUobWV0aG9kID0gIkFGSU5OIikNCg0KYmluZ19hbmRfbnJjIDwtIGJpbmRfcm93cygNCiAgcHJpZGVfcHJlanVkaWNlICU+JSANCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICAgIG11dGF0ZShtZXRob2QgPSAiQmluZyBldCBhbC4iKSwNCiAgcHJpZGVfcHJlanVkaWNlICU+JSANCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSAlPiUgDQogICAgICAgICAgICAgICAgIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKCJwb3NpdGl2ZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibmVnYXRpdmUiKSkNCiAgICApICU+JQ0KICAgIG11dGF0ZShtZXRob2QgPSAiTlJDIikpICU+JQ0KICBjb3VudChtZXRob2QsIGluZGV4ID0gbGluZW51bWJlciAlLyUgODAsIHNlbnRpbWVudCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzZW50aW1lbnQsDQogICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gbiwNCiAgICAgICAgICAgICAgdmFsdWVzX2ZpbGwgPSAwKSAlPiUgDQogIG11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlKQ0KYGBgDQpgYGB7cn0NCmJpbmRfcm93cyhhZmlubiwgDQogICAgICAgICAgYmluZ19hbmRfbnJjKSAlPiUNCiAgZ2dwbG90KGFlcyhpbmRleCwgc2VudGltZW50LCBmaWxsID0gbWV0aG9kKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofm1ldGhvZCwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKQ0KYGBgDQoNCmBgYHtyfQ0KZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSANCiAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSANCiAgY291bnQoc2VudGltZW50KQ0KYGBgDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJiaW5nIikgJT4lIA0KICBjb3VudChzZW50aW1lbnQpDQpgYGANCiMjIyAyLjQgTW9zdCBjb21tb24gcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmRzDQpgYGB7cn0NCmJpbmdfd29yZF9jb3VudHMgPC0gdGlkeV9ib29rcyAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQpiaW5nX3dvcmRfY291bnRzDQpgYGANCmBgYHtyfQ0KYmluZ193b3JkX2NvdW50cyAlPiUNCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCiAgc2xpY2VfbWF4KG4sIG4gPSAxMCkgJT4lIA0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQogIGdncGxvdChhZXMobiwgd29yZCwgZmlsbCA9IHNlbnRpbWVudCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGxhYnMoeCA9ICJDb250cmlidXRpb24gdG8gc2VudGltZW50IiwNCiAgICAgICB5ID0gTlVMTCkNCmBgYA0KYGBge3J9DQpjdXN0b21fc3RvcF93b3JkcyA8LSBiaW5kX3Jvd3ModGliYmxlKHdvcmQgPSBjKCJtaXNzIiksICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV4aWNvbiA9IGMoImN1c3RvbSIpKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcF93b3JkcykNCg0KY3VzdG9tX3N0b3Bfd29yZHMNCmBgYA0KDQojIyMgMi41IFdvcmRjbG91ZHMNCmBgYHtyfQ0KbGlicmFyeSh3b3JkY2xvdWQpDQoNCnRpZHlfYm9va3MgJT4lDQogIGFudGlfam9pbihzdG9wX3dvcmRzKSAlPiUNCiAgY291bnQod29yZCkgJT4lDQogIHdpdGgod29yZGNsb3VkKHdvcmQsIG4sIG1heC53b3JkcyA9IDEwMCkpDQpgYGANCmBgYHtyfQ0KbGlicmFyeShyZXNoYXBlMikNCg0KdGlkeV9ib29rcyAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQogIGFjYXN0KHdvcmQgfiBzZW50aW1lbnQsIHZhbHVlLnZhciA9ICJuIiwgZmlsbCA9IDApICU+JQ0KICBjb21wYXJpc29uLmNsb3VkKGNvbG9ycyA9IGMoImdyYXkyMCIsICJncmF5ODAiKSwNCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAxMDApDQpgYGANCiMjIyAyLjYgTG9va2luZyBhdCB1bml0cyBiZXlvbmQganVzdCB3b3Jkcw0KYGBge3J9DQpwX2FuZF9wX3NlbnRlbmNlcyA8LSB0aWJibGUodGV4dCA9IHByaWRlcHJlanVkaWNlKSAlPiUgDQogIHVubmVzdF90b2tlbnMoc2VudGVuY2UsIHRleHQsIHRva2VuID0gInNlbnRlbmNlcyIpDQpgYGANCg0KYGBge3J9DQpwX2FuZF9wX3NlbnRlbmNlcyRzZW50ZW5jZVsyXQ0KYGBgDQpgYGB7cn0NCmF1c3Rlbl9jaGFwdGVycyA8LSBhdXN0ZW5fYm9va3MoKSAlPiUNCiAgZ3JvdXBfYnkoYm9vaykgJT4lDQogIHVubmVzdF90b2tlbnMoY2hhcHRlciwgdGV4dCwgdG9rZW4gPSAicmVnZXgiLCANCiAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIkNoYXB0ZXJ8Q0hBUFRFUiBbXFxkSVZYTENdIikgJT4lDQogIHVuZ3JvdXAoKQ0KDQphdXN0ZW5fY2hhcHRlcnMgJT4lIA0KICBncm91cF9ieShib29rKSAlPiUgDQogIHN1bW1hcmlzZShjaGFwdGVycyA9IG4oKSkNCmBgYA0KYGBge3J9DQpiaW5nbmVnYXRpdmUgPC0gZ2V0X3NlbnRpbWVudHMoImJpbmciKSAlPiUgDQogIGZpbHRlcihzZW50aW1lbnQgPT0gIm5lZ2F0aXZlIikNCg0Kd29yZGNvdW50cyA8LSB0aWR5X2Jvb2tzICU+JQ0KICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUNCiAgc3VtbWFyaXplKHdvcmRzID0gbigpKQ0KDQp0aWR5X2Jvb2tzICU+JQ0KICBzZW1pX2pvaW4oYmluZ25lZ2F0aXZlKSAlPiUNCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lDQogIHN1bW1hcml6ZShuZWdhdGl2ZXdvcmRzID0gbigpKSAlPiUNCiAgbGVmdF9qb2luKHdvcmRjb3VudHMsIGJ5ID0gYygiYm9vayIsICJjaGFwdGVyIikpICU+JQ0KICBtdXRhdGUocmF0aW8gPSBuZWdhdGl2ZXdvcmRzL3dvcmRzKSAlPiUNCiAgZmlsdGVyKGNoYXB0ZXIgIT0gMCkgJT4lDQogIHNsaWNlX21heChyYXRpbywgbiA9IDEpICU+JSANCiAgdW5ncm91cCgpDQpgYGANCg0KIyMjIFNlbGVjdGluZyBhIERpZmZlcmVudCBDb3JwdXMNCg0KSSBzZWxlY3RlZCB0aGUgY29ycHVzOiBUaGUgQmVhdXRpZnVsIGFuZCBEYW1uZWQgZnJvbSB0aGUgZ3V0ZW5iZXJnciBsaWJyYXJ5Lg0KYGBge3J9DQpsaWJyYXJ5KGd1dGVuYmVyZ3IpDQpgYGANCg0KYGBge3J9DQojIEdldCBtZXRhZGF0YSBmb3IgYWxsIGF2YWlsYWJsZSB3b3JrcyBpbiB0aGUgR3V0ZW5iZXJnIGNvcnB1cw0KZ3V0ZW5iZXJnX21ldGFkYXRhIDwtIGd1dGVuYmVyZ193b3JrcygpDQpgYGANCg0KYGBge3J9DQojIERvd25sb2FkIHRoZSB0ZXh0IG9mICJUaGUgQmVhdXRpZnVsIGFuZCBEYW1uZWQiIHVzaW5nIGl0cyBHdXRlbmJlcmcgSUQNCmJlYXV0aWZ1bF9hbmRfZGFtbmVkX3RleHQgPC0gZ3V0ZW5iZXJnX2Rvd25sb2FkKDk4MzApDQoNCiMgVmlldyB0aGUgZmlyc3QgZmV3IGxpbmVzIG9mIHRoZSB0ZXh0DQpoZWFkKGJlYXV0aWZ1bF9hbmRfZGFtbmVkX3RleHQpDQoNCiMgTm93IHlvdSBjYW4gcHJvY2VlZCB3aXRoIHlvdXIgc2VudGltZW50IGFuYWx5c2lzIHVzaW5nIHRoZSBkb3dubG9hZGVkIHRleHQNCg0KYGBgDQoNCiMjIyBGaWx0ZXJpbmcgdGhlIExvdWdocmFuIExleGljb24NCg0KTGV4aWNvbiBGaWx0ZXJpbmc6DQpJIGZpbHRlcmVkIHRoZSBMb3VnaHJhbiBzZW50aW1lbnQgbGV4aWNvbiB0byBpbmNsdWRlIG9ubHkgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmRzLiBUaGlzIGxleGljb24gaXMgdXNlZCBmb3Igc2VudGltZW50IGFuYWx5c2lzIG9uIHRoZSBzZWxlY3RlZCBjb3JwdXMuDQoNCmBgYHtyfQ0KI2ZpbHRlcmluZyB0aGUgbG91Z2hyYW4gbGV4aWNvbiBvbmx5IGZvciBwb3NpdGl2ZSBhbiBuZWdhdGl2ZSB3b3Jkcw0KbG91Z2hyYW5fcG9zbmVnIDwtIGdldF9zZW50aW1lbnRzKCJsb3VnaHJhbiIpICU+JSANCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiIHwgc2VudGltZW50ID09Im5lZ2F0aXZlIikNCmBgYA0KDQojIyMgU2VudGltZW50IEFuYWx5c2lzDQoNClRva2VuaXphdGlvbjoNClRoZSB0ZXh0IGlzIHRva2VuaXplZCBpbnRvIGluZGl2aWR1YWwgd29yZHMsIG1ha2luZyBpdCBlYXNpZXIgdG8gYW5hbHl6ZSBzZW50aW1lbnQgb24gYSB3b3JkLWJ5LXdvcmQgYmFzaXMuDQoNCmBgYHtyfQ0KIyBUb2tlbml6ZSB0aGUgdGV4dCBpbnRvIHdvcmRzDQpiZWF1dGlmdWxfYW5kX2RhbW5lZF93b3JkcyA8LSBiZWF1dGlmdWxfYW5kX2RhbW5lZF90ZXh0ICU+JQ0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpDQpiZWF1dGlmdWxfYW5kX2RhbW5lZF93b3Jkcw0KYGBgDQoNCkpvaW5pbmcgd2l0aCBMZXhpY29uOg0KDQpJbiB0aGlzIHBhcnQsIHRoZSB0b2tlbml6ZWQgd29yZHMgYXJlIGpvaW5lZCB3aXRoIHRoZSBmaWx0ZXJlZCBMb3VnaHJhbiBzZW50aW1lbnQgbGV4aWNvbiB0byBhc3NvY2lhdGUgc2VudGltZW50IHNjb3JlcyB3aXRoIGVhY2ggd29yZCBpbiB0aGUgY29ycHVzLg0KYGBge3J9DQojIEpvaW4gd2l0aCB0aGUgTG91Z2hyYW4gc2VudGltZW50IGxleGljb24NCmJlYXV0aWZ1bF9hbmRfZGFtbmVkX3NlbnRpbWVudHMgPC0gYmVhdXRpZnVsX2FuZF9kYW1uZWRfd29yZHMgJT4lDQogIGlubmVyX2pvaW4obG91Z2hyYW5fcG9zbmVnKQ0KYmVhdXRpZnVsX2FuZF9kYW1uZWRfc2VudGltZW50cw0KYGBgDQoNClNlbnRpbWVudCBDb3VudGluZzoNCg0KQ291bnQgdGhlIG9jY3VycmVuY2VzIG9mIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyBpbiB0aGUgY29ycHVzIHRvIHVuZGVyc3RhbmQgdGhlIG92ZXJhbGwgc2VudGltZW50IGRpc3RyaWJ1dGlvbi4NCg0KYGBge3J9DQojIENvdW50IHRoZSBvY2N1cnJlbmNlcyBvZiBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgd29yZHMNCnNlbnRpbWVudF9jb3VudHMgPC0gYmVhdXRpZnVsX2FuZF9kYW1uZWRfc2VudGltZW50cyAlPiUNCiAgY291bnQoc2VudGltZW50KQ0Kc2VudGltZW50X2NvdW50cw0KYGBgDQoNClNlbnRpbWVudCBWaXN1YWxpemF0aW9uOg0KDQpUaGUgc2VudGltZW50IGRpc3RyaWJ1dGlvbiBpcyB2aXN1YWxpemVkIHVzaW5nIGEgYmFyIHBsb3QsIHdoZXJlIGVhY2ggc2VudGltZW50IChwb3NpdGl2ZS9uZWdhdGl2ZSkgaXMgcmVwcmVzZW50ZWQgYWxvbmcgd2l0aCBpdHMgZnJlcXVlbmN5IGNvdW50DQoNCmBgYHtyfQ0KIyBWaXN1YWxpemUgdGhlIHNlbnRpbWVudCBkaXN0cmlidXRpb24gd2l0aCB2YWx1ZSBjb3VudHMNCmdncGxvdChzZW50aW1lbnRfY291bnRzLCBhZXMoeCA9IHNlbnRpbWVudCwgeSA9IG4sIGZpbGwgPSBzZW50aW1lbnQpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgdmp1c3QgPSAtMC41LCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAzKSArICMgQWRkIHZhbHVlIGNvdW50cw0KICBsYWJzKHRpdGxlID0gIlNlbnRpbWVudCBEaXN0cmlidXRpb24gaW4gJ1RoZSBCZWF1dGlmdWwgYW5kIERhbW5lZCciLA0KICAgICAgIHggPSAiU2VudGltZW50IiwNCiAgICAgICB5ID0gIkZyZXF1ZW5jeSIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpgYGANCg0KIyMjIFdvcmQgQ2xvdWRzDQoNClBvc2l0aXZlIFNlbnRpbWVudCBXb3JkIENsb3VkOg0KDQpJIGNyZWF0ZWQgYSB3b3JkIGNsb3VkIHZpc3VhbGl6YXRpb24gZm9yIHdvcmRzIGFzc29jaWF0ZWQgd2l0aCBwb3NpdGl2ZSBzZW50aW1lbnQgaW4gdGhlIGNvcnB1cywgc2hvd2luZyB0aGUgbW9zdCBmcmVxdWVudGx5IG9jY3VycmluZyBwb3NpdGl2ZSB3b3Jkcy4NCg0KYGBge3J9DQojIENyZWF0ZSB3b3JkY2xvdWRzIGZvciBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgc2VudGltZW50cw0KcG9zaXRpdmVfd29yZHMgPC0gYmVhdXRpZnVsX2FuZF9kYW1uZWRfc2VudGltZW50cyAlPiUNCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKSAlPiUNCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQpwcmludChwb3NpdGl2ZV93b3JkcykNCg0KIyBQbG90IHdvcmRjbG91ZCBmb3IgcG9zaXRpdmUgc2VudGltZW50DQp3b3JkY2xvdWQocG9zaXRpdmVfd29yZHMkd29yZCwgcG9zaXRpdmVfd29yZHMkbiwNCiAgICAgICAgICBtYXgud29yZHMgPSAxMDAsIHNjYWxlPWMoMywwLjUpLA0KICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIiksDQogICAgICAgICAgcmFuZG9tLm9yZGVyID0gRkFMU0UsDQogICAgICAgICAgcm90LnBlciA9IDAuMzUsDQogICAgICAgICAgbWFpbiA9ICJXb3JkY2xvdWQgZm9yIFBvc2l0aXZlIFNlbnRpbWVudCIpDQpgYGANCg0KTmVnYXRpdmUgU2VudGltZW50IFdvcmQgQ2xvdWQ6DQoNClNpbWlsYXJseSwgSSBjcmVhdGVkIGEgd29yZCBjbG91ZCB2aXN1YWxpemF0aW9uIGZvciB3b3JkcyBhc3NvY2lhdGVkIHdpdGggbmVnYXRpdmUgc2VudGltZW50IGluIHRoZSBjb3JwdXMsIHNob3dpbmcgdGhlIG1vc3QgZnJlcXVlbnRseSBvY2N1cnJpbmcgbmVnYXRpdmUgd29yZHMuDQoNCmBgYHtyfQ0KbmVnYXRpdmVfd29yZHMgPC0gYmVhdXRpZnVsX2FuZF9kYW1uZWRfc2VudGltZW50cyAlPiUNCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAibmVnYXRpdmUiKSAlPiUNCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQpwcmludChuZWdhdGl2ZV93b3JkcykNCg0KIyBQbG90IHdvcmRjbG91ZCBmb3IgbmVnYXRpdmUgc2VudGltZW50DQp3b3JkY2xvdWQobmVnYXRpdmVfd29yZHMkd29yZCwgbmVnYXRpdmVfd29yZHMkbiwNCiAgICAgICAgICBtYXgud29yZHMgPSAxMDAsIHNjYWxlPWMoMywwLjUpLA0KICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIiksDQogICAgICAgICAgcmFuZG9tLm9yZGVyID0gRkFMU0UsDQogICAgICAgICAgcm90LnBlciA9IDAuMzUsDQogICAgICAgICAgbWFpbiA9ICJXb3JkY2xvdWQgZm9yIE5lZ2F0aXZlIFNlbnRpbWVudCIpDQpgYGANCg0KIyMjIENvbmNsdXNpb24NCg0KSW4gdGhpcyBleHRlbmRlZCBSIE1hcmtkb3duIGRvY3VtZW50LCBJIGhhdmUgcmV0YWluZWQgdGhlIGJhc2UgY29kZSBmb3Igc2VudGltZW50IGFuYWx5c2lzIHdpdGggSmFuZSBBdXN0ZW4ncyB0ZXh0cyBhbmQgaW5jb3Jwb3JhdGVkIGFkZGl0aW9uYWwgYW5hbHlzaXMgd2l0aCAiVGhlIEJlYXV0aWZ1bCBhbmQgRGFtbmVkIiBieSBGLiBTY290dCBGaXR6Z2VyYWxkLiBBZGRpdGlvbmFsbHksIEkgaGF2ZSBpbmNsdWRlZCBzZW50aW1lbnQgYW5hbHlzaXMgdXNpbmcgdGhlIExvdWdocmFuIGxleGljb24gZm9yICJUaGUgQmVhdXRpZnVsIGFuZCBEYW1uZWQiIHRleHQuIFRoZSBiYXNlIGNvZGUgdXNlZCBpbiB0aGlzIFIgbWFya2Rvd24gZG9jdW1lbnQgaXMgZnJvbSB0aGUgIlRleHQgTWluaW5nIHdpdGggUiIgYm9vaywgQ2hhcHRlciAyLg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==