Project Intro
This project focuses on analyzing the sentiments of Twitter data
related to specific keywords. Utilizing the syuzhet
package, it conducts sentiment scoring based on the comprehensive NRC
Word-Emotion Association Lexicon. The analysis encompasses eight
distinct emotional dimensions: anger, anticipation, disgust, fear, joy,
sadness, surprise, and trust.
Additionally, the project features a visualization component that
tracks the evolution of the overall sentiment score over time, offering
a dynamic view of public sentiment trends in relation to the chosen
keywords.
Load helper library
library(data.table);library(textstem);library(lubridate)
library(tm);library(syuzhet);library(ggplot2);library(hrbrthemes);library(wordcloud);library(wordcloud2)
- target: the polarity of the tweet (0 = negative, 2
= neutral, 4 = positive)
- ids: The id of the tweet (2087)
- date: the date of the tweet (Sat May 16 23:58:44
UTC 2009)
- flag: The query (
lyx
). If there is no
query, then this value is NO_QUERY
.
- user: the user that tweeted
(
robotickilldozr
)
- text: the text of the tweet (
Lyx
is
cool)
data <- read.csv("./inputs.csv", header = FALSE, col.names = c("target", "ids", "date", "flag", "user", "text"), encoding = "UTF-8")
summary(data)
target ids date flag user text
Min. :0 Min. :1.468e+09 Length:1600000 Length:1600000 Length:1600000 Length:1600000
1st Qu.:0 1st Qu.:1.957e+09 Class :character Class :character Class :character Class :character
Median :2 Median :2.002e+09 Mode :character Mode :character Mode :character Mode :character
Mean :2 Mean :1.999e+09
3rd Qu.:4 3rd Qu.:2.177e+09
Max. :4 Max. :2.329e+09
Sentiment Analysis
Data Preprocessing
- Lemmatization
Lemmatization
is a linguistic process that reduces a
word to its base or dictionary form, known as a lemma
.
lemmatization
considers the context and transforms
words to their meaningful base forms. For example, “running,” “ran,” and
“runs” would all be lemmatized to “run.”
Lemmatization
is important in NLP because it groups
together different inflected forms of a word, allowing them to be
analyzed as a single item.
preprocess_text <- function(text_column) {
text_column <- tolower(text_column)
text_column <- removePunctuation(text_column)
text_column <- removeNumbers(text_column)
text_column <- removeWords(text_column, stopwords("en"))
text_column <- stripWhitespace(text_column)
text_column <- lemmatize_strings(text_column)
return(text_column)
}
Calculating Sentiment Scores
- The sentiment score is a measure of the emotional tone behind a
series of words. It is determined by analyzing the presence of words and
phrases that are
positively
or negatively
expressed.
- In our case, we’ll use the
syuzhet
package, which
relies on the NRC Word-Emotion Association Lexicon. This lexicon
associates words with emotions
(like joy, sadness,
anger, etc.) and sentiment
valence
(positive or negative).
analyze_sentiment <- function(preprocessed_text) {
sentiment_scores <- get_nrc_sentiment(preprocessed_text)
sentiment_scores$overall_sentiment <- sentiment_scores$positive - sentiment_scores$negative
return(sentiment_scores)
}
Working with the data
test_result[, text_clean := preprocess_text(text)]
sentiment_results <- analyze_sentiment(test_result$text_clean)
Visualize the results
docs <- Corpus(VectorSource(test_result$text_clean))
dtm <- TermDocumentMatrix(docs)
matrix <- as.matrix(dtm)
words <- sort(rowSums(matrix),decreasing=TRUE)
df_words <- data.frame(word = names(words),freq=words)
set.seed(1234)
wordcloud2(data=df_words, size=4, color='random-dark')
colnames(sentiment_results)
[1] "anger" "anticipation" "disgust" "fear" "joy" "sadness"
[7] "surprise" "trust" "negative" "positive" "overall_sentiment"
emotion_averages <- data.frame(
Anger = mean(sentiment_results$anger),
Anticipation = mean(sentiment_results$anticipation),
Disgust = mean(sentiment_results$disgust),
Fear = mean(sentiment_results$fear),
Joy = mean(sentiment_results$joy),
Sadness = mean(sentiment_results$sadness),
Surprise = mean(sentiment_results$surprise),
Trust = mean(sentiment_results$trust)
)
emotion_df <- rbind(rep(0, ncol(emotion_averages)),rep(1, ncol(emotion_averages)), emotion_averages)
fmsb::radarchart(emotion_df, axistype = 1,
pcol=rgb(0.2,0.5,0.5,0.9) , pfcol=rgb(0.2,0.5,0.5,0.5) , plwd=4,
cglcol="grey", cglty=1, axislabcol="grey", caxislabels=seq(0,1,5), cglwd=0.8,
vlcex=1.2)

p <- ggplot(sentiment_results, aes(x=x) ) +
geom_density( aes(x = positive, y = ..density..), fill="#69b3a2" ) +
geom_label( aes(x=4.5, y=0.25, label="Positive"), color="#69b3a2") +
geom_density( aes(x = negative, y = -..density..), fill= "#404080") +
geom_label( aes(x=4.5, y=-0.25, label="Negative"), color="#404080") +
theme_ipsum() +
xlab("Tweets Sentimental Score")
p

p <- ggplot(sentiment_results, aes(x=overall_sentiment)) +
geom_histogram(binwidth = 1, fill="#69b3a2", color="#e9ecef", alpha=0.9) +
theme_ipsum() +
xlab("Overall Sentimental Score")
p

Check the trend
Step 1: Parse the Dates
test_result$date_parsed <- as.Date(strptime(test_result$date, format = "%a %b %d %H:%M:%S PDT %Y"))
summary(test_result$date_parsed)
Min. 1st Qu. Median Mean 3rd Qu. Max.
"2009-04-06" "2009-05-29" "2009-06-05" "2009-06-02" "2009-06-17" "2009-06-25"
Step 2: Aggregate Daily Averages
test_result$overall_sentiment <- sentiment_results$overall_sentiment
daily_avg_sentiment <- aggregate(overall_sentiment ~ date_parsed, data = test_result, mean)
summary(daily_avg_sentiment)
date_parsed overall_sentiment
Min. :2009-04-06 Min. :-0.6667
1st Qu.:2009-05-09 1st Qu.: 0.2020
Median :2009-05-27 Median : 0.3158
Mean :2009-05-23 Mean : 0.3783
3rd Qu.:2009-06-10 3rd Qu.: 0.5294
Max. :2009-06-25 Max. : 2.0000
Step 3: Plot the Data
model <- lm(overall_sentiment ~ date_parsed, data = daily_avg_sentiment)
slope <- coef(model)[2]
r_squared <- summary(model)$r.squared
p <- ggplot(daily_avg_sentiment, aes(x = date_parsed, y = overall_sentiment)) +
geom_point() +
geom_smooth(method = "lm", se = FALSE) +
labs(title = "Daily Average Overall Sentiment Score",
x = "Date",
y = "Average Sentiment Score") +
theme_minimal()
p + annotate("text", x = as.Date(quantile(as.numeric(daily_avg_sentiment$date_parsed),0.75)), y = 1.2,
label = paste("Slope:", round(slope, 4), "\nR^2:", round(r_squared, 4)),
hjust = 0, vjust = 0)

Shiny APP
Generate Word cloud and explore the word counts
screen2
Sentimental Analysis from 8 aspects
screen3
LS0tDQp0aXRsZTogIlR3ZWV0cyBTZW50aW1lbnRhbCBBbmFseXNpcyINCmF1dGhvcjogIllhbGluIFlhbmciDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogIHdvcmRfZG9jdW1lbnQ6DQogICAgdG9jOiBubw0KICAgIHRvY19kZXB0aDogJzMnDQotLS0NCiMjIFByb2plY3QgSW50cm8NCg0KVGhpcyBwcm9qZWN0IGZvY3VzZXMgb24gYW5hbHl6aW5nIHRoZSBzZW50aW1lbnRzIG9mIFR3aXR0ZXIgZGF0YSByZWxhdGVkIHRvIHNwZWNpZmljIGtleXdvcmRzLiBVdGlsaXppbmcgdGhlIGBzeXV6aGV0YCBwYWNrYWdlLCBpdCBjb25kdWN0cyBzZW50aW1lbnQgc2NvcmluZyBiYXNlZCBvbiB0aGUgY29tcHJlaGVuc2l2ZSBOUkMgV29yZC1FbW90aW9uIEFzc29jaWF0aW9uIExleGljb24uIFRoZSBhbmFseXNpcyBlbmNvbXBhc3NlcyBlaWdodCBkaXN0aW5jdCBlbW90aW9uYWwgZGltZW5zaW9uczogYW5nZXIsIGFudGljaXBhdGlvbiwgZGlzZ3VzdCwgZmVhciwgam95LCBzYWRuZXNzLCBzdXJwcmlzZSwgYW5kIHRydXN0LiANCg0KQWRkaXRpb25hbGx5LCB0aGUgcHJvamVjdCBmZWF0dXJlcyBhIHZpc3VhbGl6YXRpb24gY29tcG9uZW50IHRoYXQgdHJhY2tzIHRoZSBldm9sdXRpb24gb2YgdGhlIG92ZXJhbGwgc2VudGltZW50IHNjb3JlIG92ZXIgdGltZSwgb2ZmZXJpbmcgYSBkeW5hbWljIHZpZXcgb2YgcHVibGljIHNlbnRpbWVudCB0cmVuZHMgaW4gcmVsYXRpb24gdG8gdGhlIGNob3NlbiBrZXl3b3Jkcy4gDQoNCiMjIExvYWQgaGVscGVyIGxpYnJhcnkNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoZGF0YS50YWJsZSk7bGlicmFyeSh0ZXh0c3RlbSk7bGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHRtKTtsaWJyYXJ5KHN5dXpoZXQpO2xpYnJhcnkoZ2dwbG90Mik7bGlicmFyeShocmJydGhlbWVzKTtsaWJyYXJ5KHdvcmRjbG91ZCk7bGlicmFyeSh3b3JkY2xvdWQyKQ0KYGBgDQoNCg0KIyMgW1NlbnRpbWVudGFsIGRhdGFdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMva2F6YW5vdmEvc2VudGltZW50MTQwKSBQcmVwcm9jZXNzaW5nDQoNCiogKip0YXJnZXQqKjogdGhlIHBvbGFyaXR5IG9mIHRoZSB0d2VldCAoMCA9IG5lZ2F0aXZlLCAyID0gbmV1dHJhbCwgNCA9IHBvc2l0aXZlKQ0KKiAqKmlkcyoqOiBUaGUgaWQgb2YgdGhlIHR3ZWV0ICgyMDg3KQ0KKiAqKmRhdGUqKjogdGhlIGRhdGUgb2YgdGhlIHR3ZWV0IChTYXQgTWF5IDE2IDIzOjU4OjQ0IFVUQyAyMDA5KQ0KKiAqKmZsYWcqKjogVGhlIHF1ZXJ5IChgbHl4YCkuIElmIHRoZXJlIGlzIG5vIHF1ZXJ5LCB0aGVuIHRoaXMgdmFsdWUgaXMgYE5PX1FVRVJZYC4NCiogKip1c2VyKio6IHRoZSB1c2VyIHRoYXQgdHdlZXRlZCAoYHJvYm90aWNraWxsZG96cmApDQoqICoqdGV4dCoqOiB0aGUgdGV4dCBvZiB0aGUgdHdlZXQgKGBMeXhgIGlzIGNvb2wpDQoNCmBgYHtyfQ0KZGF0YSA8LSByZWFkLmNzdigiLi9pbnB1dHMuY3N2IiwgaGVhZGVyID0gRkFMU0UsIGNvbC5uYW1lcyA9IGMoInRhcmdldCIsICJpZHMiLCAiZGF0ZSIsICJmbGFnIiwgInVzZXIiLCAidGV4dCIpLCBlbmNvZGluZyA9ICJVVEYtOCIpDQpzdW1tYXJ5KGRhdGEpDQpgYGANCg0KIyMjIFNlYXJjaCB0d2VldHMgY29udGFpbnMgY2VydGFpbiBrZXl3b3Jkcw0KDQpgYGB7cn0NCnNlYXJjaF90d2VldHMgPC0gZnVuY3Rpb24odHdlZXRzX2RmLCBrZXl3b3JkLCBpZ25vcmUuY2FzZSA9IFRSVUUpIHsNCiAgdHJ5Q2F0Y2goew0KICAgICMgRW5zdXJlIHR3ZWV0c19kZiBpcyBhIGRhdGEudGFibGUNCiAgICBzZXREVCh0d2VldHNfZGYpDQogICAgIyBWZWN0b3JpemVkIHNlYXJjaCBmb3Iga2V5d29yZA0KICAgIHN1cHByZXNzV2FybmluZ3Moa2V5d29yZF9maWx0ZXJlZCA8LSB0d2VldHNfZGZbZ3JlcGwoa2V5d29yZCwgdGV4dCwgaWdub3JlLmNhc2UgPSBpZ25vcmUuY2FzZSldKQ0KICAgIHJldHVybihrZXl3b3JkX2ZpbHRlcmVkKQ0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICBzdG9wKCJBbiBlcnJvciBvY2N1cnJlZDogIiwgY29uZGl0aW9uTWVzc2FnZShlKSkNCiAgfSkNCn0NCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdGVzdF9yZXN1bHQgPC0gc2VhcmNoX3R3ZWV0cyhkYXRhLCJhcHBsZSIpDQpzdW1tYXJ5KHRlc3RfcmVzdWx0KQ0KYGBgDQoNCiMjIyBTZW50aW1lbnQgQW5hbHlzaXMNCg0KIyMjIyBEYXRhIFByZXByb2Nlc3NpbmcNCg0KKiBMZW1tYXRpemF0aW9uDQogICogYExlbW1hdGl6YXRpb25gIGlzIGEgbGluZ3Vpc3RpYyBwcm9jZXNzIHRoYXQgcmVkdWNlcyBhIHdvcmQgdG8gaXRzIGJhc2Ugb3IgZGljdGlvbmFyeSBmb3JtLCBrbm93biBhcyBhIGBsZW1tYWAuDQogICogYGxlbW1hdGl6YXRpb25gIGNvbnNpZGVycyB0aGUgY29udGV4dCBhbmQgdHJhbnNmb3JtcyB3b3JkcyB0byB0aGVpciBtZWFuaW5nZnVsIGJhc2UgZm9ybXMuIEZvciBleGFtcGxlLCAicnVubmluZywiICJyYW4sIiBhbmQgInJ1bnMiIHdvdWxkIGFsbCBiZSBsZW1tYXRpemVkIHRvICJydW4uIg0KICAqIGBMZW1tYXRpemF0aW9uYCBpcyBpbXBvcnRhbnQgaW4gTkxQIGJlY2F1c2UgaXQgZ3JvdXBzIHRvZ2V0aGVyIGRpZmZlcmVudCBpbmZsZWN0ZWQgZm9ybXMgb2YgYSB3b3JkLCBhbGxvd2luZyB0aGVtIHRvIGJlIGFuYWx5emVkIGFzIGEgc2luZ2xlIGl0ZW0uDQoNCmBgYHtyfQ0KcHJlcHJvY2Vzc190ZXh0IDwtIGZ1bmN0aW9uKHRleHRfY29sdW1uKSB7DQogIHRleHRfY29sdW1uIDwtIHRvbG93ZXIodGV4dF9jb2x1bW4pICAgICAgICAgICAgICAgICAgICMgQ29udmVydCB0ZXh0IHRvIGxvd2VyY2FzZQ0KICB0ZXh0X2NvbHVtbiA8LSByZW1vdmVQdW5jdHVhdGlvbih0ZXh0X2NvbHVtbikgICAgICAgICAjIFJlbW92ZSBwdW5jdHVhdGlvbg0KICB0ZXh0X2NvbHVtbiA8LSByZW1vdmVOdW1iZXJzKHRleHRfY29sdW1uKSAgICAgICAgICAgICAjIFJlbW92ZSBudW1iZXJzDQogIHRleHRfY29sdW1uIDwtIHJlbW92ZVdvcmRzKHRleHRfY29sdW1uLCBzdG9wd29yZHMoImVuIikpICMgUmVtb3ZlIHN0b3B3b3Jkcw0KICB0ZXh0X2NvbHVtbiA8LSBzdHJpcFdoaXRlc3BhY2UodGV4dF9jb2x1bW4pICAgICAgICAgICAjIFN0cmlwIGV4dHJhIHdoaXRlc3BhY2UNCiAgdGV4dF9jb2x1bW4gPC0gbGVtbWF0aXplX3N0cmluZ3ModGV4dF9jb2x1bW4pICAgICAgICAgIyBBcHBseSBsZW1tYXRpemF0aW9uDQogIHJldHVybih0ZXh0X2NvbHVtbikNCn0NCmBgYA0KDQojIyMjIENhbGN1bGF0aW5nIFNlbnRpbWVudCBTY29yZXMNCg0KKiBUaGUgc2VudGltZW50IHNjb3JlIGlzIGEgbWVhc3VyZSBvZiB0aGUgZW1vdGlvbmFsIHRvbmUgYmVoaW5kIGEgc2VyaWVzIG9mIHdvcmRzLiBJdCBpcyBkZXRlcm1pbmVkIGJ5IGFuYWx5emluZyB0aGUgcHJlc2VuY2Ugb2Ygd29yZHMgYW5kIHBocmFzZXMgdGhhdCBhcmUgYHBvc2l0aXZlbHlgIG9yIGBuZWdhdGl2ZWx5YCBleHByZXNzZWQuDQoqIEluIG91ciBjYXNlLCB3ZSdsbCB1c2UgdGhlIGBzeXV6aGV0YCBwYWNrYWdlLCB3aGljaCByZWxpZXMgb24gdGhlIE5SQyBXb3JkLUVtb3Rpb24gQXNzb2NpYXRpb24gTGV4aWNvbi4gVGhpcyBsZXhpY29uIGFzc29jaWF0ZXMgd29yZHMgd2l0aCBgZW1vdGlvbnNgICgqKmxpa2Ugam95LCBzYWRuZXNzLCBhbmdlciwgZXRjLioqKSBhbmQgYHNlbnRpbWVudGAgdmFsZW5jZSAoKipwb3NpdGl2ZSBvciBuZWdhdGl2ZSoqKS4NCg0KYGBge3J9DQphbmFseXplX3NlbnRpbWVudCA8LSBmdW5jdGlvbihwcmVwcm9jZXNzZWRfdGV4dCkgew0KICBzZW50aW1lbnRfc2NvcmVzIDwtIGdldF9ucmNfc2VudGltZW50KHByZXByb2Nlc3NlZF90ZXh0KQ0KICBzZW50aW1lbnRfc2NvcmVzJG92ZXJhbGxfc2VudGltZW50IDwtIHNlbnRpbWVudF9zY29yZXMkcG9zaXRpdmUgLSBzZW50aW1lbnRfc2NvcmVzJG5lZ2F0aXZlDQogIHJldHVybihzZW50aW1lbnRfc2NvcmVzKQ0KfQ0KYGBgDQoNCiMjIyMgV29ya2luZyB3aXRoIHRoZSBkYXRhDQoNCmBgYHtyfQ0KIyBQcmVwcm9jZXNzIHRoZSB0ZXh0DQp0ZXN0X3Jlc3VsdFssIHRleHRfY2xlYW4gOj0gcHJlcHJvY2Vzc190ZXh0KHRleHQpXQ0KDQojIENhbGN1bGF0ZSBzZW50aW1lbnQgc2NvcmVzDQpzZW50aW1lbnRfcmVzdWx0cyA8LSBhbmFseXplX3NlbnRpbWVudCh0ZXN0X3Jlc3VsdCR0ZXh0X2NsZWFuKQ0KYGBgDQoNCiMjIyMgVmlzdWFsaXplIHRoZSByZXN1bHRzDQoNCmBgYHtyfQ0KIyMgV29yZCBDbG91ZA0KDQojIENyZWF0ZSBhIGNvcnB1cyAgDQpkb2NzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGVzdF9yZXN1bHQkdGV4dF9jbGVhbikpDQpkdG0gPC0gVGVybURvY3VtZW50TWF0cml4KGRvY3MpIA0KbWF0cml4IDwtIGFzLm1hdHJpeChkdG0pIA0Kd29yZHMgPC0gc29ydChyb3dTdW1zKG1hdHJpeCksZGVjcmVhc2luZz1UUlVFKSANCmRmX3dvcmRzIDwtIGRhdGEuZnJhbWUod29yZCA9IG5hbWVzKHdvcmRzKSxmcmVxPXdvcmRzKQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTV9DQoNCnNldC5zZWVkKDEyMzQpICMgZm9yIHJlcHJvZHVjaWJpbGl0eSANCg0KIyB3b3JkY2xvdWQod29yZHMgPSBkZl93b3JkcyR3b3JkLCBmcmVxID0gZGZfd29yZHMkZnJlcSwgbWluLmZyZXEgPSAxLA0KIyAgICAgICAgICAgbWF4LndvcmRzPTIwMCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuMTUsDQojICAgICAgICAgICBjb2xvcnM9YnJld2VyLnBhbCg4LCAiU2V0MiIpKQ0Kd29yZGNsb3VkMihkYXRhPWRmX3dvcmRzLCBzaXplPTQsIGNvbG9yPSdyYW5kb20tZGFyaycpDQpgYGANCg0KDQoNCmBgYHtyfQ0KY29sbmFtZXMoc2VudGltZW50X3Jlc3VsdHMpDQpgYGANCg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD01fQ0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggYXZlcmFnZSBzY29yZXMgZm9yIGVhY2ggZW1vdGlvbg0KZW1vdGlvbl9hdmVyYWdlcyA8LSBkYXRhLmZyYW1lKA0KICBBbmdlciA9IG1lYW4oc2VudGltZW50X3Jlc3VsdHMkYW5nZXIpLA0KICBBbnRpY2lwYXRpb24gPSBtZWFuKHNlbnRpbWVudF9yZXN1bHRzJGFudGljaXBhdGlvbiksDQogIERpc2d1c3QgPSBtZWFuKHNlbnRpbWVudF9yZXN1bHRzJGRpc2d1c3QpLA0KICBGZWFyID0gbWVhbihzZW50aW1lbnRfcmVzdWx0cyRmZWFyKSwNCiAgSm95ID0gbWVhbihzZW50aW1lbnRfcmVzdWx0cyRqb3kpLA0KICBTYWRuZXNzID0gbWVhbihzZW50aW1lbnRfcmVzdWx0cyRzYWRuZXNzKSwNCiAgU3VycHJpc2UgPSBtZWFuKHNlbnRpbWVudF9yZXN1bHRzJHN1cnByaXNlKSwNCiAgVHJ1c3QgPSBtZWFuKHNlbnRpbWVudF9yZXN1bHRzJHRydXN0KQ0KKQ0KDQojIEFkZCBhIHJvdyBmb3IgdGhlIG1pbmltdW0gdmFsdWUgZm9yIGVhY2ggY2F0ZWdvcnkgZm9yIHBsb3R0aW5nDQplbW90aW9uX2RmIDwtIHJiaW5kKHJlcCgwLCBuY29sKGVtb3Rpb25fYXZlcmFnZXMpKSxyZXAoMSwgbmNvbChlbW90aW9uX2F2ZXJhZ2VzKSksIGVtb3Rpb25fYXZlcmFnZXMpDQoNCg0KIyBDcmVhdGUgdGhlIHJhZGFyIGNoYXJ0DQpmbXNiOjpyYWRhcmNoYXJ0KGVtb3Rpb25fZGYsIGF4aXN0eXBlID0gMSwNCiAgICAgICAgI2N1c3RvbSBwb2x5Z29uDQogICAgICAgIHBjb2w9cmdiKDAuMiwwLjUsMC41LDAuOSkgLCBwZmNvbD1yZ2IoMC4yLDAuNSwwLjUsMC41KSAsIHBsd2Q9NCwNCiAgICAgICAgI2N1c3RvbSB0aGUgZ3JpZA0KICAgICAgICBjZ2xjb2w9ImdyZXkiLCBjZ2x0eT0xLCBheGlzbGFiY29sPSJncmV5IiwgY2F4aXNsYWJlbHM9c2VxKDAsMSw1KSwgY2dsd2Q9MC44LA0KICAgICAgICAjY3VzdG9tIGxhYmVscw0KICAgICAgICB2bGNleD0xLjIpDQoNCg0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIENoYXJ0DQpwIDwtIGdncGxvdChzZW50aW1lbnRfcmVzdWx0cywgYWVzKHg9eCkgKSArDQogICMgVG9wDQogIGdlb21fZGVuc2l0eSggYWVzKHggPSBwb3NpdGl2ZSwgeSA9IC4uZGVuc2l0eS4uKSwgZmlsbD0iIzY5YjNhMiIgKSArDQogIGdlb21fbGFiZWwoIGFlcyh4PTQuNSwgeT0wLjI1LCBsYWJlbD0iUG9zaXRpdmUiKSwgY29sb3I9IiM2OWIzYTIiKSArDQogICMgQm90dG9tDQogIGdlb21fZGVuc2l0eSggYWVzKHggPSBuZWdhdGl2ZSwgeSA9IC0uLmRlbnNpdHkuLiksIGZpbGw9ICIjNDA0MDgwIikgKw0KICBnZW9tX2xhYmVsKCBhZXMoeD00LjUsIHk9LTAuMjUsIGxhYmVsPSJOZWdhdGl2ZSIpLCBjb2xvcj0iIzQwNDA4MCIpICsNCiAgdGhlbWVfaXBzdW0oKSArDQogIHhsYWIoIlR3ZWV0cyBTZW50aW1lbnRhbCBTY29yZSIpDQpwDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgYmFzaWMgaGlzdG9ncmFtDQpwIDwtIGdncGxvdChzZW50aW1lbnRfcmVzdWx0cywgYWVzKHg9b3ZlcmFsbF9zZW50aW1lbnQpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGZpbGw9IiM2OWIzYTIiLCBjb2xvcj0iI2U5ZWNlZiIsIGFscGhhPTAuOSkgKw0KICB0aGVtZV9pcHN1bSgpICsgDQogIHhsYWIoIk92ZXJhbGwgU2VudGltZW50YWwgU2NvcmUiKQ0KcA0KYGBgDQojIyMgQ2hlY2sgdGhlIHRyZW5kDQoNCiMjIyMgU3RlcCAxOiBQYXJzZSB0aGUgRGF0ZXMNCmBgYHtyfQ0KdGVzdF9yZXN1bHQkZGF0ZV9wYXJzZWQgPC0gYXMuRGF0ZShzdHJwdGltZSh0ZXN0X3Jlc3VsdCRkYXRlLCBmb3JtYXQgPSAiJWEgJWIgJWQgJUg6JU06JVMgUERUICVZIikpDQpzdW1tYXJ5KHRlc3RfcmVzdWx0JGRhdGVfcGFyc2VkKQ0KYGBgDQojIyMjIFN0ZXAgMjogQWdncmVnYXRlIERhaWx5IEF2ZXJhZ2VzDQoNCmBgYHtyfQ0KdGVzdF9yZXN1bHQkb3ZlcmFsbF9zZW50aW1lbnQgPC0gc2VudGltZW50X3Jlc3VsdHMkb3ZlcmFsbF9zZW50aW1lbnQNCmRhaWx5X2F2Z19zZW50aW1lbnQgPC0gYWdncmVnYXRlKG92ZXJhbGxfc2VudGltZW50IH4gZGF0ZV9wYXJzZWQsIGRhdGEgPSB0ZXN0X3Jlc3VsdCwgbWVhbikNCnN1bW1hcnkoZGFpbHlfYXZnX3NlbnRpbWVudCkNCmBgYA0KIyMjIyBTdGVwIDM6IFBsb3QgdGhlIERhdGENCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgRml0IGEgbGluZWFyIG1vZGVsDQptb2RlbCA8LSBsbShvdmVyYWxsX3NlbnRpbWVudCB+IGRhdGVfcGFyc2VkLCBkYXRhID0gZGFpbHlfYXZnX3NlbnRpbWVudCkNCg0KIyBFeHRyYWN0IHNsb3BlIGFuZCBSXjIgdmFsdWUNCnNsb3BlIDwtIGNvZWYobW9kZWwpWzJdDQpyX3NxdWFyZWQgPC0gc3VtbWFyeShtb2RlbCkkci5zcXVhcmVkDQoNCiMgWW91ciBvcmlnaW5hbCBnZ3Bsb3QgY29kZQ0KcCA8LSBnZ3Bsb3QoZGFpbHlfYXZnX3NlbnRpbWVudCwgYWVzKHggPSBkYXRlX3BhcnNlZCwgeSA9IG92ZXJhbGxfc2VudGltZW50KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArDQogIGxhYnModGl0bGUgPSAiRGFpbHkgQXZlcmFnZSBPdmVyYWxsIFNlbnRpbWVudCBTY29yZSIsDQogICAgICAgeCA9ICJEYXRlIiwNCiAgICAgICB5ID0gIkF2ZXJhZ2UgU2VudGltZW50IFNjb3JlIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBBZGQgYW5ub3RhdGlvbnMgZm9yIHNsb3BlIGFuZCBSXjINCnAgKyBhbm5vdGF0ZSgidGV4dCIsIHggPSBhcy5EYXRlKHF1YW50aWxlKGFzLm51bWVyaWMoZGFpbHlfYXZnX3NlbnRpbWVudCRkYXRlX3BhcnNlZCksMC43NSkpLCB5ID0gMS4yLCANCiAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlKCJTbG9wZToiLCByb3VuZChzbG9wZSwgNCksICJcblJeMjoiLCByb3VuZChyX3NxdWFyZWQsIDQpKSwNCiAgICAgICAgICAgICBoanVzdCA9IDAsIHZqdXN0ID0gMCkNCg0KDQpgYGANCg0KIyMgU2hpbnkgQVBQDQoNCiMjIyBRdWVyeSBUd2VldHMgYnkga2V5d29yZHMNCg0KIVtzY3JlZW4xXShhc3NldHMvU2VjMS5wbmcpDQoNCiMjIyBHZW5lcmF0ZSBXb3JkIGNsb3VkIGFuZCBleHBsb3JlIHRoZSB3b3JkIGNvdW50cw0KDQohW3NjcmVlbjJdKGFzc2V0cy9TZWMyLnBuZykNCg0KIyMjIFNlbnRpbWVudGFsIEFuYWx5c2lzIGZyb20gOCBhc3BlY3RzDQoNCiFbc2NyZWVuM10oYXNzZXRzL1NlYzMucG5nKQ0KDQojIyMgVGVtcG9yYWwgQW5sYXlzaXMgd2l0aCB0d2VldHMNCg0KIVtzY3JlZW40XShhc3NldHMvU2VjNC5wbmcpDQoNCg0KDQoNCg0KDQoNCg0K