Basic String Functions
in R
Several R string functions are commonly used in manipulating
strings.
Character
Translation
The base R has several character translation functions. The most
frequently used functions are tolower
and
toupper
. The following are a few illustrative examples.
my_string <- "Example STRING, with example numbers (12, 15 and also 10.2)?!"
tolower(my_string)
[1] "example string, with example numbers (12, 15 and also 10.2)?!"
toupper(my_string)
[1] "EXAMPLE STRING, WITH EXAMPLE NUMBERS (12, 15 AND ALSO 10.2)?!"
casefold(my_string, upper = TRUE)
[1] "EXAMPLE STRING, WITH EXAMPLE NUMBERS (12, 15 AND ALSO 10.2)?!"
Concatenating and
Splitting Strings
when processing text data, sometimes we need to combine multiple
strings to make a single string. While on some other occasions, we need
to split a string into multiple substrings. The following is a simple
example.
string1 = "It is easy to get started with R"
string2 = "However, it takes time to be a good R programmer"
###
str_split(string2, ",")
[[1]]
[1] "However"
[2] " it takes time to be a good R programmer"
To obtain the actural string, the [[]]
string operator
needs to be used,
str_split(string2, ",")[[1]]
[1] "However"
[2] " it takes time to be a good R programmer"
paste()
is widely used to concatenate strings. It is
very useful to pass values of parameters to the text of graphic
annotations.
string12 = paste(string1, ".", string2, "!" )
string12 # There is white space before added punctuation.
[1] "It is easy to get started with R . However, it takes time to be a good R programmer !"
## To remove the white space, set argument sep = ""
string120 = paste(string1, ".", string2, "!", sep = "" )
string120
[1] "It is easy to get started with R.However, it takes time to be a good R programmer!"
An example of graphical annotation that passes values of a
parameter.
myDat = data.frame(normal = rnorm(100),
exponential = rexp(100, rate=1),
gamma = rgamma(100, shape=0.5, rate = 1))
distName = c("Normal", "Exponential", "Gamma")
par(mfrow=c(1,3))
for (i in 1:3){
plot(density(myDat[,i]), xlab=paste(distName[i], "score"), main = "")
legend("topright", paste(distName[i], "distribution"), cex = 0.7, bty="n")
}
Searching &
Replacing Symbols
Two R functions grep()
and grepl()
can be
used to detect special symbols in a vector of strings.
grep()
returns the index of the string vector that contains
the special symbol while grepl()
returns the logical values
indicating whether each component has the special symbol.
Since special characters (symbols) need to be escaped to make them
literal, \\
needs to be added in front of special
symbols. The following examples show how use grep()
and
grepl()
to detect special characters in a string.
my_string <- "Example STRING, with example numbers (12, 15 and also 10.2)?!"
str_vec = str_split(my_string, ",")[[1]]
str_vec
[1] "Example STRING" " with example numbers (12"
[3] " 15 and also 10.2)?!"
grep("\\?",str_vec) # returns the index(es)
[1] 3
grepl("\\?",str_vec) # returns the logical vector
[1] FALSE FALSE TRUE
The other two useful functions in processing strings are
str_replace_all()
and str_extract_all()
.
Before presenting illustrative examples, we introduce the term
regex
(regular expression) - a sequence of characters that
forms a search pattern. regex
can be used to check if a
string contains the specified search pattern. for example,
[0-9]+
match any substring that is one or more
contiguous numbers. [a-zA-Z\s]
is The letters A-Z,
upper and lower case, plus whitespace. [^A-Za-z\s]
is
the negation of the previous regex
. \\s
is a
white space.
my_string <- "Example STRING, with example numbers (12, 15 and also 10.2)?!"
str_replace_all(my_string, "e","___") # replacement
[1] "Exampl___ STRING, with ___xampl___ numb___rs (12, 15 and also 10.2)?!"
###
str_extract_all(my_string,"[0-9]+")[[1]]
[1] "12" "15" "10" "2"
A Wrap-up
Function
The following is a function that calls the aforementioned functions
to clean a string (sentences) and return a vector of words.
StrCleaning <- function(string){
# Lowercase
temp <- tolower(string)
# Remove everything that is not a number or letter (may want to keep more
# stuff in your actual analyses).
temp <- stringr::str_replace_all(temp,"[^a-zA-Z\\s]", " ")
# Shrink down to just one white space
temp <- stringr::str_replace_all(temp,"[\\s]+", " ")
# Split it
temp <- stringr::str_split(temp, " ")[[1]]
# Get rid of trailing "" if necessary
indexes <- which(temp == "")
if(length(indexes) > 0){
temp <- temp[-indexes]
}
return(temp)
}
The following example calls the above function to clean a sentence
and make a vector of words.
sentence = "The term 'data science' (originally used interchangeably with 'datalogy') has existed for over thirty years and was used initially as a substitute for computer science by Peter Naur in 1960."
CleanSentence <- StrCleaning(sentence)
CleanSentence
[1] "the" "term" "data" "science"
[5] "originally" "used" "interchangeably" "with"
[9] "datalogy" "has" "existed" "for"
[13] "over" "thirty" "years" "and"
[17] "was" "used" "initially" "as"
[21] "a" "substitute" "for" "computer"
[25] "science" "by" "peter" "naur"
[29] "in"
The following R function cleans a text blocks.
# function to clean text
CleanTextDocs <- function(text){
# Get rid of blank lines
indexes <- which(text == "")
if (length(indexes) > 0) {
text <- text[-indexes]
}
# See if we are left with any valid text:
if (length(text) == 0) {
cat("There was no text in this document! \n")
to_return <- list(num_tokens = 0,
unique_tokens = 0,
text = "")
} else {
# If there is valid text, process it.
# Loop through the lines in the text and combine them:
clean_text <- NULL
for (i in 1:length(text)) {
# add them to a vector
clean_text <- c(clean_text, StrCleaning(text[i]))
}
# Calculate the number of tokens and unique tokens and return them in a
# named list object.
num_tok <- length(clean_text)
num_uniq <- length(unique(clean_text))
to_return <- list(num_tokens = num_tok,
unique_tokens = num_uniq,
text = clean_text)
}
return(to_return)
}
text <- readLines("https://pengdsci.github.io/STA553VIZ/w04/Obama_Speech_2-24-09.txt")
TextVec = CleanTextDocs(text)
sort(table(TextVec$text), decreasing = TRUE)[1:30]
the and to that of a we our
269 229 205 165 161 138 137 117
in will for i is this it not
107 85 81 80 77 75 70 41
but s have are on can be or
40 40 38 37 36 34 31 31
you more their t american now
31 30 28 27 26 26
The above frequency table contains many words that have less
information. In text mining, these types of words are called
stopwords
that need to be removed from the text analysis.
In R text mining package tm
lists about 175
stopwords
. The detailed list can be found using the
code
stopwords(kind = "en")
Next, we remove all stopwords
and meaningless strings
from the above partially processed text and make a frequency table of
words in the speech.
removedText = removeWords(TextVec$text, c(stopwords("english"),"s", "t", "ve"))
## remove empty string ""
removedText =removedText[removedText!=""]
## Make a frequency table
freqVec = sort(table(removedText), decreasing = TRUE)
freqVec[1:20]
removedText
will can american now know economy new people
85 34 26 26 25 22 21 21
plan every health america us care also time
21 20 20 19 19 18 16 16
must years americans education
15 15 14 14
xtick = names(freqVec[1:20])
x = barplot(freqVec[1:20], col = heat.colors(20), xaxt="n")
## xpd = TRUE or NA allows text annotation plotted outside the plot region
text(cex=0.75, x = x -.25, y = -10, labels = xtick, xpd=TRUE, srt=75, col = "navy")
The above bar chart shows the frequencies of the first 20 words with
the highest frequency. We can see the rough idea of the
speech: actions for the US economy, health care, education, etc.
Text File Processing
with Library tm
R has a powerful library for text mining. The following two functions
can be used to scrape internet files and clean them for basic text
analytics including visual representation using wordcloud.
#++++++++++++++++++++++++++++++++++
# rquery.wordcloud() : Word cloud generator
# - http://www.sthda.com
#+++++++++++++++++++++++++++++++++++
# x : character string (plain text, web url, txt file path)
# type : specify whether x is a plain text, a web page url or a file path
# lang : the language of the text
# excludeWords : a vector of words to exclude from the text
# textStemming : reduces words to their root form
# colorPalette : the name of color palette taken from RColorBrewer package,
# or a color name, or a color code
# min.freq : words with frequency below min.freq will not be plotted
# max.words : Maximum number of words to be plotted. least frequent terms dropped
# value returned by the function : a list(tdm, freqTable)
rquery.wordcloud <- function(x, type=c("text", "url", "file"),
lang="english", excludeWords=NULL,
textStemming=FALSE, colorPalette="Dark2",
min.freq=3, max.words=2000)
{
if(type[1]=="file") text <- readLines(x)
else if(type[1]=="url") text <- html_to_text(x)
else if(type[1]=="text") text <- x
# Load the text as a corpus
docs <- Corpus(VectorSource(text))
# Convert the text to lower case
docs <- tm_map(docs, content_transformer(tolower))
# Remove numbers
docs <- tm_map(docs, removeNumbers)
# Remove stopwords for the language
docs <- tm_map(docs, removeWords, stopwords(lang))
# Remove punctuations
docs <- tm_map(docs, removePunctuation)
# Eliminate extra white spaces
docs <- tm_map(docs, stripWhitespace)
# Remove your own stopwords
if(!is.null(excludeWords))
docs <- tm_map(docs, removeWords, excludeWords)
# Text stemming
if(textStemming) docs <- tm_map(docs, stemDocument)
# Create term-document matrix
tdm <- TermDocumentMatrix(docs)
m <- as.matrix(tdm)
v <- sort(rowSums(m),decreasing=TRUE)
d <- data.frame(word = names(v),freq=v)
# check the color palette name
if(!colorPalette %in% rownames(brewer.pal.info)) colors = colorPalette
else colors = brewer.pal(8, colorPalette)
# Plot the word cloud
set.seed(1234)
wordcloud(d$word,d$freq, min.freq=min.freq, max.words=max.words,
random.order=FALSE, rot.per=0.35,
use.r.layout=FALSE, colors=colors)
invisible(list(tdm=tdm, freqTable = d))
}
#++++++++++++++++++++++
# Helper function
#++++++++++++++++++++++
# Download and parse webpage
html_to_text<-function(url){
# download html
html.doc <- getURL(url)
#convert to plain text
doc = htmlParse(html.doc, asText=TRUE)
# "//text()" returns all text outside of HTML tags.
# We also don't want text such as style and script codes
text <- xpathSApply(doc, "//text()[not(ancestor::script)][not(ancestor::style)][not(ancestor::noscript)][not(ancestor::form)]", xmlValue)
# Format text vector into one character string
return(paste(text, collapse = " "))
}
rquery.wordcloud("https://pengdsci.github.io/STA553VIZ/w04/Obama_Speech_2-24-09.txt", type="url")
LS0tDQp0aXRsZTogIkRhdGEgUHJvY2Vzc2luZyBmb3IgVmlzdWFsaXphdGlvbiINCmF1dGhvcjogIkNoZW5nIFBlbmciDQpkYXRlOiAiICINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvY19jb2xsYXBzZWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHRydWUNCiAgICB0aGVtZTogcmVhZGFibGUNCiAgcGRmX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIGZpZ193aWR0aDogNQ0KICAgIGZpZ19oZWlnaHQ6IDQNCi0tLQ0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KLyogVGFibGUgb2YgY29udGVudCAtIG5hdmlnYXRpb24gKi8NCmRpdiNUT0MgbGkgew0KICAgIGxpc3Qtc3R5bGU6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOnNreWJsdWU7DQogICAgYmFja2dyb3VuZC1pbWFnZTpub25lOw0KICAgIGJhY2tncm91bmQtcmVwZWF0Om5vbmU7DQogICAgYmFja2dyb3VuZC1wb3NpdGlvbjowOw0KICAgIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KfQ0KDQpoMS50aXRsZSB7DQogIGZvbnQtc2l6ZTogMjRweDsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCg0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMiB7IC8qIEhlYWRlciAzIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE1cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KPC9zdHlsZT4NCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgY29kZSBjaHVuayBzcGVjaWZpZXMgd2hldGhlciB0aGUgUiBjb2RlLCB3YXJuaW5ncywgYW5kIG91dHB1dCANCiMgd2lsbCBiZSBpbmNsdWRlZCBpbiB0aGUgb3V0cHV0IGZpbGVzLg0Kb3B0aW9ucyhyZXBvcyA9IGxpc3QoQ1JBTj0iaHR0cDovL2NyYW4ucnN0dWRpby5jb20vIikpDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICAgbGlicmFyeSh0aWR5dmVyc2UpDQp9DQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoInN0cmluZ3IiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygic3RyaW5nciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICBsaWJyYXJ5KHN0cmluZ3IpDQp9DQoNCmlmICghcmVxdWlyZSgidG0iKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygidG0iLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICAgbGlicmFyeSh0bSkNCn0NCg0KaWYgKCFyZXF1aXJlKCJ3b3JkY2xvdWQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgIGxpYnJhcnkod29yZGNsb3VkKQ0KfQ0KDQppZiAoIXJlcXVpcmUoIlJDdXJsIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIlJDdXJsIiwgZGVwZW5kZW5jaWVzID0gVFJVRSkNCiAgIGxpYnJhcnkoUkN1cmwpDQp9DQoNCmlmICghcmVxdWlyZSgiWE1MIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIlhNTCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICBsaWJyYXJ5KFhNTCkNCn0NCg0KDQppZiAoIXJlcXVpcmUoIlNub3diYWxsQyIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJTbm93YmFsbEMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICAgbGlicmFyeShTbm93YmFsbEMpDQp9DQoNCmlmICghcmVxdWlyZSgiUkNvbG9yQnJld2VyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICBsaWJyYXJ5KFJDb2xvckJyZXdlcikNCn0NCg0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BKQ0KYGBgDQoNCg0KXA0KDQojIEludHJvZHVjdGlvbg0KDQpUaGlzIHNob3J0IG5vdGUgaW50cm9kdWNlcyB0aGUgc3RlcHMgZm9yIHByb2Nlc3Npbmcgc2ltcGxlIHRleHQgZm9ybWF0IGRhdGEgZm9yIHZpc3VhbGl6YXRpb24gYW5kIHNpbXBsZSBkZXNjcmlwdGl2ZSB2aXN1YWwgYW5hbHlzaXMgdXNpbmcgUi4NCg0KSW4gYWRkaXRpb24gdG8gdGhlIGJhc2ljIFIgc3RyaW5nIGZ1bmN0aW9ucywgd2Ugd2lsbCB1c2UgYSBwb3B1bGFyIFIgbGlicmFyeSBmb3IgdGV4dCBtaW5pbmcgZm9yIHNvbWUgdGV4dCBmb3JtYXQgZGF0YSBwcm9jZXNzaW5nLg0KDQoNCiMgQmFzaWMgU3RyaW5nIEZ1bmN0aW9ucyBpbiBSDQoNClNldmVyYWwgUiBzdHJpbmcgZnVuY3Rpb25zIGFyZSBjb21tb25seSB1c2VkIGluIG1hbmlwdWxhdGluZyBzdHJpbmdzLg0KDQojIyBDaGFyYWN0ZXIgVHJhbnNsYXRpb24NCg0KVGhlIGJhc2UgUiBoYXMgc2V2ZXJhbCBjaGFyYWN0ZXIgdHJhbnNsYXRpb24gZnVuY3Rpb25zLiBUaGUgbW9zdCBmcmVxdWVudGx5IHVzZWQgZnVuY3Rpb25zIGFyZSBgdG9sb3dlcmAgYW5kIGB0b3VwcGVyYC4gVGhlIGZvbGxvd2luZyBhcmUgYSBmZXcgaWxsdXN0cmF0aXZlIGV4YW1wbGVzLg0KDQoNCmBgYHtyfQ0KbXlfc3RyaW5nIDwtICJFeGFtcGxlIFNUUklORywgd2l0aCBleGFtcGxlIG51bWJlcnMgKDEyLCAxNSBhbmQgYWxzbyAxMC4yKT8hIg0KdG9sb3dlcihteV9zdHJpbmcpDQp0b3VwcGVyKG15X3N0cmluZykNCmNhc2Vmb2xkKG15X3N0cmluZywgdXBwZXIgPSBUUlVFKQ0KYGBgDQoNCiMjIENvbmNhdGVuYXRpbmcgYW5kIFNwbGl0dGluZyBTdHJpbmdzDQoNCndoZW4gcHJvY2Vzc2luZyB0ZXh0IGRhdGEsIHNvbWV0aW1lcyB3ZSBuZWVkIHRvIGNvbWJpbmUgbXVsdGlwbGUgc3RyaW5ncyB0byBtYWtlIGEgc2luZ2xlIHN0cmluZy4gV2hpbGUgb24gc29tZSBvdGhlciBvY2Nhc2lvbnMsIHdlIG5lZWQgdG8gc3BsaXQgYSBzdHJpbmcgaW50byBtdWx0aXBsZSBzdWJzdHJpbmdzLiBUaGUgZm9sbG93aW5nIGlzIGEgc2ltcGxlIGV4YW1wbGUuDQoNCg0KYGBge3J9DQpzdHJpbmcxID0gIkl0IGlzIGVhc3kgdG8gZ2V0IHN0YXJ0ZWQgd2l0aCBSIg0Kc3RyaW5nMiA9ICJIb3dldmVyLCBpdCB0YWtlcyB0aW1lIHRvIGJlIGEgZ29vZCBSIHByb2dyYW1tZXIiDQojIyMNCnN0cl9zcGxpdChzdHJpbmcyLCAiLCIpDQpgYGANCg0KVG8gb2J0YWluIHRoZSBhY3R1cmFsIHN0cmluZywgdGhlIGBbW11dYCBzdHJpbmcgb3BlcmF0b3IgbmVlZHMgdG8gYmUgdXNlZCwNCg0KYGBge3J9DQpzdHJfc3BsaXQoc3RyaW5nMiwgIiwiKVtbMV1dDQpgYGANCg0KYHBhc3RlKClgIGlzIHdpZGVseSB1c2VkIHRvIGNvbmNhdGVuYXRlIHN0cmluZ3MuIEl0IGlzIHZlcnkgdXNlZnVsIHRvIHBhc3MgdmFsdWVzIG9mIHBhcmFtZXRlcnMgdG8gdGhlIHRleHQgb2YgZ3JhcGhpYyBhbm5vdGF0aW9ucy4NCg0KYGBge3J9DQpzdHJpbmcxMiA9IHBhc3RlKHN0cmluZzEsICIuIiwgc3RyaW5nMiwgIiEiICkNCnN0cmluZzEyICAgICMgVGhlcmUgaXMgd2hpdGUgc3BhY2UgYmVmb3JlIGFkZGVkIHB1bmN0dWF0aW9uLg0KYGBgDQoNCmBgYHtyfQ0KIyMgVG8gcmVtb3ZlIHRoZSB3aGl0ZSBzcGFjZSwgc2V0IGFyZ3VtZW50IHNlcCA9ICIiDQpzdHJpbmcxMjAgPSBwYXN0ZShzdHJpbmcxLCAiLiIsIHN0cmluZzIsICIhIiwgc2VwID0gIiIgKQ0Kc3RyaW5nMTIwDQpgYGANCg0KQW4gZXhhbXBsZSBvZiBncmFwaGljYWwgYW5ub3RhdGlvbiB0aGF0IHBhc3NlcyB2YWx1ZXMgb2YgYSBwYXJhbWV0ZXIuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9M30NCm15RGF0ID0gZGF0YS5mcmFtZShub3JtYWwgPSBybm9ybSgxMDApLA0KICAgICAgICAgICAgICAgICAgIGV4cG9uZW50aWFsID0gcmV4cCgxMDAsIHJhdGU9MSksDQogICAgICAgICAgICAgICAgICAgZ2FtbWEgPSByZ2FtbWEoMTAwLCBzaGFwZT0wLjUsIHJhdGUgPSAxKSkNCmRpc3ROYW1lID0gYygiTm9ybWFsIiwgIkV4cG9uZW50aWFsIiwgIkdhbW1hIikNCg0KcGFyKG1mcm93PWMoMSwzKSkNCmZvciAoaSBpbiAxOjMpew0KICBwbG90KGRlbnNpdHkobXlEYXRbLGldKSwgeGxhYj1wYXN0ZShkaXN0TmFtZVtpXSwgInNjb3JlIiksIG1haW4gPSAiIikNCiAgbGVnZW5kKCJ0b3ByaWdodCIsIHBhc3RlKGRpc3ROYW1lW2ldLCAiZGlzdHJpYnV0aW9uIiksIGNleCA9IDAuNywgYnR5PSJuIikNCn0NCg0KYGBgDQoNCg0KDQojIyBTZWFyY2hpbmcgXCYgUmVwbGFjaW5nIFN5bWJvbHMgDQoNClR3byBSIGZ1bmN0aW9ucyBgZ3JlcCgpYCBhbmQgYGdyZXBsKClgIGNhbiBiZSB1c2VkIHRvIGRldGVjdCBzcGVjaWFsIHN5bWJvbHMgaW4gYSB2ZWN0b3Igb2Ygc3RyaW5ncy4gYGdyZXAoKWAgcmV0dXJucyB0aGUgaW5kZXggb2YgdGhlIHN0cmluZyB2ZWN0b3IgdGhhdCBjb250YWlucyB0aGUgc3BlY2lhbCBzeW1ib2wgd2hpbGUgYGdyZXBsKClgIHJldHVybnMgdGhlIGxvZ2ljYWwgdmFsdWVzIGluZGljYXRpbmcgd2hldGhlciBlYWNoIGNvbXBvbmVudCBoYXMgdGhlIHNwZWNpYWwgc3ltYm9sLg0KDQoNClNpbmNlIHNwZWNpYWwgY2hhcmFjdGVycyAoc3ltYm9scykgbmVlZCB0byBiZSBlc2NhcGVkIHRvIG1ha2UgdGhlbSAqbGl0ZXJhbCosIGBcXGAgbmVlZHMgdG8gYmUgYWRkZWQgaW4gZnJvbnQgb2Ygc3BlY2lhbCBzeW1ib2xzLiBUaGUgZm9sbG93aW5nIGV4YW1wbGVzIHNob3cgaG93IHVzZSBgZ3JlcCgpYCBhbmQgYGdyZXBsKClgIHRvIGRldGVjdCBzcGVjaWFsIGNoYXJhY3RlcnMgaW4gYSBzdHJpbmcuDQoNCg0KYGBge3J9DQpteV9zdHJpbmcgPC0gIkV4YW1wbGUgU1RSSU5HLCB3aXRoIGV4YW1wbGUgbnVtYmVycyAoMTIsIDE1IGFuZCBhbHNvIDEwLjIpPyEiDQpzdHJfdmVjID0gc3RyX3NwbGl0KG15X3N0cmluZywgIiwiKVtbMV1dDQpzdHJfdmVjDQpgYGANCg0KYGBge3J9DQpncmVwKCJcXD8iLHN0cl92ZWMpICAjIHJldHVybnMgdGhlIGluZGV4KGVzKQ0KZ3JlcGwoIlxcPyIsc3RyX3ZlYykgICMgcmV0dXJucyB0aGUgbG9naWNhbCB2ZWN0b3INCmBgYA0KDQpUaGUgb3RoZXIgdHdvIHVzZWZ1bCBmdW5jdGlvbnMgaW4gcHJvY2Vzc2luZyBzdHJpbmdzIGFyZSBgc3RyX3JlcGxhY2VfYWxsKClgIGFuZCBgc3RyX2V4dHJhY3RfYWxsKClgLg0KDQpCZWZvcmUgcHJlc2VudGluZyBpbGx1c3RyYXRpdmUgZXhhbXBsZXMsIHdlIGludHJvZHVjZSB0aGUgdGVybSBgcmVnZXhgIChyZWd1bGFyIGV4cHJlc3Npb24pIC0gYSBzZXF1ZW5jZSBvZiBjaGFyYWN0ZXJzIHRoYXQgZm9ybXMgYSBzZWFyY2ggcGF0dGVybi4gYHJlZ2V4YCBjYW4gYmUgdXNlZCB0byBjaGVjayBpZiBhIHN0cmluZyBjb250YWlucyB0aGUgc3BlY2lmaWVkIHNlYXJjaCBwYXR0ZXJuLiBmb3IgZXhhbXBsZSwgYFswLTldK2AgKm1hdGNoIGFueSBzdWJzdHJpbmcgdGhhdCBpcyBvbmUgb3IgbW9yZSBjb250aWd1b3VzIG51bWJlcnMqLiBgW2EtekEtWlxzXWAgaXMgKlRoZSBsZXR0ZXJzIEEtWiwgdXBwZXIgYW5kIGxvd2VyIGNhc2UsIHBsdXMgd2hpdGVzcGFjZSouIGBbXkEtWmEtelxzXWAgaXMgdGhlIG5lZ2F0aW9uIG9mIHRoZSBwcmV2aW91cyBgcmVnZXhgLiAgYFxcc2AgaXMgYSB3aGl0ZSBzcGFjZS4NCg0KDQpgYGB7cn0NCm15X3N0cmluZyA8LSAiRXhhbXBsZSBTVFJJTkcsIHdpdGggZXhhbXBsZSBudW1iZXJzICgxMiwgMTUgYW5kIGFsc28gMTAuMik/ISINCnN0cl9yZXBsYWNlX2FsbChteV9zdHJpbmcsICJlIiwiX19fIikgICAjIHJlcGxhY2VtZW50DQojIyMNCnN0cl9leHRyYWN0X2FsbChteV9zdHJpbmcsIlswLTldKyIpW1sxXV0NCg0KYGBgDQoNCiMjIEEgV3JhcC11cCBGdW5jdGlvbg0KDQpUaGUgZm9sbG93aW5nIGlzIGEgZnVuY3Rpb24gdGhhdCBjYWxscyB0aGUgYWZvcmVtZW50aW9uZWQgZnVuY3Rpb25zIHRvIGNsZWFuIGEgc3RyaW5nIChzZW50ZW5jZXMpIGFuZCByZXR1cm4gYSB2ZWN0b3Igb2Ygd29yZHMuDQoNCmBgYHtyfQ0KU3RyQ2xlYW5pbmcgPC0gZnVuY3Rpb24oc3RyaW5nKXsNCiAgICAjIExvd2VyY2FzZQ0KICAgIHRlbXAgPC0gdG9sb3dlcihzdHJpbmcpDQogICAgIyBSZW1vdmUgZXZlcnl0aGluZyB0aGF0IGlzIG5vdCBhIG51bWJlciBvciBsZXR0ZXIgKG1heSB3YW50IHRvIGtlZXAgbW9yZSANCiAgICAjIHN0dWZmIGluIHlvdXIgYWN0dWFsIGFuYWx5c2VzKS4gDQogICAgdGVtcCA8LSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwodGVtcCwiW15hLXpBLVpcXHNdIiwgIiAiKQ0KICAgICMgU2hyaW5rIGRvd24gdG8ganVzdCBvbmUgd2hpdGUgc3BhY2UNCiAgICB0ZW1wIDwtIHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbCh0ZW1wLCJbXFxzXSsiLCAiICIpDQogICAgIyBTcGxpdCBpdA0KICAgIHRlbXAgPC0gc3RyaW5ncjo6c3RyX3NwbGl0KHRlbXAsICIgIilbWzFdXQ0KICAgICMgR2V0IHJpZCBvZiB0cmFpbGluZyAiIiBpZiBuZWNlc3NhcnkNCiAgICBpbmRleGVzIDwtIHdoaWNoKHRlbXAgPT0gIiIpDQogICAgaWYobGVuZ3RoKGluZGV4ZXMpID4gMCl7DQogICAgICB0ZW1wIDwtIHRlbXBbLWluZGV4ZXNdDQogICAgfSANCiAgICByZXR1cm4odGVtcCkNCn0NCmBgYA0KDQoNClRoZSBmb2xsb3dpbmcgZXhhbXBsZSBjYWxscyB0aGUgYWJvdmUgZnVuY3Rpb24gdG8gY2xlYW4gYSBzZW50ZW5jZSBhbmQgbWFrZSBhIHZlY3RvciBvZiB3b3Jkcy4NCg0KYGBge3J9DQpzZW50ZW5jZSA9ICJUaGUgdGVybSAnZGF0YSBzY2llbmNlJyAob3JpZ2luYWxseSB1c2VkIGludGVyY2hhbmdlYWJseSB3aXRoICdkYXRhbG9neScpIGhhcyBleGlzdGVkIGZvciBvdmVyIHRoaXJ0eSB5ZWFycyBhbmQgd2FzIHVzZWQgaW5pdGlhbGx5IGFzIGEgc3Vic3RpdHV0ZSBmb3IgY29tcHV0ZXIgc2NpZW5jZSBieSBQZXRlciBOYXVyIGluIDE5NjAuIg0KQ2xlYW5TZW50ZW5jZSA8LSBTdHJDbGVhbmluZyhzZW50ZW5jZSkNCkNsZWFuU2VudGVuY2UgDQpgYGANCg0KVGhlIGZvbGxvd2luZyBSIGZ1bmN0aW9uIGNsZWFucyBhIHRleHQgYmxvY2tzLg0KDQpgYGB7cn0NCiMgZnVuY3Rpb24gdG8gY2xlYW4gdGV4dA0KQ2xlYW5UZXh0RG9jcyA8LSBmdW5jdGlvbih0ZXh0KXsNCiAgICAjIEdldCByaWQgb2YgYmxhbmsgbGluZXMNCiAgICBpbmRleGVzIDwtIHdoaWNoKHRleHQgPT0gIiIpDQogICAgaWYgKGxlbmd0aChpbmRleGVzKSA+IDApIHsNCiAgICAgICAgdGV4dCA8LSB0ZXh0Wy1pbmRleGVzXQ0KICAgIH0NCgkjIFNlZSBpZiB3ZSBhcmUgbGVmdCB3aXRoIGFueSB2YWxpZCB0ZXh0Og0KICAgIGlmIChsZW5ndGgodGV4dCkgPT0gMCkgew0KICAgICAgICBjYXQoIlRoZXJlIHdhcyBubyB0ZXh0IGluIHRoaXMgZG9jdW1lbnQhIFxuIikNCiAgICAgICAgdG9fcmV0dXJuIDwtIGxpc3QobnVtX3Rva2VucyA9IDAsIA0KCQkgICAgICAgICAgICAgICAgICAgICB1bmlxdWVfdG9rZW5zID0gMCwgDQoJCQkJCQkJIHRleHQgPSAiIikNCiAgICB9IGVsc2Ugew0KICAgICAgICAjIElmIHRoZXJlIGlzIHZhbGlkIHRleHQsIHByb2Nlc3MgaXQuDQogICAgICAgICMgTG9vcCB0aHJvdWdoIHRoZSBsaW5lcyBpbiB0aGUgdGV4dCBhbmQgY29tYmluZSB0aGVtOg0KICAgICAgICBjbGVhbl90ZXh0IDwtIE5VTEwNCiAgICAgICAgZm9yIChpIGluIDE6bGVuZ3RoKHRleHQpKSB7DQogICAgICAgICAgICAjIGFkZCB0aGVtIHRvIGEgdmVjdG9yIA0KICAgICAgICAgICAgY2xlYW5fdGV4dCA8LSBjKGNsZWFuX3RleHQsIFN0ckNsZWFuaW5nKHRleHRbaV0pKQ0KICAgICAgICB9DQogICAgICAgICMgQ2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgdG9rZW5zIGFuZCB1bmlxdWUgdG9rZW5zIGFuZCByZXR1cm4gdGhlbSBpbiBhIA0KICAgICAgICAjIG5hbWVkIGxpc3Qgb2JqZWN0Lg0KICAgICAgICBudW1fdG9rIDwtIGxlbmd0aChjbGVhbl90ZXh0KQ0KICAgICAgICBudW1fdW5pcSA8LSBsZW5ndGgodW5pcXVlKGNsZWFuX3RleHQpKQ0KICAgICAgICB0b19yZXR1cm4gPC0gbGlzdChudW1fdG9rZW5zID0gbnVtX3RvaywgDQoJCSAgICAgICAgICAgICAgICAgICAgIHVuaXF1ZV90b2tlbnMgPSBudW1fdW5pcSwgDQoJCQkJCQkJIHRleHQgPSBjbGVhbl90ZXh0KQ0KICAgIH0NCgkNCiAgICByZXR1cm4odG9fcmV0dXJuKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KdGV4dCA8LSByZWFkTGluZXMoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDQvT2JhbWFfU3BlZWNoXzItMjQtMDkudHh0IikNClRleHRWZWMgPSBDbGVhblRleHREb2NzKHRleHQpDQpzb3J0KHRhYmxlKFRleHRWZWMkdGV4dCksIGRlY3JlYXNpbmcgPSBUUlVFKVsxOjMwXQ0KYGBgDQoNClRoZSBhYm92ZSBmcmVxdWVuY3kgdGFibGUgY29udGFpbnMgbWFueSB3b3JkcyB0aGF0IGhhdmUgbGVzcyBpbmZvcm1hdGlvbi4gSW4gdGV4dCBtaW5pbmcsIHRoZXNlIHR5cGVzIG9mIHdvcmRzIGFyZSBjYWxsZWQgYHN0b3B3b3Jkc2AgdGhhdCBuZWVkIHRvIGJlIHJlbW92ZWQgZnJvbSB0aGUgdGV4dCBhbmFseXNpcy4gSW4gUiB0ZXh0IG1pbmluZyBwYWNrYWdlIGB0bWAgbGlzdHMgYWJvdXQgMTc1IGBzdG9wd29yZHNgLiBUaGUgZGV0YWlsZWQgbGlzdCBjYW4gYmUgZm91bmQgdXNpbmcgdGhlIGNvZGUNCg0KYGBgDQpzdG9wd29yZHMoa2luZCA9ICJlbiIpDQpgYGANCg0KTmV4dCwgd2UgcmVtb3ZlIGFsbCBgc3RvcHdvcmRzYCBhbmQgbWVhbmluZ2xlc3Mgc3RyaW5ncyBmcm9tIHRoZSBhYm92ZSBwYXJ0aWFsbHkgcHJvY2Vzc2VkIHRleHQgYW5kIG1ha2UgYSBmcmVxdWVuY3kgdGFibGUgb2Ygd29yZHMgaW4gdGhlIHNwZWVjaC4NCg0KDQpgYGB7cn0NCnJlbW92ZWRUZXh0ID0gcmVtb3ZlV29yZHMoVGV4dFZlYyR0ZXh0LCBjKHN0b3B3b3JkcygiZW5nbGlzaCIpLCJzIiwgInQiLCAidmUiKSkNCiMjIHJlbW92ZSBlbXB0eSBzdHJpbmcgIiINCnJlbW92ZWRUZXh0ID1yZW1vdmVkVGV4dFtyZW1vdmVkVGV4dCE9IiJdDQojIyBNYWtlIGEgZnJlcXVlbmN5IHRhYmxlDQpmcmVxVmVjID0gc29ydCh0YWJsZShyZW1vdmVkVGV4dCksIGRlY3JlYXNpbmcgPSBUUlVFKQ0KZnJlcVZlY1sxOjIwXQ0KYGBgDQoNCmBgYHtyfQ0KeHRpY2sgPSBuYW1lcyhmcmVxVmVjWzE6MjBdKQ0KeCA9IGJhcnBsb3QoZnJlcVZlY1sxOjIwXSwgY29sID0gaGVhdC5jb2xvcnMoMjApLCB4YXh0PSJuIikNCiMjIHhwZCA9IFRSVUUgb3IgTkEgYWxsb3dzIHRleHQgYW5ub3RhdGlvbiBwbG90dGVkIG91dHNpZGUgdGhlIHBsb3QgcmVnaW9uDQp0ZXh0KGNleD0wLjc1LCB4ID0geCAtLjI1LCB5ID0gLTEwLCBsYWJlbHMgPSB4dGljaywgeHBkPVRSVUUsIHNydD03NSwgY29sID0gIm5hdnkiKQ0KDQpgYGANCg0KVGhlIGFib3ZlIGJhciBjaGFydCBzaG93cyB0aGUgZnJlcXVlbmNpZXMgb2YgdGhlIGZpcnN0IDIwIHdvcmRzIHdpdGggdGhlIGhpZ2hlc3QgZnJlcXVlbmN5LiBXZSBjYW4gc2VlIHRoZSAqKnJvdWdoIGlkZWEqKiBvZiB0aGUgc3BlZWNoOiBhY3Rpb25zIGZvciB0aGUgVVMgZWNvbm9teSwgaGVhbHRoIGNhcmUsIGVkdWNhdGlvbiwgZXRjLg0KDQoNCiMgVGV4dCBGaWxlIFByb2Nlc3Npbmcgd2l0aCBMaWJyYXJ5IGB0bWANCg0KUiBoYXMgYSBwb3dlcmZ1bCBsaWJyYXJ5IGZvciB0ZXh0IG1pbmluZy4gVGhlIGZvbGxvd2luZyB0d28gZnVuY3Rpb25zIGNhbiBiZSB1c2VkIHRvIHNjcmFwZSBpbnRlcm5ldCBmaWxlcyBhbmQgY2xlYW4gdGhlbSBmb3IgYmFzaWMgdGV4dCBhbmFseXRpY3MgaW5jbHVkaW5nIHZpc3VhbCByZXByZXNlbnRhdGlvbiB1c2luZyB3b3JkY2xvdWQuDQoNCmBgYHtyfQ0KIysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysNCiMgcnF1ZXJ5LndvcmRjbG91ZCgpIDogV29yZCBjbG91ZCBnZW5lcmF0b3INCiMgLSBodHRwOi8vd3d3LnN0aGRhLmNvbQ0KIysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrKysrDQojIHggOiBjaGFyYWN0ZXIgc3RyaW5nIChwbGFpbiB0ZXh0LCB3ZWIgdXJsLCB0eHQgZmlsZSBwYXRoKQ0KIyB0eXBlIDogc3BlY2lmeSB3aGV0aGVyIHggaXMgYSBwbGFpbiB0ZXh0LCBhIHdlYiBwYWdlIHVybCBvciBhIGZpbGUgcGF0aA0KIyBsYW5nIDogdGhlIGxhbmd1YWdlIG9mIHRoZSB0ZXh0DQojIGV4Y2x1ZGVXb3JkcyA6IGEgdmVjdG9yIG9mIHdvcmRzIHRvIGV4Y2x1ZGUgZnJvbSB0aGUgdGV4dA0KIyB0ZXh0U3RlbW1pbmcgOiByZWR1Y2VzIHdvcmRzIHRvIHRoZWlyIHJvb3QgZm9ybQ0KIyBjb2xvclBhbGV0dGUgOiB0aGUgbmFtZSBvZiBjb2xvciBwYWxldHRlIHRha2VuIGZyb20gUkNvbG9yQnJld2VyIHBhY2thZ2UsIA0KICAjIG9yIGEgY29sb3IgbmFtZSwgb3IgYSBjb2xvciBjb2RlDQojIG1pbi5mcmVxIDogd29yZHMgd2l0aCBmcmVxdWVuY3kgYmVsb3cgbWluLmZyZXEgd2lsbCBub3QgYmUgcGxvdHRlZA0KIyBtYXgud29yZHMgOiBNYXhpbXVtIG51bWJlciBvZiB3b3JkcyB0byBiZSBwbG90dGVkLiBsZWFzdCBmcmVxdWVudCB0ZXJtcyBkcm9wcGVkDQoNCiMgdmFsdWUgcmV0dXJuZWQgYnkgdGhlIGZ1bmN0aW9uIDogYSBsaXN0KHRkbSwgZnJlcVRhYmxlKQ0KcnF1ZXJ5LndvcmRjbG91ZCA8LSBmdW5jdGlvbih4LCB0eXBlPWMoInRleHQiLCAidXJsIiwgImZpbGUiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxhbmc9ImVuZ2xpc2giLCBleGNsdWRlV29yZHM9TlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRleHRTdGVtbWluZz1GQUxTRSwgIGNvbG9yUGFsZXR0ZT0iRGFyazIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4uZnJlcT0zLCBtYXgud29yZHM9MjAwMCkNCnsgDQogIGlmKHR5cGVbMV09PSJmaWxlIikgdGV4dCA8LSByZWFkTGluZXMoeCkNCiAgZWxzZSBpZih0eXBlWzFdPT0idXJsIikgdGV4dCA8LSBodG1sX3RvX3RleHQoeCkNCiAgZWxzZSBpZih0eXBlWzFdPT0idGV4dCIpIHRleHQgPC0geA0KICANCiAgIyBMb2FkIHRoZSB0ZXh0IGFzIGEgY29ycHVzDQogIGRvY3MgPC0gQ29ycHVzKFZlY3RvclNvdXJjZSh0ZXh0KSkNCiAgIyBDb252ZXJ0IHRoZSB0ZXh0IHRvIGxvd2VyIGNhc2UNCiAgZG9jcyA8LSB0bV9tYXAoZG9jcywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkNCiAgIyBSZW1vdmUgbnVtYmVycw0KICBkb2NzIDwtIHRtX21hcChkb2NzLCByZW1vdmVOdW1iZXJzKQ0KICAjIFJlbW92ZSBzdG9wd29yZHMgZm9yIHRoZSBsYW5ndWFnZSANCiAgZG9jcyA8LSB0bV9tYXAoZG9jcywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcyhsYW5nKSkNCiAgIyBSZW1vdmUgcHVuY3R1YXRpb25zDQogIGRvY3MgPC0gdG1fbWFwKGRvY3MsIHJlbW92ZVB1bmN0dWF0aW9uKQ0KICAjIEVsaW1pbmF0ZSBleHRyYSB3aGl0ZSBzcGFjZXMNCiAgZG9jcyA8LSB0bV9tYXAoZG9jcywgc3RyaXBXaGl0ZXNwYWNlKQ0KICAjIFJlbW92ZSB5b3VyIG93biBzdG9wd29yZHMNCiAgaWYoIWlzLm51bGwoZXhjbHVkZVdvcmRzKSkgDQogICAgZG9jcyA8LSB0bV9tYXAoZG9jcywgcmVtb3ZlV29yZHMsIGV4Y2x1ZGVXb3JkcykgDQogICMgVGV4dCBzdGVtbWluZw0KICBpZih0ZXh0U3RlbW1pbmcpIGRvY3MgPC0gdG1fbWFwKGRvY3MsIHN0ZW1Eb2N1bWVudCkNCiAgIyBDcmVhdGUgdGVybS1kb2N1bWVudCBtYXRyaXgNCiAgdGRtIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChkb2NzKQ0KICBtIDwtIGFzLm1hdHJpeCh0ZG0pDQogIHYgPC0gc29ydChyb3dTdW1zKG0pLGRlY3JlYXNpbmc9VFJVRSkNCiAgZCA8LSBkYXRhLmZyYW1lKHdvcmQgPSBuYW1lcyh2KSxmcmVxPXYpDQogICMgY2hlY2sgdGhlIGNvbG9yIHBhbGV0dGUgbmFtZSANCiAgaWYoIWNvbG9yUGFsZXR0ZSAlaW4lIHJvd25hbWVzKGJyZXdlci5wYWwuaW5mbykpIGNvbG9ycyA9IGNvbG9yUGFsZXR0ZQ0KICBlbHNlIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgY29sb3JQYWxldHRlKSANCiAgIyBQbG90IHRoZSB3b3JkIGNsb3VkDQogIHNldC5zZWVkKDEyMzQpDQogIHdvcmRjbG91ZChkJHdvcmQsZCRmcmVxLCBtaW4uZnJlcT1taW4uZnJlcSwgbWF4LndvcmRzPW1heC53b3JkcywNCiAgICAgICAgICAgIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjM1LCANCiAgICAgICAgICAgIHVzZS5yLmxheW91dD1GQUxTRSwgY29sb3JzPWNvbG9ycykNCiAgDQogIGludmlzaWJsZShsaXN0KHRkbT10ZG0sIGZyZXFUYWJsZSA9IGQpKQ0KfQ0KDQojKysrKysrKysrKysrKysrKysrKysrKw0KIyBIZWxwZXIgZnVuY3Rpb24NCiMrKysrKysrKysrKysrKysrKysrKysrDQojIERvd25sb2FkIGFuZCBwYXJzZSB3ZWJwYWdlDQpodG1sX3RvX3RleHQ8LWZ1bmN0aW9uKHVybCl7DQogICMgZG93bmxvYWQgaHRtbA0KICBodG1sLmRvYyA8LSBnZXRVUkwodXJsKSAgDQogICNjb252ZXJ0IHRvIHBsYWluIHRleHQNCiAgZG9jID0gaHRtbFBhcnNlKGh0bWwuZG9jLCBhc1RleHQ9VFJVRSkNCiAjICIvL3RleHQoKSIgcmV0dXJucyBhbGwgdGV4dCBvdXRzaWRlIG9mIEhUTUwgdGFncy4NCiAjIFdlIGFsc28gZG9uJ3Qgd2FudCB0ZXh0IHN1Y2ggYXMgc3R5bGUgYW5kIHNjcmlwdCBjb2Rlcw0KICB0ZXh0IDwtIHhwYXRoU0FwcGx5KGRvYywgIi8vdGV4dCgpW25vdChhbmNlc3Rvcjo6c2NyaXB0KV1bbm90KGFuY2VzdG9yOjpzdHlsZSldW25vdChhbmNlc3Rvcjo6bm9zY3JpcHQpXVtub3QoYW5jZXN0b3I6OmZvcm0pXSIsIHhtbFZhbHVlKQ0KICAjIEZvcm1hdCB0ZXh0IHZlY3RvciBpbnRvIG9uZSBjaGFyYWN0ZXIgc3RyaW5nDQogIHJldHVybihwYXN0ZSh0ZXh0LCBjb2xsYXBzZSA9ICIgIikpDQp9DQpgYGANCg0KDQpgYGB7cn0NCnJxdWVyeS53b3JkY2xvdWQoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDQvT2JhbWFfU3BlZWNoXzItMjQtMDkudHh0IiwgdHlwZT0idXJsIikNCmBgYA0K