Lab 01: Regular expressions

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

We will learn more about regular expressions and string processing in R. Please take into your consideration that regular expressions are indeed complicated and in that fact it takes a course on its own in order to master them completely. Here we will cover only some elementary applications of regular expressions that are useful in simple data cleaning operations.

1. Regular expressions

Please consider this piece of documentation seriously: regex

1.1 Introduction: grepl(), grep(), and regexpr()

Let’s begin by reviewing what we have already learned: grepl()

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grepl(pattern = "plane", x = strings)
[1]  TRUE  TRUE FALSE FALSE  TRUE

The base R function grepl() asks us to define a pattern - which is a regex - and then to provide a value for the x argument which is a string (or a vector of strings, as in this example) in which we want to look for the pattern. Our first example is very simple: we ask if "plane" is present in "hyperplane", "airplane", "filter", "dplyr", or "plane", and the result is of course: TRUE, TRUE, FALSE, FALSE, TRUE.

What about the following:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grepl(pattern = "Plane", x = strings)
[1] FALSE FALSE FALSE FALSE FALSE

Well, of course: "Plane" is simply not the same as "plane". Now, grepl() has another argument that we did not use before, ignore.case. It is a logical one:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grepl(pattern = "Plane", x = strings, ignore.case = TRUE)
[1]  TRUE  TRUE FALSE FALSE  TRUE

Enter regular expressions: remember that ^ represents the beginning of the string?

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grepl(pattern = "^plane", x = strings, ignore.case = TRUE)
[1] FALSE FALSE FALSE FALSE  TRUE

and of course only "plane" in strings begins with "plane".

What elements of the strings character vector end with plane? We have already seen in this course that as ^ represents the empty character at the beginning of the string there is $ that represents the empty character at the end of the string:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grepl(pattern = "plane$", x = strings, ignore.case = TRUE)
[1]  TRUE  TRUE FALSE FALSE  TRUE

Besides grepl() we also have grep() in base R:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grep(pattern = "plane", x = strings)
[1] 1 2 5

Unlike grepl() that returns a logical vector, grep() returns the indices of x where the pattern is found. For example:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grep(pattern = "^plane", x = strings)
[1] 5

Or:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grep(pattern = "plane$", x = strings)
[1] 1 2 5

But we can also ask for the values from grep():

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
grep(pattern = "plane$", x = strings, value = T)
[1] "hyperplane" "airplane"   "plane"     

Another important base R function to work with regular expressions is regexpr().

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
regexpr(pattern = "plane", text = strings)
[1]  6  4 -1 -1  1
attr(,"match.length")
[1]  5  5 -1 -1  5
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUE

Please disregard the useBytes and index.type attributes for now. What is interesting here is the function output and the match.length attribute. The value that the function returns is the position where pattern begins in each element of text in which the pattern is actually found, with -1 indicating that the pattern was not found indeed:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
foundWhere <- regexpr(pattern = "plane", text = strings)
foundWhere[1]
[1] 6

This means that the pattern was found to begin in the 6h position of the first element of text, which is: “hyperplane”. What was the length of the pattern found?

attr(foundWhere, 'match.length')[1]
[1] 5

Now this you have not seen before. R objects have attributes, and the attributes are accessed via the attr() function. In this case, attr(foundWhere, 'match.length')[1] asks for the first value of the match.length attribute of the foundWhere object.

This is how we find where patterns begin and end in strings in base R, look:

strings = c("hyperplane", "airplane", "filter", "dplyr", "plane")
foundWhere <- regexpr(pattern = "plane", text = strings)
start = foundWhere[1]
end = attr(foundWhere, 'match.length')[1]
cat(
  paste0('"plane" is found in "',
         strings[1],
         '" beginning in the ',
         start, 'th position and ending in the ',
         start+end, 'th position'))
"plane" is found in "hyperplane" beginning in the 6th position and ending in the 11th position

and it follows that the value of 'match.length' attribute corresponds to the value of the pattern that we were looking for!

Hint: study what gregexpr() does in base R + learn about the difference between print() and cat() in R.

1.2 Regex, seriously

We understand the meaning of ^ and $ in regex already. Now we want to learn about the meaning of +, *, ., [, ], and |.

Imagine that we are facing the task to analyze some aspect of a system that encompasses user names of the following form:

userID <-  "Maria0001449"
grepl("0001449", userID)
[1] TRUE

Of course. However, what if the system imposes the following rule: a user name must begin with the user’s real name, followed by any number of digits?

See:

userID <-  "Maria0001449"
grepl("[0123456789]+$", userID)
[1] TRUE

What we find in between [ and ] is a character class: it matches any character found in it. What + means is: the previous character - or a character class, in our example - is found once or more than once. So, the semantics of [0123456789]+$ is exactly the following one:

  • any of 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 is found,
  • and it is repeated (hint: +) once or more than once, and then
  • the string ends (hint: $).

What if the system has the following rule for user names: a user name must begin with the user’s real name, followed or not by any number of digits?

userID <-  "Maria0001449"
grepl("[0123456789]*$", userID)
[1] TRUE

Ok, but also:

userID <-  "Maria"
grepl("[0123456789]*$", userID)
[1] TRUE

Because in regular expressions * means: the previous repeats zero or as many times!

So, + and * are *quantifiers` in regular expressions. Again, character classes:

string <-  "ABCDE"
grepl("[Y|I|O]", string)
[1] FALSE

What is |? Let’ see:

string <-  "ABCDE"
grepl("[Y|A|O]", string)
[1] TRUE
string <-  "ABCDE"
grepl("[Y|9|O]", string)
[1] FALSE
string <-  "ABCDE"
grepl("[D|E|0]", string)
[1] TRUE

So | means: logical OR :)

The following one: . is dangerous. The . means: just anything:

string <-  "ABCDE"
grepl(".", string)
[1] TRUE

And it is, of course, found everywhere:

gregexpr(".", string)
[[1]]
[1] 1 2 3 4 5
attr(,"match.length")
[1] 1 1 1 1 1
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUE

So what if we need to recognize . literally in a string? Well, we have to escape it:

string <-  "Goran.S.Milovanovic"
gregexpr("\.", string)
Error: '\.' is an unrecognized escape in character string starting ""\."

No, no… This is what we need:

string <-  "Goran.S.Milovanovic"
gregexpr("\\.", string)
[[1]]
[1] 6 8
attr(,"match.length")
[1] 1 1
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUE

And yes . is found in the sixth and eighth position in Goran.S.Milovanovic!

Why the double backslash: \\? Because the backslash escapes in R, but in regex also, of course, so we need to inform R that what follows the first \ needs to be interpreted not as some special character but literally as what it is, and then the second \ informs the regex engine that what follows it - and that would be . - should be escaped, i.e. not interpreted as anything (regex semantics) but as the . character literally.

Complicated? You will get used to it, do not worry… Remember that Data Science is the sexiest profession of the 21st century. Well is it not? :)

Look:

string <-  "Goran.S.Milovanovic0528$@3674"
grepl("[[:alpha:]]+\\.[[:alpha:]]\\.[[:alpha:]]+.+", string)
[1] TRUE

[[:alpha:]]+\\.[[:alpha:]]\\.[[:alpha:]]+.+ means:

  • a letter ([[:alpha:]]),
  • occurring once or more than once (+), is followed by
  • a dot (\\.), followed by
  • a single letter ([[:alpha:]]), followed by
  • a dot (\\.), followed by
  • a letter ([[:alpha:]]),
  • occurring once or more than once (+), followed by
  • just anything repeated once or more than once (.+).

What is [[:alpha:]]?

1.3 Predefined character classes

From the Regex: Regular Expressions As Used In R documentation page:

Certain named classes of characters are predefined.

[:alnum:]
Alphanumeric characters: [:alpha:] and [:digit:].

[:alpha:]
Alphabetic characters: [:lower:] and [:upper:].

[:blank:]
Blank characters: space and tab, and possibly other locale-dependent characters such as non-breaking space.

[:cntrl:]
Control characters. In ASCII, these characters have octal codes 000 through 037, and 177 (DEL). In another character set, these are the equivalent characters, if any.

[:digit:]
Digits: 0 1 2 3 4 5 6 7 8 9.

[:graph:]
Graphical characters: [:alnum:] and [:punct:].

[:lower:]
Lower-case letters in the current locale.

[:print:]
Printable characters: [:alnum:], [:punct:] and space.

[:punct:]
Punctuation characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~.

[:space:]
Space characters: tab, newline, vertical tab, form feed, carriage return, space and possibly other locale-dependent characters.

[:upper:]
Upper-case letters in the current locale.

[:xdigit:]
Hexadecimal digits: 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f.

Q. Ok, but why the double square brackets, like in: [[:alpha:]]?

A. Because [ and ] in [:alpha:] are simply a part of the predefined name, and we still want to inform the regex engine that we mean: a character class.

1.4 Backreferences and gsub()

Did we mention gsub() in the past? I think we already did:

string <-  "New York City"
gsub("New", "Old", string)
[1] "Old York City"

Ok. Now what if I would like to change each occurrence of #err to an empty string "" in the following:

string <-  "someDatabas#erreRecordGoneWrong"
gsub("#err", "", string)
[1] "someDatabaseRecordGoneWrong"

Great. Now we know how to delete things that we do not need in strings.

Now, the backreferences in regex with gsub(). Imagine that we are facing the following situation:

strings <- c("NewYork", "NewAmsterdam", "NewBelgrade")
print(strings)
[1] "NewYork"      "NewAmsterdam" "NewBelgrade" 

Obviously, a set of typos, all missing a white space, which can be fixed in the following way:

gsub("(New)", "\\1 ", strings)
[1] "New York"      "New Amsterdam" "New Belgrade" 

\\1 in this example is a backreference which refers to the first parenthesized expression in the pattern "(New)"; it will be replaced by itself concatenated with a white space - "\\1 " in gsub()! Let’s elaborate; in

gsub(pattern, replacement, x)

we have used "(New)" as a pattern, and "\\1 " as a replacement, where strings played a role of x: the replacement used \\1 as a backreference to (New) in the pattern argument "(New)".

Further Readings

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gTGFiMDENCmF1dGhvcjoNCi0gbmFtZTogR29yYW4gUy4gTWlsb3Zhbm92acSHLCBQaEQNCiAgYWZmaWxpYXRpb246IERhdGFLb2xla3RpdiwgQ2hpZWYgU2NpZW50aXN0ICYgT3duZXI7IERhdGEgU2NpZW50aXN0IGZvciBXaWtpZGF0YSwgV01ERQ0KYWJzdHJhY3Q6IA0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNQ0KLS0tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQojIExhYiAwMTogUmVndWxhciBleHByZXNzaW9ucw0KIA0KKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tYC4gDQpUaGVzZSBub3RlYm9va3MgYWNjb21wYW55IHRoZSBJbnRybyB0byBEYXRhIFNjaWVuY2U6IE5vbi1UZWNobmljYWwgQmFja2dyb3VuZCBjb3Vyc2UgMjAyMC8yMS4NCg0KKioqDQoNCiMjIyBXaGF0IGRvIHdlIHdhbnQgdG8gZG8gdG9kYXk/DQoNCldlIHdpbGwgbGVhcm4gbW9yZSBhYm91dCByZWd1bGFyIGV4cHJlc3Npb25zIGFuZCBzdHJpbmcgcHJvY2Vzc2luZyBpbiBSLiBQbGVhc2UgdGFrZSBpbnRvIHlvdXIgY29uc2lkZXJhdGlvbiB0aGF0IHJlZ3VsYXIgZXhwcmVzc2lvbnMgYXJlIGluZGVlZCBjb21wbGljYXRlZCBhbmQgaW4gdGhhdCBmYWN0IGl0IHRha2VzIGEgY291cnNlIG9uIGl0cyBvd24gaW4gb3JkZXIgdG8gbWFzdGVyIHRoZW0gY29tcGxldGVseS4gSGVyZSB3ZSB3aWxsIGNvdmVyIG9ubHkgc29tZSBlbGVtZW50YXJ5IGFwcGxpY2F0aW9ucyBvZiByZWd1bGFyIGV4cHJlc3Npb25zIHRoYXQgYXJlIHVzZWZ1bCBpbiBzaW1wbGUgZGF0YSBjbGVhbmluZyBvcGVyYXRpb25zLg0KDQojIyMgMS4gUmVndWxhciBleHByZXNzaW9ucw0KDQoqKlBsZWFzZSoqIGNvbnNpZGVyIHRoaXMgcGllY2Ugb2YgZG9jdW1lbnRhdGlvbiBzZXJpb3VzbHk6IFtyZWdleF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2Jhc2UvdmVyc2lvbnMvMy42LjIvdG9waWNzL3JlZ2V4KSANCg0KIyMjIyAxLjEgSW50cm9kdWN0aW9uOiBgZ3JlcGwoKWAsIGBncmVwKClgLCBhbmQgYHJlZ2V4cHIoKWANCg0KTGV0J3MgYmVnaW4gYnkgcmV2aWV3aW5nIHdoYXQgd2UgaGF2ZSBhbHJlYWR5IGxlYXJuZWQ6IGBncmVwbCgpYA0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZ3MgPC0gYygiaHlwZXJwbGFuZSIsICJhaXJwbGFuZSIsICJmaWx0ZXIiLCAiZHBseXIiLCAicGxhbmUiKQ0KZ3JlcGwocGF0dGVybiA9ICJwbGFuZSIsIHggPSBzdHJpbmdzKQ0KYGBgDQpUaGUgYmFzZSBSIGZ1bmN0aW9uIGBncmVwbCgpYCBhc2tzIHVzIHRvIGRlZmluZSBhIGBwYXR0ZXJuYCAtIHdoaWNoIGlzIGEgcmVnZXggLSBhbmQgdGhlbiB0byBwcm92aWRlIGEgdmFsdWUgZm9yIHRoZSBgeGAgYXJndW1lbnQgd2hpY2ggaXMgYSAqc3RyaW5nKiAob3IgYSB2ZWN0b3Igb2Ygc3RyaW5ncywgYXMgaW4gdGhpcyBleGFtcGxlKSBpbiB3aGljaCB3ZSB3YW50IHRvIGxvb2sgZm9yIHRoZSBgcGF0dGVybmAuIE91ciBmaXJzdCBleGFtcGxlIGlzIHZlcnkgc2ltcGxlOiB3ZSBhc2sgaWYgYCJwbGFuZSJgIGlzIHByZXNlbnQgaW4gYCJoeXBlcnBsYW5lImAsIGAiYWlycGxhbmUiYCwgYCJmaWx0ZXIiYCwgYCJkcGx5ciJgLCBvciBgInBsYW5lImAsIGFuZCB0aGUgcmVzdWx0IGlzIG9mIGNvdXJzZTogYFRSVUVgLCBgVFJVRWAsIGBGQUxTRWAsIGBGQUxTRWAsIGAgVFJVRWAuDQoNCldoYXQgYWJvdXQgdGhlIGZvbGxvd2luZzoNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmdzIDwtIGMoImh5cGVycGxhbmUiLCAiYWlycGxhbmUiLCAiZmlsdGVyIiwgImRwbHlyIiwgInBsYW5lIikNCmdyZXBsKHBhdHRlcm4gPSAiUGxhbmUiLCB4ID0gc3RyaW5ncykNCmBgYA0KV2VsbCwgb2YgY291cnNlOiBgIlBsYW5lImAgaXMgc2ltcGx5IG5vdCB0aGUgc2FtZSBhcyBgInBsYW5lImAuIE5vdywgYGdyZXBsKClgIGhhcyBhbm90aGVyIGFyZ3VtZW50IHRoYXQgd2UgZGlkIG5vdCB1c2UgYmVmb3JlLCBgaWdub3JlLmNhc2VgLiBJdCBpcyBhIGxvZ2ljYWwgb25lOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZ3MgPC0gYygiaHlwZXJwbGFuZSIsICJhaXJwbGFuZSIsICJmaWx0ZXIiLCAiZHBseXIiLCAicGxhbmUiKQ0KZ3JlcGwocGF0dGVybiA9ICJQbGFuZSIsIHggPSBzdHJpbmdzLCBpZ25vcmUuY2FzZSA9IFRSVUUpDQpgYGANCg0KRW50ZXIgcmVndWxhciBleHByZXNzaW9uczogcmVtZW1iZXIgdGhhdCBgXmAgcmVwcmVzZW50cyB0aGUgYmVnaW5uaW5nIG9mIHRoZSBzdHJpbmc/DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5ncyA8LSBjKCJoeXBlcnBsYW5lIiwgImFpcnBsYW5lIiwgImZpbHRlciIsICJkcGx5ciIsICJwbGFuZSIpDQpncmVwbChwYXR0ZXJuID0gIl5wbGFuZSIsIHggPSBzdHJpbmdzLCBpZ25vcmUuY2FzZSA9IFRSVUUpDQpgYGANCmFuZCBvZiBjb3Vyc2Ugb25seSBgInBsYW5lImAgaW4gYHN0cmluZ3NgIGJlZ2lucyB3aXRoIGAicGxhbmUiYC4gDQoNCldoYXQgZWxlbWVudHMgb2YgdGhlIGBzdHJpbmdzYCBjaGFyYWN0ZXIgdmVjdG9yIGVuZCB3aXRoIGBwbGFuZWA/IFdlIGhhdmUgYWxyZWFkeSBzZWVuIGluIHRoaXMgY291cnNlIHRoYXQgYXMgYF5gIHJlcHJlc2VudHMgdGhlIGVtcHR5IGNoYXJhY3RlciBhdCB0aGUgYmVnaW5uaW5nIG9mIHRoZSBzdHJpbmcgdGhlcmUgaXMgYCRgIHRoYXQgcmVwcmVzZW50cyB0aGUgZW1wdHkgY2hhcmFjdGVyIGF0IHRoZSAqZW5kIG9mIHRoZSBzdHJpbmcqOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZ3MgPC0gYygiaHlwZXJwbGFuZSIsICJhaXJwbGFuZSIsICJmaWx0ZXIiLCAiZHBseXIiLCAicGxhbmUiKQ0KZ3JlcGwocGF0dGVybiA9ICJwbGFuZSQiLCB4ID0gc3RyaW5ncywgaWdub3JlLmNhc2UgPSBUUlVFKQ0KYGBgDQpCZXNpZGVzIGBncmVwbCgpYCB3ZSBhbHNvIGhhdmUgYGdyZXAoKWAgaW4gYmFzZSBSOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZ3MgPC0gYygiaHlwZXJwbGFuZSIsICJhaXJwbGFuZSIsICJmaWx0ZXIiLCAiZHBseXIiLCAicGxhbmUiKQ0KZ3JlcChwYXR0ZXJuID0gInBsYW5lIiwgeCA9IHN0cmluZ3MpDQpgYGANClVubGlrZSBgZ3JlcGwoKWAgdGhhdCByZXR1cm5zIGEgbG9naWNhbCB2ZWN0b3IsIGBncmVwKClgIHJldHVybnMgdGhlIGluZGljZXMgb2YgYHhgIHdoZXJlIHRoZSBwYXR0ZXJuIGlzIGZvdW5kLiBGb3IgZXhhbXBsZToNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmdzIDwtIGMoImh5cGVycGxhbmUiLCAiYWlycGxhbmUiLCAiZmlsdGVyIiwgImRwbHlyIiwgInBsYW5lIikNCmdyZXAocGF0dGVybiA9ICJecGxhbmUiLCB4ID0gc3RyaW5ncykNCmBgYA0KT3I6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5ncyA8LSBjKCJoeXBlcnBsYW5lIiwgImFpcnBsYW5lIiwgImZpbHRlciIsICJkcGx5ciIsICJwbGFuZSIpDQpncmVwKHBhdHRlcm4gPSAicGxhbmUkIiwgeCA9IHN0cmluZ3MpDQpgYGANCkJ1dCB3ZSBjYW4gYWxzbyBhc2sgZm9yIHRoZSB2YWx1ZXMgZnJvbSBgZ3JlcCgpYDoNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmdzIDwtIGMoImh5cGVycGxhbmUiLCAiYWlycGxhbmUiLCAiZmlsdGVyIiwgImRwbHlyIiwgInBsYW5lIikNCmdyZXAocGF0dGVybiA9ICJwbGFuZSQiLCB4ID0gc3RyaW5ncywgdmFsdWUgPSBUKQ0KYGBgDQpBbm90aGVyIGltcG9ydGFudCBiYXNlIFIgZnVuY3Rpb24gdG8gd29yayB3aXRoIHJlZ3VsYXIgZXhwcmVzc2lvbnMgaXMgYHJlZ2V4cHIoKWAuDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5ncyA8LSBjKCJoeXBlcnBsYW5lIiwgImFpcnBsYW5lIiwgImZpbHRlciIsICJkcGx5ciIsICJwbGFuZSIpDQpyZWdleHByKHBhdHRlcm4gPSAicGxhbmUiLCB0ZXh0ID0gc3RyaW5ncykNCmBgYA0KUGxlYXNlIGRpc3JlZ2FyZCB0aGUgYHVzZUJ5dGVzYCBhbmQgYGluZGV4LnR5cGVgIGF0dHJpYnV0ZXMgZm9yIG5vdy4gV2hhdCBpcyBpbnRlcmVzdGluZyBoZXJlIGlzIHRoZSBmdW5jdGlvbiBvdXRwdXQgYW5kIHRoZSAgYG1hdGNoLmxlbmd0aGAgYXR0cmlidXRlLiBUaGUgdmFsdWUgdGhhdCB0aGUgZnVuY3Rpb24gcmV0dXJucyBpcyB0aGUgcG9zaXRpb24gd2hlcmUgYHBhdHRlcm5gIGJlZ2lucyBpbiBlYWNoIGVsZW1lbnQgb2YgYHRleHRgIGluIHdoaWNoIHRoZSBgcGF0dGVybmAgaXMgYWN0dWFsbHkgZm91bmQsIHdpdGggYC0xYCBpbmRpY2F0aW5nIHRoYXQgdGhlIHBhdHRlcm4gd2FzIG5vdCBmb3VuZCBpbmRlZWQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5ncyA8LSBjKCJoeXBlcnBsYW5lIiwgImFpcnBsYW5lIiwgImZpbHRlciIsICJkcGx5ciIsICJwbGFuZSIpDQpmb3VuZFdoZXJlIDwtIHJlZ2V4cHIocGF0dGVybiA9ICJwbGFuZSIsIHRleHQgPSBzdHJpbmdzKQ0KZm91bmRXaGVyZVsxXQ0KYGBgDQpUaGlzIG1lYW5zIHRoYXQgdGhlIGBwYXR0ZXJuYCB3YXMgZm91bmQgdG8gYmVnaW4gaW4gdGhlIDZoIHBvc2l0aW9uIG9mIHRoZSBmaXJzdCBlbGVtZW50IG9mIGB0ZXh0YCwgd2hpY2ggaXM6ICJoeXBlcnBsYW5lIi4gV2hhdCB3YXMgdGhlIGxlbmd0aCBvZiB0aGUgcGF0dGVybiBmb3VuZD8NCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmdzIDwtIGMoImh5cGVycGxhbmUiLCAiYWlycGxhbmUiLCAiZmlsdGVyIiwgImRwbHlyIiwgInBsYW5lIikNCmZvdW5kV2hlcmUgPC0gcmVnZXhwcihwYXR0ZXJuID0gInBsYW5lIiwgdGV4dCA9IHN0cmluZ3MpDQphdHRyKGZvdW5kV2hlcmUsICdtYXRjaC5sZW5ndGgnKVsxXQ0KYGBgDQpOb3cgdGhpcyB5b3UgaGF2ZSBub3Qgc2VlbiBiZWZvcmUuIFIgb2JqZWN0cyBoYXZlICphdHRyaWJ1dGVzKiwgYW5kIHRoZSBhdHRyaWJ1dGVzIGFyZSBhY2Nlc3NlZCB2aWEgdGhlIGBhdHRyKClgIGZ1bmN0aW9uLiBJbiB0aGlzIGNhc2UsIGBhdHRyKGZvdW5kV2hlcmUsICdtYXRjaC5sZW5ndGgnKVsxXWAgYXNrcyBmb3IgdGhlIGZpcnN0IHZhbHVlIG9mIHRoZSBgbWF0Y2gubGVuZ3RoYCBhdHRyaWJ1dGUgb2YgdGhlIGBmb3VuZFdoZXJlYCBvYmplY3QuDQoNClRoaXMgaXMgaG93IHdlIGZpbmQgd2hlcmUgcGF0dGVybnMgYmVnaW4gYW5kIGVuZCBpbiBzdHJpbmdzIGluIGJhc2UgUiwgbG9vazoNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmdzIDwtIGMoImh5cGVycGxhbmUiLCAiYWlycGxhbmUiLCAiZmlsdGVyIiwgImRwbHlyIiwgInBsYW5lIikNCmZvdW5kV2hlcmUgPC0gcmVnZXhwcihwYXR0ZXJuID0gInBsYW5lIiwgdGV4dCA9IHN0cmluZ3MpDQpzdGFydCA8LSBmb3VuZFdoZXJlWzFdDQplbmQgPC0gYXR0cihmb3VuZFdoZXJlLCAnbWF0Y2gubGVuZ3RoJylbMV0NCmNhdCgNCiAgcGFzdGUwKCcicGxhbmUiIGlzIGZvdW5kIGluICInLA0KICAgICAgICAgc3RyaW5nc1sxXSwNCiAgICAgICAgICciIGJlZ2lubmluZyBpbiB0aGUgJywNCiAgICAgICAgIHN0YXJ0LCAndGggcG9zaXRpb24gYW5kIGVuZGluZyBpbiB0aGUgJywNCiAgICAgICAgIHN0YXJ0K2VuZC0xLCAndGggcG9zaXRpb24nKSkNCmBgYA0KYW5kIGl0IGZvbGxvd3MgdGhhdCB0aGUgdmFsdWUgb2YgYCdtYXRjaC5sZW5ndGgnYCBhdHRyaWJ1dGUgY29ycmVzcG9uZHMgdG8gdGhlIHZhbHVlIG9mIHRoZSBgcGF0dGVybmAgdGhhdCB3ZSB3ZXJlIGxvb2tpbmcgZm9yIQ0KDQoqKkhpbnQ6Kiogc3R1ZHkgd2hhdCBgZ3JlZ2V4cHIoKWAgZG9lcyBpbiBiYXNlIFIgKyBsZWFybiBhYm91dCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGBwcmludCgpYCBhbmQgYGNhdCgpYCBpbiBSLg0KDQojIyMjIDEuMiBSZWdleCwgc2VyaW91c2x5DQoNCldlIHVuZGVyc3RhbmQgdGhlIG1lYW5pbmcgb2YgYF5gIGFuZCBgJGAgaW4gcmVnZXggYWxyZWFkeS4gTm93IHdlIHdhbnQgdG8gbGVhcm4gYWJvdXQgdGhlIG1lYW5pbmcgb2YgYCtgLCBgKmAsIGAuYCwgYFtgLCBgXWAsIGFuZCBgfGAuDQoNCkltYWdpbmUgdGhhdCB3ZSBhcmUgZmFjaW5nIHRoZSB0YXNrIHRvIGFuYWx5emUgc29tZSBhc3BlY3Qgb2YgYSBzeXN0ZW0gdGhhdCBlbmNvbXBhc3NlcyB1c2VyIG5hbWVzIG9mIHRoZSBmb2xsb3dpbmcgZm9ybToNCg0KYGBge3IgZWNobyA9IFR9DQp1c2VySUQgPC0gICJNYXJpYTAwMDE0NDkiDQpncmVwbCgiMDAwMTQ0OSIsIHVzZXJJRCkNCmBgYA0KT2YgY291cnNlLiBIb3dldmVyLCB3aGF0IGlmIHRoZSBzeXN0ZW0gaW1wb3NlcyB0aGUgZm9sbG93aW5nIHJ1bGU6IGEgdXNlciBuYW1lIG11c3QgYmVnaW4gd2l0aCB0aGUgdXNlcidzIHJlYWwgbmFtZSwgZm9sbG93ZWQgYnkgKmFueSogbnVtYmVyIG9mIGRpZ2l0cz8NCg0KU2VlOg0KDQpgYGB7ciBlY2hvID0gVH0NCnVzZXJJRCA8LSAgIk1hcmlhMDAwMTQ0OSINCmdyZXBsKCJbMDEyMzQ1Njc4OV0rJCIsIHVzZXJJRCkNCmBgYA0KV2hhdCB3ZSBmaW5kIGluIGJldHdlZW4gYFtgIGFuZCBgXWAgaXMgYSBjaGFyYWN0ZXIgY2xhc3M6IGl0IG1hdGNoZXMgKmFueSogY2hhcmFjdGVyIGZvdW5kIGluIGl0LiBXaGF0IGArYCBtZWFucyBpczogdGhlIHByZXZpb3VzIGNoYXJhY3RlciAtIG9yIGEgY2hhcmFjdGVyIGNsYXNzLCBpbiBvdXIgZXhhbXBsZSAtIGlzIGZvdW5kICpvbmNlIG9yIG1vcmUgdGhhbiBvbmNlKi4gU28sIHRoZSBzZW1hbnRpY3Mgb2YgYFswMTIzNDU2Nzg5XSskYCBpcyBleGFjdGx5IHRoZSBmb2xsb3dpbmcgb25lOg0KDQotICphbnkqIG9mIGAwYCwgYDFgLCBgMmAsIGAzYCwgYDRgLCBgNWAsIGA2YCwgYDdgLCBgOGAsIGA5YCBpcyBmb3VuZCwNCi0gYW5kIGl0IGlzIHJlcGVhdGVkIChoaW50OiBgK2ApIG9uY2Ugb3IgbW9yZSB0aGFuIG9uY2UsIGFuZCB0aGVuIA0KLSB0aGUgc3RyaW5nIGVuZHMgKGhpbnQ6IGAkYCkuDQoNCldoYXQgaWYgdGhlIHN5c3RlbSBoYXMgdGhlIGZvbGxvd2luZyBydWxlIGZvciB1c2VyIG5hbWVzOiBhIHVzZXIgbmFtZSBtdXN0IGJlZ2luIHdpdGggdGhlIHVzZXIncyByZWFsIG5hbWUsIGZvbGxvd2VkICpvciBub3QqIGJ5ICphbnkqIG51bWJlciBvZiBkaWdpdHM/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KdXNlcklEIDwtICAiTWFyaWEwMDAxNDQ5Ig0KZ3JlcGwoIlswMTIzNDU2Nzg5XSokIiwgdXNlcklEKQ0KYGBgDQpPaywgYnV0IGFsc286DQoNCg0KYGBge3IgZWNobyA9IFR9DQp1c2VySUQgPC0gICJNYXJpYSINCmdyZXBsKCJbMDEyMzQ1Njc4OV0qJCIsIHVzZXJJRCkNCmBgYA0KQmVjYXVzZSBpbiByZWd1bGFyIGV4cHJlc3Npb25zIGAqYCBtZWFuczogdGhlIHByZXZpb3VzIHJlcGVhdHMgKip6ZXJvKiogb3IgYXMgbWFueSB0aW1lcyENCg0KU28sIGArYCBhbmQgYCpgIGFyZSAqcXVhbnRpZmllcnNgIGluIHJlZ3VsYXIgZXhwcmVzc2lvbnMuIEFnYWluLCBjaGFyYWN0ZXIgY2xhc3NlczoNCg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZyA8LSAgIkFCQ0RFIg0KZ3JlcGwoIltZfEl8T10iLCBzdHJpbmcpDQpgYGANCldoYXQgaXMgYHxgPyBMZXQnIHNlZToNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmcgPC0gICJBQkNERSINCmdyZXBsKCJbWXxBfE9dIiwgc3RyaW5nKQ0KYGBgDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZyA8LSAgIkFCQ0RFIg0KZ3JlcGwoIltZfDl8T10iLCBzdHJpbmcpDQpgYGANCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5nIDwtICAiQUJDREUiDQpncmVwbCgiW0R8RXwwXSIsIHN0cmluZykNCmBgYA0KU28gYHxgIG1lYW5zOiAqKmxvZ2ljYWwgT1IqKiA6KQ0KDQpUaGUgZm9sbG93aW5nIG9uZTogYC5gIGlzIGRhbmdlcm91cy4gVGhlIGAuYCBtZWFuczogKipqdXN0IGFueXRoaW5nKio6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5nIDwtICAiQUJDREUiDQpncmVwbCgiLiIsIHN0cmluZykNCmBgYA0KQW5kIGl0IGlzLCBvZiBjb3Vyc2UsIGZvdW5kIGV2ZXJ5d2hlcmU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5nIDwtICAiQUJDREUiDQpncmVnZXhwcigiLiIsIHN0cmluZykNCmBgYA0KU28gd2hhdCBpZiB3ZSBuZWVkIHRvIHJlY29nbml6ZSBgLmAgbGl0ZXJhbGx5IGluIGEgc3RyaW5nPyBXZWxsLCB3ZSBoYXZlIHRvIGVzY2FwZSBpdDoNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmcgPC0gICJHb3Jhbi5TLk1pbG92YW5vdmljIg0KZ3JlZ2V4cHIoIlwuIiwgc3RyaW5nKQ0KYGBgDQpObywgbm8uLi4gVGhpcyBpcyB3aGF0IHdlIG5lZWQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5nIDwtICAiR29yYW4uUy5NaWxvdmFub3ZpYyINCmdyZWdleHByKCJcXC4iLCBzdHJpbmcpDQpgYGANCkFuZCB5ZXMgYC5gIGlzIGZvdW5kIGluIHRoZSBzaXh0aCBhbmQgZWlnaHRoIHBvc2l0aW9uIGluIGBHb3Jhbi5TLk1pbG92YW5vdmljYCENCg0KV2h5IHRoZSBkb3VibGUgYmFja3NsYXNoOiBgXFxgPyBCZWNhdXNlIHRoZSBiYWNrc2xhc2ggZXNjYXBlcyBpbiBSLCBidXQgaW4gcmVnZXggYWxzbywgb2YgY291cnNlLCBzbyB3ZSBuZWVkIHRvIGluZm9ybSBSIHRoYXQgd2hhdCBmb2xsb3dzIHRoZSBmaXJzdCBgXGAgbmVlZHMgdG8gYmUgaW50ZXJwcmV0ZWQgbm90IGFzIHNvbWUgc3BlY2lhbCBjaGFyYWN0ZXIgYnV0IGxpdGVyYWxseSBhcyB3aGF0IGl0IGlzLCBhbmQgdGhlbiB0aGUgc2Vjb25kIGBcYCBpbmZvcm1zIHRoZSByZWdleCBlbmdpbmUgdGhhdCB3aGF0IGZvbGxvd3MgaXQgLSBhbmQgdGhhdCB3b3VsZCBiZSBgLmAgLSBzaG91bGQgYmUgZXNjYXBlZCwgaS5lLiBub3QgaW50ZXJwcmV0ZWQgYXMgYGFueXRoaW5nYCAocmVnZXggc2VtYW50aWNzKSBidXQgYXMgdGhlIGAuYCBjaGFyYWN0ZXIgbGl0ZXJhbGx5LiANCg0KQ29tcGxpY2F0ZWQ/IFlvdSB3aWxsIGdldCB1c2VkIHRvIGl0LCBkbyBub3Qgd29ycnkuLi4gUmVtZW1iZXIgdGhhdCBEYXRhIFNjaWVuY2UgaXMgdGhlIHNleGllc3QgcHJvZmVzc2lvbiBvZiB0aGUgMjFzdCBjZW50dXJ5LiBXZWxsIGlzIGl0IG5vdD8gOikNCg0KTG9vazoNCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmcgPC0gICJHb3Jhbi5TLk1pbG92YW5vdmljMDUyOCRAMzY3NCINCmdyZXBsKCJbWzphbHBoYTpdXStcXC5bWzphbHBoYTpdXVxcLltbOmFscGhhOl1dKy4rIiwgc3RyaW5nKQ0KYGBgDQpgW1s6YWxwaGE6XV0rXFwuW1s6YWxwaGE6XV1cXC5bWzphbHBoYTpdXSsuK2AgbWVhbnM6IA0KDQotIGEgbGV0dGVyIChgW1s6YWxwaGE6XV1gKSwNCi0gb2NjdXJyaW5nIG9uY2Ugb3IgbW9yZSB0aGFuIG9uY2UgKGArYCksIGlzIGZvbGxvd2VkIGJ5IA0KLSBhIGRvdCAoYFxcLmApLCBmb2xsb3dlZCBieSANCi0gYSBzaW5nbGUgbGV0dGVyIChgW1s6YWxwaGE6XV1gKSwgZm9sbG93ZWQgYnkNCi0gYSBkb3QgKGBcXC5gKSwgZm9sbG93ZWQgYnkgDQotIGEgbGV0dGVyIChgW1s6YWxwaGE6XV1gKSwNCi0gb2NjdXJyaW5nIG9uY2Ugb3IgbW9yZSB0aGFuIG9uY2UgKGArYCksIGZvbGxvd2VkIGJ5DQotIGp1c3QgYW55dGhpbmcgcmVwZWF0ZWQgb25jZSBvciBtb3JlIHRoYW4gb25jZSAoYC4rYCkuDQoNCldoYXQgaXMgYFtbOmFscGhhOl1dYD8NCg0KIyMjIyAxLjMgUHJlZGVmaW5lZCBjaGFyYWN0ZXIgY2xhc3Nlcw0KDQpGcm9tIHRoZSBbUmVnZXg6IFJlZ3VsYXIgRXhwcmVzc2lvbnMgQXMgVXNlZCBJbiBSIGRvY3VtZW50YXRpb24gcGFnZV0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2Jhc2UvdmVyc2lvbnMvMy42LjIvdG9waWNzL3JlZ2V4KTogDQoNCj4gQ2VydGFpbiBuYW1lZCBjbGFzc2VzIG9mIGNoYXJhY3RlcnMgYXJlIHByZWRlZmluZWQuDQoNCmBgYCB7fQ0KWzphbG51bTpdDQpBbHBoYW51bWVyaWMgY2hhcmFjdGVyczogWzphbHBoYTpdIGFuZCBbOmRpZ2l0Ol0uDQoNCls6YWxwaGE6XQ0KQWxwaGFiZXRpYyBjaGFyYWN0ZXJzOiBbOmxvd2VyOl0gYW5kIFs6dXBwZXI6XS4NCg0KWzpibGFuazpdDQpCbGFuayBjaGFyYWN0ZXJzOiBzcGFjZSBhbmQgdGFiLCBhbmQgcG9zc2libHkgb3RoZXIgbG9jYWxlLWRlcGVuZGVudCBjaGFyYWN0ZXJzIHN1Y2ggYXMgbm9uLWJyZWFraW5nIHNwYWNlLg0KDQpbOmNudHJsOl0NCkNvbnRyb2wgY2hhcmFjdGVycy4gSW4gQVNDSUksIHRoZXNlIGNoYXJhY3RlcnMgaGF2ZSBvY3RhbCBjb2RlcyAwMDAgdGhyb3VnaCAwMzcsIGFuZCAxNzcgKERFTCkuIEluIGFub3RoZXIgY2hhcmFjdGVyIHNldCwgdGhlc2UgYXJlIHRoZSBlcXVpdmFsZW50IGNoYXJhY3RlcnMsIGlmIGFueS4NCg0KWzpkaWdpdDpdDQpEaWdpdHM6IDAgMSAyIDMgNCA1IDYgNyA4IDkuDQoNCls6Z3JhcGg6XQ0KR3JhcGhpY2FsIGNoYXJhY3RlcnM6IFs6YWxudW06XSBhbmQgWzpwdW5jdDpdLg0KDQpbOmxvd2VyOl0NCkxvd2VyLWNhc2UgbGV0dGVycyBpbiB0aGUgY3VycmVudCBsb2NhbGUuDQoNCls6cHJpbnQ6XQ0KUHJpbnRhYmxlIGNoYXJhY3RlcnM6IFs6YWxudW06XSwgWzpwdW5jdDpdIGFuZCBzcGFjZS4NCg0KWzpwdW5jdDpdDQpQdW5jdHVhdGlvbiBjaGFyYWN0ZXJzOiAhICIgIyAkICUgJiAnICggKSAqICsgLCAtIC4gLyA6IDsgPCA9ID4gPyBAIFsgXCBdIF4gXyBgIHsgfCB9IH4uDQoNCls6c3BhY2U6XQ0KU3BhY2UgY2hhcmFjdGVyczogdGFiLCBuZXdsaW5lLCB2ZXJ0aWNhbCB0YWIsIGZvcm0gZmVlZCwgY2FycmlhZ2UgcmV0dXJuLCBzcGFjZSBhbmQgcG9zc2libHkgb3RoZXIgbG9jYWxlLWRlcGVuZGVudCBjaGFyYWN0ZXJzLg0KDQpbOnVwcGVyOl0NClVwcGVyLWNhc2UgbGV0dGVycyBpbiB0aGUgY3VycmVudCBsb2NhbGUuDQoNCls6eGRpZ2l0Ol0NCkhleGFkZWNpbWFsIGRpZ2l0czogMCAxIDIgMyA0IDUgNiA3IDggOSBBIEIgQyBEIEUgRiBhIGIgYyBkIGUgZi4NCmBgYA0KDQoqKlEuKiogT2ssIGJ1dCB3aHkgdGhlIGRvdWJsZSBzcXVhcmUgYnJhY2tldHMsIGxpa2UgaW46IGBbWzphbHBoYTpdXWA/DQoNCioqQS4qKiBCZWNhdXNlIGBbYCBhbmQgYF1gIGluIGBbOmFscGhhOl1gIGFyZSBzaW1wbHkgYSBwYXJ0IG9mIHRoZSBwcmVkZWZpbmVkIG5hbWUsIGFuZCB3ZSBzdGlsbCB3YW50IHRvIGluZm9ybSB0aGUgcmVnZXggZW5naW5lIHRoYXQgd2UgbWVhbjogKmEgY2hhcmFjdGVyIGNsYXNzKi4NCg0KIyMjIyAxLjQgQmFja3JlZmVyZW5jZXMgYW5kIGBnc3ViKClgDQoNCkRpZCB3ZSBtZW50aW9uIGBnc3ViKClgIGluIHRoZSBwYXN0PyBJIHRoaW5rIHdlIGFscmVhZHkgZGlkOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZyA8LSAgIk5ldyBZb3JrIENpdHkiDQpnc3ViKCJOZXciLCAiT2xkIiwgc3RyaW5nKQ0KYGBgDQpPay4gTm93IHdoYXQgaWYgSSB3b3VsZCBsaWtlIHRvIGNoYW5nZSAqZWFjaCogb2NjdXJyZW5jZSBvZiBgI2VycmAgdG8gYW4gZW1wdHkgc3RyaW5nIGAiImAgaW4gdGhlIGZvbGxvd2luZzogDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5nIDwtICAic29tZURhdGFiYXMjZXJyZVJlY29yZEdvbmVXcm9uZyINCmdzdWIoIiNlcnIiLCAiIiwgc3RyaW5nKQ0KYGBgDQpHcmVhdC4gTm93IHdlIGtub3cgaG93IHRvIGRlbGV0ZSB0aGluZ3MgdGhhdCB3ZSBkbyBub3QgbmVlZCBpbiBzdHJpbmdzLiANCg0KTm93LCB0aGUgKmJhY2tyZWZlcmVuY2VzKiBpbiByZWdleCB3aXRoIGBnc3ViKClgLiBJbWFnaW5lIHRoYXQgd2UgYXJlIGZhY2luZyB0aGUgZm9sbG93aW5nIHNpdHVhdGlvbjogDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5ncyA8LSBjKCJOZXdZb3JrIiwgIk5ld0Ftc3RlcmRhbSIsICJOZXdCZWxncmFkZSIpDQpwcmludChzdHJpbmdzKQ0KYGBgDQpPYnZpb3VzbHksIGEgc2V0IG9mIHR5cG9zLCBhbGwgbWlzc2luZyBhIHdoaXRlIHNwYWNlLCB3aGljaCBjYW4gYmUgZml4ZWQgaW4gdGhlIGZvbGxvd2luZyB3YXk6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ3N1YigiKE5ldykiLCAiXFwxICIsIHN0cmluZ3MpDQpgYGANCmBcXDFgIGluIHRoaXMgZXhhbXBsZSBpcyBhICoqYmFja3JlZmVyZW5jZSoqIHdoaWNoIHJlZmVycyB0byB0aGUgKmZpcnN0KiBwYXJlbnRoZXNpemVkIGV4cHJlc3Npb24gaW4gdGhlIHBhdHRlcm4gYCIoTmV3KSJgOyBpdCB3aWxsIGJlIHJlcGxhY2VkIGJ5IGl0c2VsZiBjb25jYXRlbmF0ZWQgd2l0aCBhIHdoaXRlIHNwYWNlIC0gYCJcXDEgImAgaW4gYGdzdWIoKWAhIExldCdzIGVsYWJvcmF0ZTsgaW4NCg0KYGBgDQpnc3ViKHBhdHRlcm4sIHJlcGxhY2VtZW50LCB4KQ0KYGBgDQoNCndlIGhhdmUgdXNlZCBgIihOZXcpImAgYXMgYSBgcGF0dGVybmAsIGFuZCBgIlxcMSAiYCBhcyBhIGByZXBsYWNlbWVudGAsIHdoZXJlIGBzdHJpbmdzYCBwbGF5ZWQgYSByb2xlIG9mIGB4YDogdGhlIGByZXBsYWNlbWVudGAgdXNlZCBgXFwxYCBhcyBhIGJhY2tyZWZlcmVuY2UgdG8gYChOZXcpYCBpbiB0aGUgYHBhdHRlcm5gIGFyZ3VtZW50IGAiKE5ldykiYC4NCg0KDQoNCg0KIyMjIEZ1cnRoZXIgUmVhZGluZ3MNCg0KLSBPbmNlIGFnYWluOiBbR2FzdG9uIFNhbmNoZXoncyAiSGFuZGxpbmcgYW5kIFByb2Nlc3NpbmcgU3RyaW5ncyBpbiBSIl0oaHR0cDovL2dhc3RvbnNhbmNoZXouY29tL0hhbmRsaW5nX2FuZF9Qcm9jZXNzaW5nX1N0cmluZ3NfaW5fUi5wZGYpIC0gdGhlIGNoYW5jZXMgeW91IHdpbGwgZXZlciBuZWVkIG1vcmUgdGhhbiB3aGF0J3MgY292ZXJlZCBpbiB0aGlzIHRleHQtYm9vayBhcmUgc2xpbS4NCg0KLSAqKlJlZ3VsYXIgRXhwcmVzc2lvbnMqKjogZ28gcHJvLiBbUmVndWxhci1FeHByZXNzaW9ucy5pbmZvXShodHRwOi8vd3d3LnJlZ3VsYXItZXhwcmVzc2lvbnMuaW5mby8pIGlzIGEgd2VsbCBrbm93biBsZWFybmluZyByZXNvdXJjZS4gSW4gb3JkZXIgdG8gZmlndXJlIG91dCB0aGUgc3BlY2lmaWMgcmVnZXggc3RhbmRhcmQgdXNlZCBpbiBSOiBbUmVndWxhciBFeHByZXNzaW9ucyBhcyB1c2VkIGluIFJdKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9iYXNlL2h0bWwvcmVnZXguaHRtbCkuIFtUaGlzIHNlY3Rpb24gb2YgUmVndWxhci1FeHByZXNzaW9ucy5pbmZvXShodHRwOi8vd3d3LnJlZ3VsYXItZXhwcmVzc2lvbnMuaW5mby9ybGFuZ3VhZ2UuaHRtbCkgaXMgb24gcmVnZXggaW4gUiBzcGVjaWZpY2FsbHkuDQoNCg0KIyMjIFIgTWFya2Rvd24NCg0KW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIA0KDQoNCioqKg0KR29yYW4gUy4gTWlsb3Zhbm92acSHDQoNCkRhdGFLb2xla3RpdiwgMjAyMC8yMQ0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==