字符串函数一般都是向量化的,对输入的字符串向量的每个元素进行操作

字符串基础

readLines()

  1. 在R中读入一个文本文件可以用readLines()函数,它返回一个字符型向量,文件中每一行都是向量中对应的一个元素。这个文件可以是本地文件,也可以是一个网址。
index <- readLines("https://www.baidu.com") # 我的主页
#> Warning in readLines("https://www.baidu.com"): incomplete final line found on
#> 'https://www.baidu.com'
head(index) # HTML代码
#> [1] "<html>"                                                                
#> [2] "<head>"                                                                
#> [3] "\t<script>"                                                            
#> [4] "\t\tlocation.replace(location.href.replace(\"https://\",\"http://\"));"
#> [5] "\t</script>"                                                           
#> [6] "</head>"
  1. readline()支持从键盘直接输入一行文本,显然,R必须在交互式模式下运行,否则人没法输入任何东西。它有一个常用的地方,就是在代码运行过程中要求用户输入一些回答,例如:
x <- readline("Answer yes or no: ")

当R运行到这一行时会停下来等待用户输入(回车表示输入结束),然后根据输入的结果,代码可以接着执行。

  1. 在Windows系统中,readLines()读utf-8编码的txt文件会导致乱码,只能顺利读取ANSI编码的txt文件。

writeLines()

  1. 注意,字符串的打印形式与其本身的内容不是相同的,因为打印形式中会显示出转义字符。如果想要查看字符串的真正内容,可以使用writeLines()

writeLines(text, con = stdout(), sep = “”, useBytes = FALSE)

  1. Windows系统中writeLines()写入txt文件会导致乱码,替代方案是用writeBin(charToRaw(string), file)写入文件

字符串包含引文时引号的使用

若一个字符串包含引文,为了符合行文习惯(双引号),并使代码引号有层次的区别,外层应用单引号。

# string1 <- 'When putting a 'quote' inside a string, use single quotation marks.'
# string1 <- "When putting a "quote" inside a string, use double quotation marks."
string3 <- "When putting a 'quote' inside a string, use single quotation marks inside and double quotation marks outside."
string4 <- 'When putting a "quote" inside a string, use double quotation marks inside and single quotation marks outside.'

# 字符串的打印形式
string3
string4
#> [1] "When putting a 'quote' inside a string, use single quotation marks inside and double quotation marks outside."
#> [1] "When putting a \"quote\" inside a string, use double quotation marks inside and single quotation marks outside."
# 字符串编译后导出的形式
writeLines(string3)
writeLines(string4)
#> When putting a 'quote' inside a string, use single quotation marks inside and double quotation marks outside.
#> When putting a "quote" inside a string, use double quotation marks inside and single quotation marks outside.

string1和string2有语法错误,前后两个字符串中间夹了一个编译器不认识的quote。

string3和string4在语法上没问题,但编译时效果不同。string4才是我们想要的。

转义符号”\”

反斜杠”\“可以将R语言及R Markdown的语法部件转义为字符。
如果想要在字符串中包含一个单引号或双引号,可以使用”\’“和”\““对其进行转义。

string5 <- 'I don\'t know why they called us "Chinese".'
string6 <- "When putting a \"quote\" inside a string, you can use backslash as well."
writeLines(string5)
writeLines(string6)
#> I don't know why they called us "Chinese".
#> When putting a "quote" inside a string, you can use backslash as well.

如果想要在字符串中包含一个反斜杠,就需要使用两个反斜杠:“\\”

string7 <- "I think the backslash \"\\\" is amazing!"
print(string7) # 或 string7
writeLines(string7)
#> [1] "I think the backslash \"\\\" is amazing!"
#> I think the backslash "\" is amazing!

特殊字符

其他几种特殊字符。最常用的是换行符”\n”、制表符”\t”和退格”\b”,你可以使用 ?‘“’ 或 ?”’” 调出帮助文件来查看完整的特殊字符列表。

string8 <- 'The special characters "\n", "\t" and "\b" are very interesing.'
string9 <- "The special characters \"\\n\", \"\\t\" and \"\\b\" are very interesing."
writeLines(string8)
writeLines(string9)
#> The special characters "
#> ", " " and "" are very interesing.
#> The special characters "\n", "\t" and "\b" are very interesing.

生成的html文件中”\b”变成了一个奇怪的方框,控制台显示的则是正确的。可能R Markdown与R语言中”\b”的含义不同。

字符串格式化

base::sprintf()

sprintf(包含占位符的字符串,欲填充的字符串)

占位符
% 如果要在占位符内部使用%,需要写成%%
转换符 类型
d 十进制整数
o 八进制整数
x, X 小/大写十六进制整数
f 十进制浮点数
e, E 科学计数法表示的数,e小写或大写
g, G 智能选择使用浮点数还是科学计数法(g对应e,G对应E),默认显示6位有效数字
s 字符串
浮点数格式
width.precision 输出的最小宽度(不足以空格补齐).输出精度(对f和e/E意味着小数点后几位,对g/G意味着有效数字位数)
- 指定左对齐(默认右对齐),即宽度不足时从右边补充空格
+ 永远带符号(默认只有负数带一个负号,正数不带符号)
空格 首字符并非负号时,空一格
0 宽度不足时补0,而非默认的空格
sprintf("The ratio is %d%%", 10)
#> [1] "The ratio is 10%"
sprintf("%f", pi) # 浮点数
#> [1] "3.141593"
sprintf("%.3f", pi) # 保留小数点后3位
#> [1] "3.142"
sprintf("%.0f", pi) # 保留小数点后0位
#> [1] "3"
sprintf("%5.1f", pi) # 最小宽度为5,保留小数点后1位,不足补空格
#> [1] "  3.1"
sprintf("%05.1f", pi) # 用0补齐
#> [1] "003.1"
sprintf("%+f", pi) # 永远带符号
#> [1] "+3.141593"
sprintf("% f", pi)
#> [1] " 3.141593"
sprintf("%-10f", pi) # 左对齐,宽度为10
#> [1] "3.141593  "
sprintf("%e", pi)
#> [1] "3.141593e+00"
sprintf("%E", pi)
#> [1] "3.141593E+00"
sprintf("%g", pi)
#> [1] "3.14159"
sprintf("%g", 1e6 * pi)
#> [1] "3.14159e+06"
sprintf("%.9g", 1e6 * pi)
#> [1] "3141592.65"
sprintf("%.2g", 23.75)
#> [1] "24"
sprintf(
  "min 10-char string '%10s'",
  c("a", "ABC", "and an even longer one")
)
#> [1] "min 10-char string '         a'"            
#> [2] "min 10-char string '       ABC'"            
#> [3] "min 10-char string 'and an even longer one'"

借助 python

加入 python 代码块,使用 python 中的字符串格式化方法 .format()

模板字符串.format(以逗号分隔的填充参数序列)

其中,{}为模板中的槽,槽内部定义每个槽的格式化样式,形如:

id:[fill][align][sign][width][,][.precision][type]

类别 含义
fill 宽度不足时填充的字符
align 对齐方式. < 左对齐;> 右对齐;= 内容右对齐,符号放置在填充字符(如”0”)的左侧;^ 居中
sign

+ 正数加+,负数加-

空格,正数加空格,负数加-

, 为数字添加千位分隔符
width 输出的最小宽度(不足以 fill 补齐)
.precision 小数点后保留精度
type 格式化类型
s 字符串(不指定type时默认)
d 十进制整数
o 八进制整数
x, X 小/大写十六进制整数
f 十进制浮点数
e, E 科学计数法表示的数,e小写或大写
g, G 智能选择使用浮点数还是科学计数法(g对应e,G对应E),默认显示6位有效数字
% 百分比,默认显示小数点后6位
b 二进制整数
c 10进制整数转换为 Unicode 字符

这种方法的缺点在于,输出宽度、精度等参数必须是确定的常数,而不能是变量。因此当这些数值必须取变量时,仍要使用.rjust().ljust().center()方法。

示例:

str1 = "{0:=^20}".format("Python") 
str1 # 用 '=' 填充,居中对齐,槽宽度为20
#> '=======Python======='
str2 = "{:=^20}".format("Python") 
str2 # 参数索引0可以省略
#> '=======Python======='
str3 = "{:*>20}".format("BIT") 
str3 # 用 '*' 填充,右对齐,槽宽度为20
#> '*****************BIT'
str4 = "{:10}".format("BIT")
str4 # 默认左对齐,空格填充
#> 'BIT       '
str5 = "{:,.2f}".format(12345.6789)
str5 # 使用千位分隔符,保留两位小数的浮点型
#> '12,345.68'
str6 = "{0:b}, {0:c}, {0:d}, {0:o}, {0:x}, {0:X}".format(426)
str6 # 一个参数填到所有槽中,分别转换为:二进制,Unicode 编码,十进制,八进制,十六进制,大写的十六进制
#> '110101010, ƪ, 426, 652, 1aa, 1AA'
str7 = "{0:.2e}, {0:.2E}, {0:.2f}, {0:.2%}".format(3.14)
str7 # 分别转换为:科学记数法,大写科学记数法,浮点数,百分数
#> '3.14e+00, 3.14E+00, 3.14, 314.00%'
str8  = "I am {:s}, age {:d}, money {:.2f}".format("seven", 18, 88888.1)
str8 # 槽与填充参数的顺序恰好对应时,也可以省略参数索引
#> 'I am seven, age 18, money 88888.10'

formattable 包

  • percent()
  • comma()
  • currency()
  • accounting(),会计格式,如负数用括号表示
  • scientific()
library(formattable)

df <-
  data.frame(
    id = 1:10,
    name = c(
      "Bob",
      "Ashley",
      "James",
      "David",
      "Jenny",
      "Hans",
      "Leo",
      "John",
      "Emily",
      "Lee"
    ),
    age = c(28, 27, 30, 28, 29, 29, 27, 27, 31, 30),
    grade = c("C", "A", "A", "C", "B", "B", "B", "A", "C", "C"),
    test1_score = c(8.9, 9.5, 9.6, 8.9, 9.1, 9.3, 9.3, 9.9, 8.5, 8.6),
    test2_score = c(9.1, 9.1, 9.2, 9.1, 8.9, 8.5, 9.2, 9.3, 9.1, 8.8),
    final_score = c(9, 9.3, 9.4, 9, 9, 8.9, 9.25, 9.6, 8.8, 8.7),
    registered = c(TRUE, FALSE, TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, FALSE, FALSE),
    stringsAsFactors = FALSE
  )
df
#>    id   name age grade test1_score test2_score final_score registered
#> 1   1    Bob  28     C         8.9         9.1        9.00       TRUE
#> 2   2 Ashley  27     A         9.5         9.1        9.30      FALSE
#> 3   3  James  30     A         9.6         9.2        9.40       TRUE
#> 4   4  David  28     C         8.9         9.1        9.00      FALSE
#> 5   5  Jenny  29     B         9.1         8.9        9.00       TRUE
#> 6   6   Hans  29     B         9.3         8.5        8.90       TRUE
#> 7   7    Leo  27     B         9.3         9.2        9.25       TRUE
#> 8   8   John  27     A         9.9         9.3        9.60      FALSE
#> 9   9  Emily  31     C         8.5         9.1        8.80      FALSE
#> 10 10    Lee  30     C         8.6         8.8        8.70      FALSE
formattable(
  df,
  list(
    age = color_tile("white", "orange"),
    grade = formatter("span", style = x ~ ifelse(
      x == "A", style(color = "green", font.weight = "bold"), NA
    )),
    area(col = c(test1_score, test2_score)) ~ normalize_bar("pink", 0.2),
    final_score = formatter(
      "span",
      style = x ~ style(color = ifelse(rank(-x) <= 3, "green", "gray")),
      x ~ sprintf("%.2f (rank: %02d)", x, rank(-x))
    ),
    registered = formatter(
      "span",
      style = x ~ style(color = ifelse(x, "green", "red")),
      x ~ icontext(ifelse(x, "ok", "remove"), ifelse(x, "Yes", "No"))
    )
  )
)
id name age grade test1_score test2_score final_score registered
1 Bob 28 C 8.9 9.1 9.00 (rank: 06) Yes
2 Ashley 27 A 9.5 9.1 9.30 (rank: 03) No
3 James 30 A 9.6 9.2 9.40 (rank: 02) Yes
4 David 28 C 8.9 9.1 9.00 (rank: 06) No
5 Jenny 29 B 9.1 8.9 9.00 (rank: 06) Yes
6 Hans 29 B 9.3 8.5 8.90 (rank: 08) Yes
7 Leo 27 B 9.3 9.2 9.25 (rank: 04) Yes
8 John 27 A 9.9 9.3 9.60 (rank: 01) No
9 Emily 31 C 8.5 9.1 8.80 (rank: 09) No
10 Lee 30 C 8.6 8.8 8.70 (rank: 10) No

文字格式自定义文字背景自定义文本自定义三种自定义可视化类型:

  • color_tile函数用于输出按照数值量级进行颜色背景填充的列。
  • formatter函数提供字体显示格式的自定义,grade列自定义了值为A的记录显示绿色,并将字体加粗,否则忽略。
  • test1_score, test2_score两列通过area函数在对应字体背景位置使用条形图来代表指标量级大小,颜色填充粉色。
  • final_score列对指标按照top3显示绿色,其余显示灰色,同时将内容显示格式自定义为浮点型+(rank:名次)进行显示。
  • registered列则在对填充颜色按照对应布尔值进行显示(TRUE显示绿色、FALSE显示红色)之外,在左侧添加了对用的icon文本(TRUE显示绿色对号,FALSE显示红色叉号)。

函数分类

字符串拼接函数

  • str_c: 字符串拼接。
  • str_trim: 去掉字符串的白空格
  • str_pad: 补充字符串的长度
  • str_squish:删除字符串中多于1个的空格。非常有用的函数!
  • str_dup: 复制字符串
  • str_sub: 字符串切片
  • str_sub<- 字符串切片,并赋值,同str_sub

字符串计算函数

  • str_count: 字符串计数
  • str_length(): 字符串长度,同nchar()
  • str_sort: 字符串值排序
  • str_order: 字符串索引排序,规则同str_sort

字符串匹配函数

  • str_view()/str_view_all() 查看string是否匹配pattern,如果匹配,就高亮显示
  • str_split: 字符串分割
  • str_split_fixed: 字符串分割,同str_split
  • str_subset: 返回匹配的字符串
  • word: 从文本中提取单词
  • str_detect()/str_view(): 检查匹配字符串的字符
  • str_match: 从字符串中提取匹配组。
  • str_match_all: 从字符串中提取匹配组,同str_match
  • chartr(old, new, string): 从 old 到 new 的一一对应的替换,作用在 string 上
  • str_replace: 字符串替换
  • str_replace_all: 字符串替换,同str_replace
  • str_replace_na: 把NA替换为字符串”NA”
  • str_remove:删除匹配的字符串
  • str_remove_all:删除所有匹配的字符
  • str_locate: 找到匹配的字符串的位置。
  • str_locate_all: 找到匹配的字符串的位置,同str_locate
  • str_extract: 从字符串中提取匹配字符
  • str_extract_all: 从字符串中提取匹配字符,同str_extract

字符串格式化

  • base::sprintf(),详见上一节
  • str_glue():参数里面的{}占位符,同javascript的${}
  • str_wrap: 控制长字符串输出排版
  • str_conv: 字符编码转换
  • str_to_upper()/toupper(): 字符串转成大写
  • str_to_lower()/tolower(): 字符串转成小写,规则同str_to_upper
  • str_to_title(): 字符串转成首字母大写,规则同str_to_upper
  • str_to_sentence(): 只有一句的首字母大写

参数控制函数,仅用于构造功能的参数,不能独立使用。

  • boundary: 定义使用边界
  • coll: 定义字符串标准排序规则。
  • fixed: 定义用于匹配的字符,包括正则表达式中的转义符
  • regex: 定义正则表达式

stringr::

stringr cheatsheet.pdf

str_length() 字符串长度

base::nchar()

使用 RStudio,那么通用前缀 str_ 会特别有用,因为输入 str_ 后会触发自动完成功能,你可以看到所有的字符串函数:

str_length(c("a", "R for data science"))
#> [1]  1 18
str_length(c("a", "R for data science")) %>% sum()
#> [1] 19

str_c()字符串组合

str_c(..., sep = "", collapse = NULL)

合并若干个字符串或字符向量的对应元素,默认无间隔符

# 向量化操作,自动循环短向量
str_c("prefix-", c("a", "b", "c"), "-suffix")
#> [1] "prefix-a-suffix" "prefix-b-suffix" "prefix-c-suffix"
# 合并一个字符向量的各元素为一个长字符串,使用参数collapse,将向量“塌缩”为一个字符串
str_c(c("x", "y", "z"), collapse = "+")
#> [1] "x+y+z"

str_replace_na(string, replacement = "NA"),默认将 na 转化为字符串”NA”

x <- c("abc", NA)
str_c("|-", x, "-|")
#> [1] "|-abc-|" NA
str_c("|-", str_replace_na(x), "-|")
#> [1] "|-abc-|" "|-NA-|"

str_trim() 去掉字符串首尾的空格和制表符(\t)

str_trim(string, side = c(“both”, “left”, “right”))

side: 过滤方式,both两边都过滤,left左边过滤,right右边过滤

str_pad() 补充字符串的长度

str_pad(string, width, side = c(“left”, “right”, “both”), pad = ” “)

width: 字符串填充后的长度
side: 填充方向,both两边都填充,left左边填充,right右边填充
pad: 用于填充的字符

str_dup() 复制字符串

str_dup(string, times)

times: 复制次数

val <- c("abca4", 123, "cba2")

# 复制2次
str_dup(val, 2)
#> [1] "abca4abca4" "123123"     "cba2cba2"
# 按位置复制
str_dup(val, 1:3)
#> [1] "abca4"        "123123"       "cba2cba2cba2"

str_wrap() 控制长字符串输出排版

str_wrap(string, width = 80, indent = 0, exdent = 0)

width: 设置一行所占的宽度。
indent: 段落首行的缩进值
exdent: 段落非首行的缩进值

# 这段代码可以在 console 中运行,但不能在 Rmarkdown 中直接运行
str <- "R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。"


# 设置宽度为40个字符
# cat(str_wrap(str, width = 40), "\n")

# 设置宽度为60字符,首行缩进4字符
# cat(str_wrap(txt, width = 60, indent = 4), "\n")

# 设置宽度为10字符,非首行缩进4字符
# cat(str_wrap(txt, width = 10, exdent = 4), "\n")

str_split() 拆分字符串

str_split(string, pattern, n = Inf) str_split_fixed(string, pattern, n)

pattern: 匹配的字符。
n: 分割个数

val <- "abc,123,234,iuuu"

# 以,进行分割
s1 <- str_split(val, ",")
s1
#> [[1]]
#> [1] "abc"  "123"  "234"  "iuuu"
# 以,进行分割,且只保留2块
s2 <- str_split(val, ",", 2)
s2
#> [[1]]
#> [1] "abc"          "123,234,iuuu"
# 查看str_split()函数操作的结果类型list
class(s1)
#> [1] "list"
# 用str_split_fixed()函数分割,结果类型是matrix
s3 <- str_split_fixed(val, ",", 2)
s3
#>      [,1]  [,2]          
#> [1,] "abc" "123,234,iuuu"
class(s3)
#> [1] "matrix" "array"
fruits <- c(
  "apples and oranges and pears and bananas",
  "pineapples and mangos and guavas"
)
str_split(fruits, " and ")
#> [[1]]
#> [1] "apples"  "oranges" "pears"   "bananas"
#> 
#> [[2]]
#> [1] "pineapples" "mangos"     "guavas"
gpl <- readLines(file.path(R.home(), "COPYING"))
words <- unlist(strsplit(gpl, "\\W")) # \\W 意为非单词字符
words <- words[words != ""] # 去掉空字符
tail(sort(table(tolower(words))), 10) # 频数最大的10个单词
#> 
#>    this      is       a program     and     you      or      of      to     the 
#>      49      53      57      71      72      76      77     104     108     194

str_sub() 字符串取子集

str_sub(string, start = 1L, end = -1L)

str_sub(string, start = 1L, end = -1L) <- value

两个参数分别为始末位置,符号代表从后往前数第多少个字符

txt <- "I am Conan."

# 分2段截取字符串
str_sub(txt, c(1, 4), c(6, 8))
#> [1] "I am C" "m Con"
# 通过负坐标截取字符串
str_sub(txt, -3)
#> [1] "an."
str_sub(txt, end = -3)
#> [1] "I am Cona"
# 赋值
x <- c("Apple", "Banana", "Pear")
# str_sub()对x向量化操作;str_to_lower全部转换为小写字母
str_sub(x, 1, 1) <- str_to_lower(str_sub(x, 1, 1))
x
#> [1] "apple"  "banana" "pear"

str_to_*() 大小写函数族

str_to_upper(string, locale = "") 转换为全大写字母

str_to_lower(string, locale = "") 转换为全小写字母

str_to_title(string, locale = "") 单词首字母大写

str_to_sentence() 全句首字母大写

locale:按哪种语言习惯排序

dog <- "Look the quick brown dog!"
str_to_upper(dog)
#> [1] "LOOK THE QUICK BROWN DOG!"
str_to_lower(dog)
#> [1] "look the quick brown dog!"
str_to_title(dog)
#> [1] "Look The Quick Brown Dog!"
str_to_sentence("look the quick brown dog!")
#> [1] "Look the quick brown dog!"

str_count() 字符串计数

str_count(string, pattern = ““)

# 注意正则表达式的特殊字符
str_count(c("a.", ".", ".a.", NA), ".")
#> [1]  2  1  3 NA
# 用fixed匹配字符
str_count(c("a.", ".", ".a.", NA), fixed("."))
#> [1]  1  1  2 NA
# 用\\匹配字符
str_count(c("a.", ".", ".a.", NA), "\\.")
#> [1]  1  1  2 NA

str_sort() 字符串排序

str_sort(x, decreasing = FALSE, na_last = TRUE, locale = ““, …) str_order(x, decreasing = FALSE, na_last = TRUE, locale =”“, …)

x: 字符串,字符串向量。 decreasing: 排序方向。 na_last: NA值的存放位置,一共3个值,TRUE放到最后,FALSE放到最前,NA过滤处理 locale: 按哪种语言习惯排序

# 按ASCII字母排序
str_sort(c("a", 1, 2, "11"), locale = "en")
#> [1] "1"  "11" "2"  "a"
# 倒序排序
str_sort(letters, decreasing = TRUE)
#>  [1] "z" "y" "x" "w" "v" "u" "t" "s" "r" "q" "p" "o" "n" "m" "l" "k" "j" "i" "h"
#> [20] "g" "f" "e" "d" "c" "b" "a"
# 按拼音排序
str_sort(c("你", "好", "粉", "丝", "日", "志"), locale = "zh")
#> [1] "粉" "好" "你" "日" "丝" "志"

对NA的排序处理:

# 把NA放最后面
str_sort(c(NA, "1", NA), na_last = TRUE)
#> [1] "1" NA  NA
# 把NA放最前面
str_sort(c(NA, "1", NA), na_last = FALSE)
#> [1] NA  NA  "1"
# 去掉NA值
str_sort(c(NA, "1", NA), na_last = NA)
#> [1] "1"

str_detect() 检测是否匹配,返回T/F

val <- c("abca4", 123, "cba2")

# 检查字符串向量,是否包括a
str_detect(val, "a")
#> [1]  TRUE FALSE  TRUE
# 检查字符串向量,是否以a为开头
str_detect(val, "^a")
#> [1]  TRUE FALSE FALSE
# 检查字符串向量,是否以a为结尾
str_detect(val, "a$")
#> [1] FALSE FALSE FALSE

str_subset() 返回成功匹配的字符串

val <- c("abc", 123, "cba")

# 全文匹配
str_subset(val, "a")
#> [1] "abc" "cba"
# 开头匹配
str_subset(val, "^a")
#> [1] "abc"
# 结尾匹配
str_subset(val, "a$")
#> [1] "cba"

str_match()/str_extract() 从字符串中提取匹配模式

主要与正则表达式配合使用

str_match(string, pattern) str_match_all(string, pattern)

以字符串matrix格式返回

val <- c("abc", 123, "cba")

# 匹配字符a,并返回对应的字符
str_match(val, "a")
#>      [,1]
#> [1,] "a" 
#> [2,] NA  
#> [3,] "a"
# 匹配字符0-9,限1个,并返回对应的字符
str_match(val, "[0-9]")
#>      [,1]
#> [1,] NA  
#> [2,] "1" 
#> [3,] NA
# 匹配字符0-9,不限数量,并返回对应的字符
str_match(val, "[0-9]*")
#>      [,1] 
#> [1,] ""   
#> [2,] "123"
#> [3,] ""
# 返回matrix
str_match_all(val, "a")
#> [[1]]
#>      [,1]
#> [1,] "a" 
#> 
#> [[2]]
#>      [,1]
#> 
#> [[3]]
#>      [,1]
#> [1,] "a"
str_match_all(val, "[0-9]")
#> [[1]]
#>      [,1]
#> 
#> [[2]]
#>      [,1]
#> [1,] "1" 
#> [2,] "2" 
#> [3,] "3" 
#> 
#> [[3]]
#>      [,1]

str_extract(string, pattern) str_extract_all(string, pattern, simplify = FALSE)

simplify: TRUE返回matrix,FALSE返回列表

val <- c("abca4", 123, "cba2")

# 返回匹配的数字
str_extract(val, "\\d")
#> [1] "4" "1" "2"
# 返回匹配的字符
str_extract(val, "[a-z]+")
#> [1] "abca" NA     "cba"
val <- c("abca4", 123, "cba2")
str_extract_all(val, "\\d")
#> [[1]]
#> [1] "4"
#> 
#> [[2]]
#> [1] "1" "2" "3"
#> 
#> [[3]]
#> [1] "2"
str_extract_all(val, "[a-z]+")
#> [[1]]
#> [1] "abca"
#> 
#> [[2]]
#> character(0)
#> 
#> [[3]]
#> [1] "cba"

word() 从文本中提取单词(以单词为单位看待字符串)

word(string, start = 1L, end = start, sep = fixed(” “))

start: 从第几个词开始。 end: 到第几个词结束。 sep: 分词间隔字符,默认为空格。

val <- c("I am Conan.", "http://fens.me, ok")

# 默认以空格分割,取第一个位置的字符串
word(val, 1)
#> [1] "I"               "http://fens.me,"
word(val, -1)
#> [1] "Conan." "ok"
word(val, 2, -1)
#> [1] "am Conan." "ok"
# 以,分割,取第一个位置的字符串
val <- "111,222,333,444"
word(val, 1, sep = fixed(","))
#> [1] "111"
word(val, 3, sep = fixed(","))
#> [1] "333"

str_replace() 字符串替换

str_replace(string, pattern, replacement)
str_replace_all(string, pattern, replacement)
str_replace_na(string, replacement = “NA”), 将NA(默认)替换为’NA’

参数 replacement 可以是一个函数,使替换的规则更加复杂

val <- c("abc", 123, "cba")

# 把目标字符串第一个出现的a或b,替换为-
str_replace(val, "[ab]", "-")
#> [1] "-bc" "123" "c-a"
# 把目标字符串所有出现的a或b,替换为-
str_replace_all(val, "[ab]", "-")
#> [1] "--c" "123" "c--"
# 把目标字符串所有出现的a,替换为被转义的字符
str_replace_all(val, "[a]", "\\")
#> [1] "bc"  "123" "cb"
pigIt <- function(string) {
  str_replace_all(string, "\\w+", function(word) {
    str_c(str_sub(word, 2), str_sub(word, 1, 1), "ay")
  })
}

library(testthat)
#> 
#> Attaching package: 'testthat'
#> The following objects are masked from 'package:magrittr':
#> 
#>     equals, is_less_than, not
#> The following object is masked from 'package:dplyr':
#> 
#>     matches
#> The following object is masked from 'package:purrr':
#> 
#>     is_null
#> The following objects are masked from 'package:readr':
#> 
#>     edition_get, local_edition
#> The following object is masked from 'package:tidyr':
#> 
#>     matches
test_that("Sample Tests", {
  expect_equal(pigIt("Pig latin is cool"), "igPay atinlay siay oolcay")
  expect_equal(pigIt("Hello world !"), "elloHay orldway !")
})
#> Test passed 😀

str_locate() 找到模式在字符串中的始末位置

str_locate(string, pattern) str_locate_all(string, pattern)

val <- c("abca", 123, "cba")

# 匹配a在字符串中的位置
str_locate(val, "a")
#>      start end
#> [1,]     1   1
#> [2,]    NA  NA
#> [3,]     3   3
# 用向量匹配
str_locate(val, c("a", 12, "b"))
#>      start end
#> [1,]     1   1
#> [2,]     1   2
#> [3,]     2   2
# 以字符串matrix格式返回
str_locate_all(val, "a")
#> [[1]]
#>      start end
#> [1,]     1   1
#> [2,]     4   4
#> 
#> [[2]]
#>      start end
#> 
#> [[3]]
#>      start end
#> [1,]     3   3
# 匹配a或b字符,以字符串matrix格式返回
str_locate_all(val, "[ab]")
#> [[1]]
#>      start end
#> [1,]     1   1
#> [2,]     2   2
#> [3,]     4   4
#> 
#> [[2]]
#>      start end
#> 
#> [[3]]
#>      start end
#> [1,]     2   2
#> [2,]     3   3

str_conv 字符编码转换

str_conv(string, encoding)

# 把中文字符字节化
x <- charToRaw("你好")
x
#> [1] e4 bd a0 e5 a5 bd
# 默认win系统字符集为GBK,GB2312为GBK字集,转码正常
str_conv(x, "GBK")
#> [1] "浣犲ソ"
str_conv(x, "GB2312")
#> Warning in stri_conv(string, encoding, "UTF-8"): input data \xffffffa0 in the
#> current source encoding could not be converted to Unicode
#> Warning in stri_conv(string, encoding, "UTF-8"): input data \xffffffbd in the
#> current source encoding could not be converted to Unicode
#> [1] "浣\032濂\032"
# 转UTF-8失败
str_conv(x, "UTF-8")
#> [1] "你好"
# 把unicode转UTF-8
str_conv("\u5317\u4eac", "UTF-8")
#> [1] "北京"

str_glue() 模板字符串

name <- "Fred"
age <- 50
anniversary <- as.Date("1991-10-12")
str_glue(
  "My name is {name}, ",
  "my age next year is {age + 1}, ",
  "and my anniversary is {format(anniversary, '%A, %B %d, %Y')}."
)
#> My name is Fred, my age next year is 51, and my anniversary is 星期六, 十月 12, 1991.
str_glue("My name is {name}, not {{name}}.")
#> My name is Fred, not {name}.

base::

chartr() 字符替换

chartr(old, new, x),非常好用的函数,相当于按一个哈希表进行字符转换

chartr(old = "a", new = "c", x = "a123")
#> [1] "c123"
# 注意,old和new参数可以都是字符串,index 对应的字符进行任意次数的替换
chartr("Tt", "Uu", "AtGCtttACC")
#> [1] "AuGCuuuACC"

字符串分割

strsplit(x, split, fixed = FALSE)

# fixed=F, 第2个参数为正则表达式。'.'可匹配除“\n”和"\r"之外的任何单个字符。
splited <- unlist(strsplit("a.b.c", "."))
splited
#> [1] "" "" "" "" ""
# fixed=T,第2个参数为文本字符串
splited <- unlist(strsplit("a.b.c", ".", fixed = T))
splited
#> [1] "a" "b" "c"

字符串匹配

字符串匹配函数 grep(pattern, x, fixed=FALSE)和grepl()

cstr <- c("b", "A", "c", "a.a")
# fixed=T,第1个参数为文本字符串
grep(".", cstr, fixed = TRUE)
#> [1] 4
# fixed=F,第1个参数为正则表达式
grep(".", cstr, fixed = F)
#> [1] 1 2 3 4
# grep仅返回匹配项的下标;而grepl返回逻辑向量表示有没有找到匹配
grepl(".", cstr, fixed = TRUE)
#> [1] FALSE FALSE FALSE  TRUE
# 用于提取子集是一样的
cstr[grep(".", cstr, fixed = TRUE)]
#> [1] "a.a"
cstr[grepl(".", cstr, fixed = TRUE)]
#> [1] "a.a"

字符串替换

字符串替换函数 sub(pattern, replacement, x, fixed=FALSE)

# fixed=F, 第1个参数为正则表达式,'\\s'表示空白
sub("\\s", ".", "Hello There")
#> [1] "Hello.There"
# fixed=T, 第1个参数为文本字符串,在'Hello There'中找不到'\\s'
sub("\\s", ".", "Hello There", fixed = T)
#> [1] "Hello There"
# sub()与gsub()的区别,替换一次和替换任意次(只要匹配成功)
sub("e", "E", "Hello There", fixed = T)
#> [1] "HEllo There"
gsub("e", "E", "Hello There", fixed = T)
#> [1] "HEllo ThErE"
LS0tDQp0aXRsZTogIlN0cmluZyINCnN1YnRpdGxlOiAnJw0KYXV0aG9yOiAiSHVtb29uIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY3NzOiBbIi4uL2Nzcy9zdHlsZS5jc3MiXQ0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICB0aGVtZTogdW5pdGVkDQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB5ZXMNCiAgICAgIHNtb290aF9zY3JvbGw6IHllcw0KZG9jdW1lbnRjbGFzczogY3RleGFydA0KY2xhc3NvcHRpb246IGh5cGVycmVmLA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9DQpzb3VyY2UoIi4uL1JtYXJrZG93bi10ZW1wbGF0ZS9SbWFya2Rvd25fY29uZmlnLlIiKQ0KDQojIyBnbG9iYWwgb3B0aW9ucyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICB3aWR0aCA9IGNvbmZpZyR3aWR0aCwNCiAgZmlnLndpZHRoID0gY29uZmlnJGZpZy53aWR0aCwNCiAgZmlnLmFzcCA9IGNvbmZpZyRmaWcuYXNwLA0KICBvdXQud2lkdGggPSBjb25maWckb3V0LndpZHRoLA0KICBmaWcuYWxpZ24gPSBjb25maWckZmlnLmFsaWduLA0KICBmaWcucGF0aCA9IGNvbmZpZyRmaWcucGF0aCwNCiAgZmlnLnNob3cgPSBjb25maWckZmlnLnNob3csDQogIHdhcm4gPSBjb25maWckd2FybiwNCiAgd2FybmluZyA9IGNvbmZpZyR3YXJuaW5nLA0KICBtZXNzYWdlID0gY29uZmlnJG1lc3NhZ2UsDQogIGVjaG8gPSBjb25maWckZWNobywgDQogIGV2YWwgPSBjb25maWckZXZhbCwgDQogIHRpZHkgPSBjb25maWckdGlkeSwgDQogIGNvbW1lbnQgPSBjb25maWckY29tbWVudCwgDQogIGNvbGxhcHNlID0gY29uZmlnJGNvbGxhcHNlLCANCiAgY2FjaGUgPSBjb25maWckY2FjaGUsDQogIGNhY2hlLmNvbW1lbnRzID0gY29uZmlnJGNhY2hlLmNvbW1lbnRzLA0KICBhdXRvZGVwID0gY29uZmlnJGF1dG9kZXANCikNCg0KIyMgdXNlIG5lY2Vzc2FyeSBwYWNrYWdlcyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkYXRhLnRhYmxlKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShodG1sd2lkZ2V0cykNCmBgYA0KDQoqKuWtl+espuS4suWHveaVsOS4gOiIrOmDveaYr+WQkemHj+WMlueahO+8jOWvuei+k+WFpeeahOWtl+espuS4suWQkemHj+eahOavj+S4quWFg+e0oOi/m+ihjOaTjeS9nCoq44CCDQoNCiMjIOWtl+espuS4suWfuuehgA0KDQojIyMgcmVhZExpbmVzKCkNCg0KMS4gIOWcqFLkuK3or7vlhaXkuIDkuKrmlofmnKzmlofku7blj6/ku6XnlKhgcmVhZExpbmVzKClg5Ye95pWw77yM5a6D6L+U5Zue5LiA5Liq5a2X56ym5Z6L5ZCR6YeP77yM5paH5Lu25Lit5q+P5LiA6KGM6YO95piv5ZCR6YeP5Lit5a+55bqU55qE5LiA5Liq5YWD57Sg44CC6L+Z5Liq5paH5Lu25Y+v5Lul5piv5pys5Zyw5paH5Lu277yM5Lmf5Y+v5Lul5piv5LiA5Liq572R5Z2A44CCDQoNCmBgYHtyfQ0KaW5kZXggPC0gcmVhZExpbmVzKCJodHRwczovL3d3dy5iYWlkdS5jb20iKSAjIOaIkeeahOS4u+mhtQ0KaGVhZChpbmRleCkgIyBIVE1M5Luj56CBDQpgYGANCg0KMi4gIGByZWFkbGluZSgpYOaUr+aMgeS7jumUruebmOebtOaOpei+k+WFpeS4gOihjOaWh+acrO+8jOaYvueEtu+8jFLlv4XpobvlnKjkuqTkupLlvI/mqKHlvI/kuIvov5DooYzvvIzlkKbliJnkurrmsqHms5XovpPlhaXku7vkvZXkuJzopb/jgILlroPmnInkuIDkuKrluLjnlKjnmoTlnLDmlrnvvIzlsLHmmK/lnKjku6PnoIHov5DooYzov4fnqIvkuK3opoHmsYLnlKjmiLfovpPlhaXkuIDkupvlm57nrZTvvIzkvovlpoLvvJoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQp4IDwtIHJlYWRsaW5lKCJBbnN3ZXIgeWVzIG9yIG5vOiAiKQ0KYGBgDQoNCuW9k1Lov5DooYzliLDov5nkuIDooYzml7bkvJrlgZzkuIvmnaXnrYnlvoXnlKjmiLfovpPlhaXvvIjlm57ovabooajnpLrovpPlhaXnu5PmnZ/vvInvvIznhLblkI7moLnmja7ovpPlhaXnmoTnu5PmnpzvvIzku6PnoIHlj6/ku6XmjqXnnYDmiafooYzjgIINCg0KMy4gIOWcqFdpbmRvd3Pns7vnu5/kuK3vvIxgcmVhZExpbmVzKClg6K+7dXRmLTjnvJbnoIHnmoR0eHTmlofku7bkvJrlr7zoh7TkubHnoIHvvIzlj6rog73pobrliKnor7vlj5ZBTlNJ57yW56CB55qEdHh05paH5Lu244CCDQoNCiMjIyB3cml0ZUxpbmVzKCkNCg0KMS4gIOazqOaEj++8jOWtl+espuS4sueahOaJk+WNsOW9ouW8j+S4juWFtuacrOi6q+eahOWGheWuueS4jeaYr+ebuOWQjOeahO+8jOWboOS4uuaJk+WNsOW9ouW8j+S4reS8muaYvuekuuWHuui9rOS5ieWtl+espuOAgioq5aaC5p6c5oOz6KaB5p+l55yL5a2X56ym5Liy55qE55yf5q2j5YaF5a6577yM5Y+v5Lul5L2/55SoYHdyaXRlTGluZXMoKWAqKg0KDQo+IHdyaXRlTGluZXModGV4dCwgY29uID0gc3Rkb3V0KCksIHNlcCA9ICJcbiIsIHVzZUJ5dGVzID0gRkFMU0UpDQoNCjIuICBXaW5kb3dz57O757uf5LitYHdyaXRlTGluZXMoKWDlhpnlhaV0eHTmlofku7bkvJrlr7zoh7TkubHnoIHvvIzmm7/ku6PmlrnmoYjmmK/nlKhgd3JpdGVCaW4oY2hhclRvUmF3KHN0cmluZyksIGZpbGUpYOWGmeWFpeaWh+S7tg0KDQojIyMg5a2X56ym5Liy5YyF5ZCr5byV5paH5pe25byV5Y+355qE5L2/55SoDQoNCuiLpeS4gOS4quWtl+espuS4suWMheWQq+W8leaWh++8jOS4uuS6huespuWQiOihjOaWh+S5oOaDryjlj4zlvJXlj7cp77yM5bm25L2/5Luj56CB5byV5Y+35pyJ5bGC5qyh55qE5Yy65Yir77yM5aSW5bGC5bqU55So5Y2V5byV5Y+344CCDQoNCmBgYHtyIHF1b3RlLCByZXN1bHRzPSdob2xkJ30NCiMgc3RyaW5nMSA8LSAnV2hlbiBwdXR0aW5nIGEgJ3F1b3RlJyBpbnNpZGUgYSBzdHJpbmcsIHVzZSBzaW5nbGUgcXVvdGF0aW9uIG1hcmtzLicNCiMgc3RyaW5nMSA8LSAiV2hlbiBwdXR0aW5nIGEgInF1b3RlIiBpbnNpZGUgYSBzdHJpbmcsIHVzZSBkb3VibGUgcXVvdGF0aW9uIG1hcmtzLiINCnN0cmluZzMgPC0gIldoZW4gcHV0dGluZyBhICdxdW90ZScgaW5zaWRlIGEgc3RyaW5nLCB1c2Ugc2luZ2xlIHF1b3RhdGlvbiBtYXJrcyBpbnNpZGUgYW5kIGRvdWJsZSBxdW90YXRpb24gbWFya3Mgb3V0c2lkZS4iDQpzdHJpbmc0IDwtICdXaGVuIHB1dHRpbmcgYSAicXVvdGUiIGluc2lkZSBhIHN0cmluZywgdXNlIGRvdWJsZSBxdW90YXRpb24gbWFya3MgaW5zaWRlIGFuZCBzaW5nbGUgcXVvdGF0aW9uIG1hcmtzIG91dHNpZGUuJw0KDQojIOWtl+espuS4sueahOaJk+WNsOW9ouW8jw0Kc3RyaW5nMw0Kc3RyaW5nNA0KYGBgDQoNCmBgYHtyLCByZXN1bHRzPSdob2xkJ30NCiMg5a2X56ym5Liy57yW6K+R5ZCO5a+85Ye655qE5b2i5byPDQp3cml0ZUxpbmVzKHN0cmluZzMpDQp3cml0ZUxpbmVzKHN0cmluZzQpDQpgYGANCg0Kc3RyaW5nMeWSjHN0cmluZzLmnInor63ms5XplJnor6/vvIzliY3lkI7kuKTkuKrlrZfnrKbkuLLkuK3pl7TlpLnkuobkuIDkuKrnvJbor5HlmajkuI3orqTor4bnmoRxdW90ZeOAgg0KDQpzdHJpbmcz5ZKMc3RyaW5nNOWcqOivreazleS4iuayoemXrumimO+8jOS9hue8luivkeaXtuaViOaenOS4jeWQjOOAgnN0cmluZzTmiY3mmK/miJHku6zmg7PopoHnmoTjgIINCg0KIyMjIOi9rOS5ieespuWPtyJcXCINCg0K5Y+N5pac5p2gIlxcIuWPr+S7peWwhlLor63oqIDlj4pSIE1hcmtkb3du55qE6K+t5rOV6YOo5Lu26L2s5LmJ5Li65a2X56ym44CCXA0K5aaC5p6c5oOz6KaB5Zyo5a2X56ym5Liy5Lit5YyF5ZCr5LiA5Liq5Y2V5byV5Y+35oiW5Y+M5byV5Y+377yM5Y+v5Lul5L2/55SoIlxcJyLlkowiXFwiIuWvueWFtui/m+ihjOi9rOS5ieOAgg0KDQpgYGB7ciwgcmVzdWx0cz0naG9sZCd9DQpzdHJpbmc1IDwtICdJIGRvblwndCBrbm93IHdoeSB0aGV5IGNhbGxlZCB1cyAiQ2hpbmVzZSIuJw0Kc3RyaW5nNiA8LSAiV2hlbiBwdXR0aW5nIGEgXCJxdW90ZVwiIGluc2lkZSBhIHN0cmluZywgeW91IGNhbiB1c2UgYmFja3NsYXNoIGFzIHdlbGwuIg0Kd3JpdGVMaW5lcyhzdHJpbmc1KQ0Kd3JpdGVMaW5lcyhzdHJpbmc2KQ0KYGBgDQoNCuWmguaenOaDs+imgeWcqOWtl+espuS4suS4reWMheWQq+S4gOS4quWPjeaWnOadoO+8jOWwsemcgOimgeS9v+eUqOS4pOS4quWPjeaWnOadoO+8miJcXFxcIg0KDQpgYGB7ciwgcmVzdWx0cz0naG9sZCd9DQpzdHJpbmc3IDwtICJJIHRoaW5rIHRoZSBiYWNrc2xhc2ggXCJcXFwiIGlzIGFtYXppbmchIg0KcHJpbnQoc3RyaW5nNykgIyDmiJYgc3RyaW5nNw0Kd3JpdGVMaW5lcyhzdHJpbmc3KQ0KYGBgDQoNCiMjIyDnibnmrorlrZfnrKYNCg0K5YW25LuW5Yeg56eN54m55q6K5a2X56ym44CC5pyA5bi455So55qE5piv5o2i6KGM56ymIlxcbiLjgIHliLbooajnrKYiXFx0IuWSjOmAgOagvCJcXGIi77yM5L2g5Y+v5Lul5L2/55SoID8nIicg5oiWID8iJyIg6LCD5Ye65biu5Yqp5paH5Lu25p2l5p+l55yL5a6M5pW055qE54m55q6K5a2X56ym5YiX6KGo44CCDQoNCmBgYHtyLCByZXN1bHRzPSdob2xkJ30NCnN0cmluZzggPC0gJ1RoZSBzcGVjaWFsIGNoYXJhY3RlcnMgIlxuIiwgIlx0IiBhbmQgIlxiIiBhcmUgdmVyeSBpbnRlcmVzaW5nLicNCnN0cmluZzkgPC0gIlRoZSBzcGVjaWFsIGNoYXJhY3RlcnMgXCJcXG5cIiwgXCJcXHRcIiBhbmQgXCJcXGJcIiBhcmUgdmVyeSBpbnRlcmVzaW5nLiINCndyaXRlTGluZXMoc3RyaW5nOCkNCndyaXRlTGluZXMoc3RyaW5nOSkNCmBgYA0KDQrnlJ/miJDnmoRodG1s5paH5Lu25LitIlxcYiLlj5jmiJDkuobkuIDkuKrlpYfmgKrnmoTmlrnmoYbvvIzmjqfliLblj7DmmL7npLrnmoTliJnmmK/mraPnoa7nmoTjgILlj6/og71SIE1hcmtkb3du5LiOUuivreiogOS4rSJcXGIi55qE5ZCr5LmJ5LiN5ZCM44CCDQoNCiMjIOWtl+espuS4suagvOW8j+WMlg0KDQojIyMgYmFzZTo6c3ByaW50ZigpDQoNCmBzcHJpbnRmKOWMheWQq+WNoOS9jeespueahOWtl+espuS4su+8jOassuWhq+WFheeahOWtl+espuS4silgDQoNCnwgKirljaDkvY3nrKYqKiAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwtLS0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgXCUgICAgICAgICAgICAgIHwg5aaC5p6c6KaB5Zyo5Y2g5L2N56ym5YaF6YOo5L2/55SoJe+8jOmcgOimgeWGmeaIkCUlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoq6L2s5o2i56ymKiogICAgICB8ICoq57G75Z6LKiogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgZCAgICAgICAgICAgICAgIHwg5Y2B6L+b5Yi25pW05pWwICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCBvICAgICAgICAgICAgICAgfCDlhavov5vliLbmlbTmlbAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IHgsIFggICAgICAgICAgICB8IOWwjy/lpKflhpnljYHlha3ov5vliLbmlbTmlbAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IGYgICAgICAgICAgICAgICB8IOWNgei/m+WItua1rueCueaVsCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IGUsIEUgICAgICAgICAgICB8IOenkeWtpuiuoeaVsOazleihqOekuueahOaVsO+8jGXlsI/lhpnmiJblpKflhpkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IGcsIEcgICAgICAgICAgICB8IOaZuuiDvemAieaLqeS9v+eUqOa1rueCueaVsOi/mOaYr+enkeWtpuiuoeaVsOazle+8iGflr7nlupRl77yMR+WvueW6lEXvvInvvIzpu5jorqTmmL7npLo25L2N5pyJ5pWI5pWw5a2XICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IHMgICAgICAgICAgICAgICB8IOWtl+espuS4siAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICoq5rWu54K55pWw5qC85byPKiogIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgd2lkdGgucHJlY2lzaW9uIHwg6L6T5Ye655qE5pyA5bCP5a695bqm77yI5LiN6Laz5Lul56m65qC86KGl6b2Q77yJLui+k+WHuueyvuW6pu+8iOWvuWblkoxlL0XmhI/lkbPnnYDlsI/mlbDngrnlkI7lh6DkvY3vvIzlr7lnL0fmhI/lkbPnnYDmnInmlYjmlbDlrZfkvY3mlbDvvIkgfA0KfCBcLSAgICAgICAgICAgICAgfCDmjIflrprlt6blr7npvZDvvIjpu5jorqTlj7Plr7npvZDvvInvvIzljbPlrr3luqbkuI3otrPml7bku47lj7PovrnooaXlhYXnqbrmoLwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IFwrICAgICAgICAgICAgICB8IOawuOi/nOW4puespuWPt++8iOm7mOiupOWPquaciei0n+aVsOW4puS4gOS4qui0n+WPt++8jOato+aVsOS4jeW4puespuWPt++8iSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCDnqbrmoLwgICAgICAgICAgICB8IOmmluWtl+espuW5tumdnui0n+WPt+aXtu+8jOepuuS4gOagvCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IDAgICAgICAgICAgICAgICB8IOWuveW6puS4jei2s+aXtuihpTDvvIzogIzpnZ7pu5jorqTnmoTnqbrmoLwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCg0KYGBge3J9DQpzcHJpbnRmKCJUaGUgcmF0aW8gaXMgJWQlJSIsIDEwKQ0KDQoNCnNwcmludGYoIiVmIiwgcGkpICMg5rWu54K55pWwDQpzcHJpbnRmKCIlLjNmIiwgcGkpICMg5L+d55WZ5bCP5pWw54K55ZCOM+S9jQ0Kc3ByaW50ZigiJS4wZiIsIHBpKSAjIOS/neeVmeWwj+aVsOeCueWQjjDkvY0NCnNwcmludGYoIiU1LjFmIiwgcGkpICMg5pyA5bCP5a695bqm5Li6Ne+8jOS/neeVmeWwj+aVsOeCueWQjjHkvY3vvIzkuI3otrPooaXnqbrmoLwNCnNwcmludGYoIiUwNS4xZiIsIHBpKSAjIOeUqDDooaXpvZANCnNwcmludGYoIiUrZiIsIHBpKSAjIOawuOi/nOW4puespuWPtw0Kc3ByaW50ZigiJSBmIiwgcGkpDQpzcHJpbnRmKCIlLTEwZiIsIHBpKSAjIOW3puWvuem9kO+8jOWuveW6puS4ujEwDQpzcHJpbnRmKCIlZSIsIHBpKQ0Kc3ByaW50ZigiJUUiLCBwaSkNCnNwcmludGYoIiVnIiwgcGkpDQpzcHJpbnRmKCIlZyIsIDFlNiAqIHBpKQ0Kc3ByaW50ZigiJS45ZyIsIDFlNiAqIHBpKQ0Kc3ByaW50ZigiJS4yZyIsIDIzLjc1KQ0KDQpzcHJpbnRmKA0KICAibWluIDEwLWNoYXIgc3RyaW5nICclMTBzJyIsDQogIGMoImEiLCAiQUJDIiwgImFuZCBhbiBldmVuIGxvbmdlciBvbmUiKQ0KKQ0KYGBgDQoNCiMjIyDlgJ/liqkgcHl0aG9uDQoNCuWKoOWFpSBweXRob24g5Luj56CB5Z2X77yM5L2/55SoIHB5dGhvbiDkuK3nmoTlrZfnrKbkuLLmoLzlvI/ljJbmlrnms5UgLmZvcm1hdCgpDQoNCmDmqKHmnb/lrZfnrKbkuLIuZm9ybWF0KOS7pemAl+WPt+WIhumalOeahOWhq+WFheWPguaVsOW6j+WIlylgDQoNCuWFtuS4re+8jGB7fWDkuLrmqKHmnb/kuK3nmoTmp73vvIzmp73lhoXpg6jlrprkuYnmr4/kuKrmp73nmoTmoLzlvI/ljJbmoLflvI/vvIzlvaLlpoLvvJoNCg0KYGlkOltmaWxsXVthbGlnbl1bc2lnbl1bd2lkdGhdWyxdWy5wcmVjaXNpb25dW3R5cGVdYA0KDQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCDnsbvliKsgICAgICAgfCDlkKvkuYkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorPT09PT09PT09PT09Kz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Kw0KfCBmaWxsICAgICAgIHwg5a695bqm5LiN6Laz5pe25aGr5YWF55qE5a2X56ymICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBhbGlnbiAgICAgIHwg5a+56b2Q5pa55byPLiBgPGAg5bem5a+56b2Q77ybYD5gIOWPs+Wvuem9kO+8m2A9YCDlhoXlrrnlj7Plr7npvZDvvIznrKblj7fmlL7nva7lnKjloavlhYXlrZfnrKbvvIjlpoIiMCLvvInnmoTlt6bkvqfvvJtgXmAg5bGF5LitIHwNCistLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8IHNpZ24gICAgICAgfCBgK2Ag5q2j5pWw5YqgYCtg77yM6LSf5pWw5YqgYC1gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgfCDnqbrmoLzvvIzmraPmlbDliqDnqbrmoLzvvIzotJ/mlbDliqBgLWAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgLCAgICAgICAgICB8IOS4uuaVsOWtl+a3u+WKoOWNg+S9jeWIhumalOespiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgd2lkdGggICAgICB8IOi+k+WHuueahOacgOWwj+WuveW6pu+8iOS4jei2s+S7pSBmaWxsIOihpem9kO+8iSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8IC5wcmVjaXNpb24gfCDlsI/mlbDngrnlkI7kv53nlZnnsr7luqYgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCB0eXBlICAgICAgIHwg5qC85byP5YyW57G75Z6LICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgICAgICAgICAgICB8IHMg5a2X56ym5Liy77yI5LiN5oyH5a6adHlwZeaXtum7mOiupO+8iSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8ICAgICAgICAgICAgfCBkIOWNgei/m+WItuaVtOaVsCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAgICAgICAgICAgIHwgbyDlhavov5vliLbmlbTmlbAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgICAgICAgICAgICB8IHgsIFgg5bCPL+Wkp+WGmeWNgeWFrei/m+WItuaVtOaVsCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAgICAgICAgICAgIHwgZiDljYHov5vliLbmta7ngrnmlbAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCistLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQp8ICAgICAgICAgICAgfCBlLCBFIOenkeWtpuiuoeaVsOazleihqOekuueahOaVsO+8jGXlsI/lhpnmiJblpKflhpkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgICAgICAgICAgICB8IGcsIEcg5pm66IO96YCJ5oup5L2/55So5rWu54K55pWw6L+Y5piv56eR5a2m6K6h5pWw5rOV77yIZ+WvueW6lGXvvIxH5a+55bqURe+8ie+8jOm7mOiupOaYvuekujbkvY3mnInmlYjmlbDlrZcgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAgICAgICAgICAgIHwgXCUg55m+5YiG5q+U77yM6buY6K6k5pi+56S65bCP5pWw54K55ZCONuS9jSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCAgICAgICAgICAgIHwgYiDkuozov5vliLbmlbTmlbAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSsNCnwgICAgICAgICAgICB8IGMgMTDov5vliLbmlbTmlbDovazmjaLkuLogVW5pY29kZSDlrZfnrKYgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQorLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KDQrov5nnp43mlrnms5XnmoTnvLrngrnlnKjkuo7vvIzovpPlh7rlrr3luqbjgIHnsr7luqbnrYnlj4LmlbDlv4XpobvmmK/noa7lrprnmoTluLjmlbDvvIzogIzkuI3og73mmK/lj5jph4/jgILlm6DmraTlvZPov5nkupvmlbDlgLzlv4Xpobvlj5blj5jph4/ml7bvvIzku43opoHkvb/nlKhgLnJqdXN0KClg44CBYC5sanVzdCgpYOWSjGAuY2VudGVyKClg5pa55rOV44CCDQoNCuekuuS+i++8mg0KDQpgYGB7cHl0aG9ufQ0Kc3RyMSA9ICJ7MDo9XjIwfSIuZm9ybWF0KCJQeXRob24iKSANCnN0cjEgIyDnlKggJz0nIOWhq+WFhe+8jOWxheS4reWvuem9kO+8jOanveWuveW6puS4ujIwDQoNCnN0cjIgPSAiezo9XjIwfSIuZm9ybWF0KCJQeXRob24iKSANCnN0cjIgIyDlj4LmlbDntKLlvJUw5Y+v5Lul55yB55WlDQoNCnN0cjMgPSAiezoqPjIwfSIuZm9ybWF0KCJCSVQiKSANCnN0cjMgIyDnlKggJyonIOWhq+WFhe+8jOWPs+Wvuem9kO+8jOanveWuveW6puS4ujIwDQoNCnN0cjQgPSAiezoxMH0iLmZvcm1hdCgiQklUIikNCnN0cjQgIyDpu5jorqTlt6blr7npvZDvvIznqbrmoLzloavlhYUNCg0Kc3RyNSA9ICJ7OiwuMmZ9Ii5mb3JtYXQoMTIzNDUuNjc4OSkNCnN0cjUgIyDkvb/nlKjljYPkvY3liIbpmpTnrKbvvIzkv53nlZnkuKTkvY3lsI/mlbDnmoTmta7ngrnlnosNCg0Kc3RyNiA9ICJ7MDpifSwgezA6Y30sIHswOmR9LCB7MDpvfSwgezA6eH0sIHswOlh9Ii5mb3JtYXQoNDI2KQ0Kc3RyNiAjIOS4gOS4quWPguaVsOWhq+WIsOaJgOacieanveS4re+8jOWIhuWIq+i9rOaNouS4uu+8muS6jOi/m+WItu+8jFVuaWNvZGUg57yW56CB77yM5Y2B6L+b5Yi277yM5YWr6L+b5Yi277yM5Y2B5YWt6L+b5Yi277yM5aSn5YaZ55qE5Y2B5YWt6L+b5Yi2DQoNCnN0cjcgPSAiezA6LjJlfSwgezA6LjJFfSwgezA6LjJmfSwgezA6LjIlfSIuZm9ybWF0KDMuMTQpDQpzdHI3ICMg5YiG5Yir6L2s5o2i5Li677ya56eR5a2m6K6w5pWw5rOV77yM5aSn5YaZ56eR5a2m6K6w5pWw5rOV77yM5rWu54K55pWw77yM55m+5YiG5pWwDQoNCnN0cjggID0gIkkgYW0gezpzfSwgYWdlIHs6ZH0sIG1vbmV5IHs6LjJmfSIuZm9ybWF0KCJzZXZlbiIsIDE4LCA4ODg4OC4xKQ0Kc3RyOCAjIOanveS4juWhq+WFheWPguaVsOeahOmhuuW6j+aBsOWlveWvueW6lOaXtu+8jOS5n+WPr+S7peecgeeVpeWPguaVsOe0ouW8lQ0KYGBgDQoNCiMjIyBmb3JtYXR0YWJsZSDljIUNCg0KLSBwZXJjZW50KCkNCi0gY29tbWEoKQ0KLSBjdXJyZW5jeSgpDQotIGFjY291bnRpbmcoKe+8jOS8muiuoeagvOW8j++8jOWmgui0n+aVsOeUqOaLrOWPt+ihqOekug0KLSBzY2llbnRpZmljKCkNCg0KDQpgYGB7cn0NCmxpYnJhcnkoZm9ybWF0dGFibGUpDQoNCmRmIDwtDQogIGRhdGEuZnJhbWUoDQogICAgaWQgPSAxOjEwLA0KICAgIG5hbWUgPSBjKA0KICAgICAgIkJvYiIsDQogICAgICAiQXNobGV5IiwNCiAgICAgICJKYW1lcyIsDQogICAgICAiRGF2aWQiLA0KICAgICAgIkplbm55IiwNCiAgICAgICJIYW5zIiwNCiAgICAgICJMZW8iLA0KICAgICAgIkpvaG4iLA0KICAgICAgIkVtaWx5IiwNCiAgICAgICJMZWUiDQogICAgKSwNCiAgICBhZ2UgPSBjKDI4LCAyNywgMzAsIDI4LCAyOSwgMjksIDI3LCAyNywgMzEsIDMwKSwNCiAgICBncmFkZSA9IGMoIkMiLCAiQSIsICJBIiwgIkMiLCAiQiIsICJCIiwgIkIiLCAiQSIsICJDIiwgIkMiKSwNCiAgICB0ZXN0MV9zY29yZSA9IGMoOC45LCA5LjUsIDkuNiwgOC45LCA5LjEsIDkuMywgOS4zLCA5LjksIDguNSwgOC42KSwNCiAgICB0ZXN0Ml9zY29yZSA9IGMoOS4xLCA5LjEsIDkuMiwgOS4xLCA4LjksIDguNSwgOS4yLCA5LjMsIDkuMSwgOC44KSwNCiAgICBmaW5hbF9zY29yZSA9IGMoOSwgOS4zLCA5LjQsIDksIDksIDguOSwgOS4yNSwgOS42LCA4LjgsIDguNyksDQogICAgcmVnaXN0ZXJlZCA9IGMoVFJVRSwgRkFMU0UsIFRSVUUsIEZBTFNFLCBUUlVFLCBUUlVFLCBUUlVFLCBGQUxTRSwgRkFMU0UsIEZBTFNFKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KZGYNCg0KZm9ybWF0dGFibGUoDQogIGRmLA0KICBsaXN0KA0KICAgIGFnZSA9IGNvbG9yX3RpbGUoIndoaXRlIiwgIm9yYW5nZSIpLA0KICAgIGdyYWRlID0gZm9ybWF0dGVyKCJzcGFuIiwgc3R5bGUgPSB4IH4gaWZlbHNlKA0KICAgICAgeCA9PSAiQSIsIHN0eWxlKGNvbG9yID0gImdyZWVuIiwgZm9udC53ZWlnaHQgPSAiYm9sZCIpLCBOQQ0KICAgICkpLA0KICAgIGFyZWEoY29sID0gYyh0ZXN0MV9zY29yZSwgdGVzdDJfc2NvcmUpKSB+IG5vcm1hbGl6ZV9iYXIoInBpbmsiLCAwLjIpLA0KICAgIGZpbmFsX3Njb3JlID0gZm9ybWF0dGVyKA0KICAgICAgInNwYW4iLA0KICAgICAgc3R5bGUgPSB4IH4gc3R5bGUoY29sb3IgPSBpZmVsc2UocmFuaygteCkgPD0gMywgImdyZWVuIiwgImdyYXkiKSksDQogICAgICB4IH4gc3ByaW50ZigiJS4yZiAocmFuazogJTAyZCkiLCB4LCByYW5rKC14KSkNCiAgICApLA0KICAgIHJlZ2lzdGVyZWQgPSBmb3JtYXR0ZXIoDQogICAgICAic3BhbiIsDQogICAgICBzdHlsZSA9IHggfiBzdHlsZShjb2xvciA9IGlmZWxzZSh4LCAiZ3JlZW4iLCAicmVkIikpLA0KICAgICAgeCB+IGljb250ZXh0KGlmZWxzZSh4LCAib2siLCAicmVtb3ZlIiksIGlmZWxzZSh4LCAiWWVzIiwgIk5vIikpDQogICAgKQ0KICApDQopDQpgYGANCmDmloflrZfmoLzlvI/oh6rlrprkuYlg44CBYOaWh+Wtl+iDjOaZr+iHquWumuS5iWDjgIFg5paH5pys6Ieq5a6a5LmJYOS4ieenjeiHquWumuS5ieWPr+inhuWMluexu+Wei++8mg0KDQotIGNvbG9yX3RpbGXlh73mlbDnlKjkuo7ovpPlh7rmjInnhafmlbDlgLzph4/nuqfov5vooYzpopzoibLog4zmma/loavlhYXnmoTliJfjgIINCi0gZm9ybWF0dGVy5Ye95pWw5o+Q5L6b5a2X5L2T5pi+56S65qC85byP55qE6Ieq5a6a5LmJ77yMZ3JhZGXliJfoh6rlrprkuYnkuoblgLzkuLpB55qE6K6w5b2V5pi+56S657u/6Imy77yM5bm25bCG5a2X5L2T5Yqg57KX77yM5ZCm5YiZ5b+955Wl44CCDQotIHRlc3QxX3Njb3JlLCB0ZXN0Ml9zY29yZeS4pOWIl+mAmui/h2FyZWHlh73mlbDlnKjlr7nlupTlrZfkvZPog4zmma/kvY3nva7kvb/nlKjmnaHlvaLlm77mnaXku6PooajmjIfmoIfph4/nuqflpKflsI/vvIzpopzoibLloavlhYXnsonoibLjgIINCi0gZmluYWxfc2NvcmXliJflr7nmjIfmoIfmjInnhad0b3Az5pi+56S657u/6Imy77yM5YW25L2Z5pi+56S654Gw6Imy77yM5ZCM5pe25bCG5YaF5a655pi+56S65qC85byP6Ieq5a6a5LmJ5Li65rWu54K55Z6LKyhyYW5rOuWQjeasoSnov5vooYzmmL7npLrjgIINCi0gcmVnaXN0ZXJlZOWIl+WImeWcqOWvueWhq+WFheminOiJsuaMieeFp+WvueW6lOW4g+WwlOWAvOi/m+ihjOaYvuekuu+8iFRSVUXmmL7npLrnu7/oibLjgIFGQUxTReaYvuekuue6ouiJsu+8ieS5i+Wklu+8jOWcqOW3puS+p+a3u+WKoOS6huWvueeUqOeahGljb27mlofmnKzvvIhUUlVF5pi+56S657u/6Imy5a+55Y+377yMRkFMU0XmmL7npLrnuqLoibLlj4nlj7fvvInjgIINCg0KDQojIyDlh73mlbDliIbnsbsNCg0KKirlrZfnrKbkuLLmi7zmjqXlh73mlbAqKg0KDQotICAgc3RyX2M6IOWtl+espuS4suaLvOaOpeOAgg0KLSAgIHN0cl90cmltOiDljrvmjonlrZfnrKbkuLLnmoTnmb3nqbrmoLwNCi0gICBzdHJfcGFkOiDooaXlhYXlrZfnrKbkuLLnmoTplb/luqYNCi0gICBgc3RyX3NxdWlzaGDvvJrliKDpmaTlrZfnrKbkuLLkuK3lpJrkuo4x5Liq55qE56m65qC844CC6Z2e5bi45pyJ55So55qE5Ye95pWw77yBDQotICAgc3RyX2R1cDog5aSN5Yi25a2X56ym5LiyDQotICAgc3RyX3N1Yjog5a2X56ym5Liy5YiH54mHDQotICAgc3RyX3N1Ylw8LSDlrZfnrKbkuLLliIfniYfvvIzlubbotYvlgLzvvIzlkIxzdHJfc3ViDQoNCioq5a2X56ym5Liy6K6h566X5Ye95pWwKioNCg0KLSAgIHN0cl9jb3VudDog5a2X56ym5Liy6K6h5pWwDQotICAgYHN0cl9sZW5ndGgoKWA6IOWtl+espuS4sumVv+W6pu+8jOWQjGBuY2hhcigpYA0KLSAgIHN0cl9zb3J0OiDlrZfnrKbkuLLlgLzmjpLluo8NCi0gICBzdHJfb3JkZXI6IOWtl+espuS4sue0ouW8leaOkuW6j++8jOinhOWImeWQjHN0cl9zb3J0DQoNCioq5a2X56ym5Liy5Yy56YWN5Ye95pWwKioNCg0KLSAgIGBzdHJfdmlldygpL3N0cl92aWV3X2FsbCgpYCDmn6XnnItzdHJpbmfmmK/lkKbljLnphY1wYXR0ZXJu77yM5aaC5p6c5Yy56YWN77yM5bCx6auY5Lqu5pi+56S6DQotICAgc3RyX3NwbGl0OiDlrZfnrKbkuLLliIblibINCi0gICBzdHJfc3BsaXRfZml4ZWQ6IOWtl+espuS4suWIhuWJsu+8jOWQjHN0cl9zcGxpdA0KLSAgIHN0cl9zdWJzZXQ6IOi/lOWbnuWMuemFjeeahOWtl+espuS4sg0KLSAgIHdvcmQ6IOS7juaWh+acrOS4reaPkOWPluWNleivjQ0KLSAgIGBzdHJfZGV0ZWN0KClgL2BzdHJfdmlldygpYDog5qOA5p+l5Yy56YWN5a2X56ym5Liy55qE5a2X56ymDQotICAgc3RyX21hdGNoOiDku47lrZfnrKbkuLLkuK3mj5Dlj5bljLnphY3nu4TjgIINCi0gICBzdHJfbWF0Y2hfYWxsOiDku47lrZfnrKbkuLLkuK3mj5Dlj5bljLnphY3nu4TvvIzlkIxzdHJfbWF0Y2gNCi0gICBgY2hhcnRyKG9sZCwgbmV3LCBzdHJpbmcpYDog5LuOIG9sZCDliLAgbmV3IOeahOS4gOS4gOWvueW6lOeahOabv+aNou+8jOS9nOeUqOWcqCBzdHJpbmcg5LiKDQotICAgc3RyX3JlcGxhY2U6IOWtl+espuS4suabv+aNog0KLSAgIHN0cl9yZXBsYWNlX2FsbDog5a2X56ym5Liy5pu/5o2i77yM5ZCMc3RyX3JlcGxhY2UNCi0gICBzdHJfcmVwbGFjZV9uYTog5oqKTkHmm7/mjaLkuLrlrZfnrKbkuLIiTkEiDQotICAgc3RyX3JlbW92Ze+8muWIoOmZpOWMuemFjeeahOWtl+espuS4sg0KLSAgIHN0cl9yZW1vdmVfYWxs77ya5Yig6Zmk5omA5pyJ5Yy56YWN55qE5a2X56ymDQotICAgc3RyX2xvY2F0ZTog5om+5Yiw5Yy56YWN55qE5a2X56ym5Liy55qE5L2N572u44CCDQotICAgc3RyX2xvY2F0ZV9hbGw6IOaJvuWIsOWMuemFjeeahOWtl+espuS4sueahOS9jee9rizlkIxzdHJfbG9jYXRlDQotICAgc3RyX2V4dHJhY3Q6IOS7juWtl+espuS4suS4reaPkOWPluWMuemFjeWtl+espg0KLSAgIHN0cl9leHRyYWN0X2FsbDog5LuO5a2X56ym5Liy5Lit5o+Q5Y+W5Yy56YWN5a2X56ym77yM5ZCMc3RyX2V4dHJhY3QNCg0KKirlrZfnrKbkuLLmoLzlvI/ljJYqKg0KDQotIGBiYXNlOjpzcHJpbnRmKClg77yM6K+m6KeB5LiK5LiA6IqCDQotICAgYHN0cl9nbHVlKClg77ya5Y+C5pWw6YeM6Z2i55qEYHt9YOWNoOS9jeespu+8jOWQjGphdmFzY3JpcHTnmoRgJHt9YA0KLSAgIHN0cl93cmFwOiDmjqfliLbplb/lrZfnrKbkuLLovpPlh7rmjpLniYgNCi0gICBzdHJfY29udjog5a2X56ym57yW56CB6L2s5o2iDQotICAgYHN0cl90b191cHBlcigpYC9gdG91cHBlcigpYDog5a2X56ym5Liy6L2s5oiQ5aSn5YaZDQotICAgYHN0cl90b19sb3dlcigpYC9gdG9sb3dlcigpYDog5a2X56ym5Liy6L2s5oiQ5bCP5YaZLOinhOWImeWQjHN0cl90b191cHBlcg0KLSAgIHN0cl90b190aXRsZSgpOiDlrZfnrKbkuLLovazmiJDpppblrZfmr43lpKflhpks6KeE5YiZ5ZCMc3RyX3RvX3VwcGVyDQotICAgc3RyX3RvX3NlbnRlbmNlKCk6IOWPquacieS4gOWPpeeahOmmluWtl+avjeWkp+WGmQ0KDQoqKuWPguaVsOaOp+WItuWHveaVsO+8jOS7heeUqOS6juaehOmAoOWKn+iDveeahOWPguaVsO+8jOS4jeiDveeLrOeri+S9v+eUqOOAgioqDQoNCi0gICBib3VuZGFyeTog5a6a5LmJ5L2/55So6L6555WMDQotICAgY29sbDog5a6a5LmJ5a2X56ym5Liy5qCH5YeG5o6S5bqP6KeE5YiZ44CCDQotICAgZml4ZWQ6IOWumuS5ieeUqOS6juWMuemFjeeahOWtl+espu+8jOWMheaLrOato+WImeihqOi+vuW8j+S4reeahOi9rOS5ieespg0KLSAgIHJlZ2V4OiDlrprkuYnmraPliJnooajovr7lvI8NCg0KDQojIyBzdHJpbmdyOjoNCg0KPGEgaHJlZj0iLi4vcGRmL2NoZWF0c2hlZXQtc3RyaW5ncy5wZGYiPjxzdHJvbmc+c3RyaW5nciBjaGVhdHNoZWV0LnBkZjwvc3Ryb25nPjwvYT4NCg0KPG9iamVjdCBkYXRhPSIuLi9wZGYvY2hlYXRzaGVldC1zdHJpbmdzLnBkZiIgdHlwZT0iYXBwbGljYXRpb24vcGRmIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIj48L29iamVjdD4NCg0KDQojIyMgYHN0cl9sZW5ndGgoKWAg5a2X56ym5Liy6ZW/5bqmDQoNCuWQjCBgYmFzZTo6bmNoYXIoKWANCg0K5L2/55SoIFJTdHVkaW/vvIzpgqPkuYjpgJrnlKjliY3nvIAgc3RyXF8g5Lya54m55Yir5pyJ55So77yM5Zug5Li66L6T5YWlIHN0clxfIOWQjuS8muinpuWPkeiHquWKqOWujOaIkOWKn+iDve+8jOS9oOWPr+S7peeci+WIsOaJgOacieeahOWtl+espuS4suWHveaVsO+8mg0KDQpgYGB7cn0NCnN0cl9sZW5ndGgoYygiYSIsICJSIGZvciBkYXRhIHNjaWVuY2UiKSkNCnN0cl9sZW5ndGgoYygiYSIsICJSIGZvciBkYXRhIHNjaWVuY2UiKSkgJT4lIHN1bSgpDQpgYGANCg0KIyMjIGBzdHJfYygpYOWtl+espuS4sue7hOWQiA0KDQpgc3RyX2MoLi4uLCBzZXAgPSAiIiwgY29sbGFwc2UgPSBOVUxMKWANCg0K5ZCI5bm26Iul5bmy5Liq5a2X56ym5Liy5oiW5a2X56ym5ZCR6YeP55qE5a+55bqU5YWD57Sg77yM6buY6K6k5peg6Ze06ZqU56ymDQoNCmBgYHtyfQ0KIyDlkJHph4/ljJbmk43kvZzvvIzoh6rliqjlvqrnjq/nn63lkJHph48NCnN0cl9jKCJwcmVmaXgtIiwgYygiYSIsICJiIiwgImMiKSwgIi1zdWZmaXgiKQ0KYGBgDQoNCmBgYHtyfQ0KIyDlkIjlubbkuIDkuKrlrZfnrKblkJHph4/nmoTlkITlhYPntKDkuLrkuIDkuKrplb/lrZfnrKbkuLLvvIzkvb/nlKjlj4LmlbBjb2xsYXBzZe+8jOWwhuWQkemHj+KAnOWhjOe8qeKAneS4uuS4gOS4quWtl+espuS4sg0Kc3RyX2MoYygieCIsICJ5IiwgInoiKSwgY29sbGFwc2UgPSAiKyIpDQpgYGANCg0KDQpgc3RyX3JlcGxhY2VfbmEoc3RyaW5nLCByZXBsYWNlbWVudCA9ICJOQSIpYO+8jOm7mOiupOWwhiBuYSDovazljJbkuLrlrZfnrKbkuLIiTkEiDQoNCmBgYHtyfQ0KeCA8LSBjKCJhYmMiLCBOQSkNCnN0cl9jKCJ8LSIsIHgsICItfCIpDQoNCnN0cl9jKCJ8LSIsIHN0cl9yZXBsYWNlX25hKHgpLCAiLXwiKQ0KYGBgDQoNCiMjIyBgc3RyX3RyaW0oKWAg5Y675o6J5a2X56ym5Liy6aaW5bC+55qE56m65qC85ZKM5Yi26KGo56ymKFxcdCkNCg0KPiBzdHJfdHJpbShzdHJpbmcsIHNpZGUgPSBjKCJib3RoIiwgImxlZnQiLCAicmlnaHQiKSkNCg0Kc2lkZTog6L+H5ruk5pa55byP77yMYm90aOS4pOi+uemDvei/h+a7pO+8jGxlZnTlt6bovrnov4fmu6TvvIxyaWdodOWPs+i+uei/h+a7pA0KDQojIyMgYHN0cl9wYWQoKWAg6KGl5YWF5a2X56ym5Liy55qE6ZW/5bqmDQoNCj4gc3RyX3BhZChzdHJpbmcsIHdpZHRoLCBzaWRlID0gYygibGVmdCIsICJyaWdodCIsICJib3RoIiksIHBhZCA9ICIgIikNCg0Kd2lkdGg6IOWtl+espuS4suWhq+WFheWQjueahOmVv+W6plwNCnNpZGU6IOWhq+WFheaWueWQke+8jGJvdGjkuKTovrnpg73loavlhYXvvIxsZWZ05bem6L655aGr5YWF77yMcmlnaHTlj7PovrnloavlhYVcDQpwYWQ6IOeUqOS6juWhq+WFheeahOWtl+espg0KDQojIyMgYHN0cl9kdXAoKWAg5aSN5Yi25a2X56ym5LiyDQoNCj4gc3RyX2R1cChzdHJpbmcsIHRpbWVzKQ0KDQp0aW1lczog5aSN5Yi25qyh5pWwDQoNCmBgYHtyfQ0KdmFsIDwtIGMoImFiY2E0IiwgMTIzLCAiY2JhMiIpDQoNCiMg5aSN5Yi2MuasoQ0Kc3RyX2R1cCh2YWwsIDIpDQoNCiMg5oyJ5L2N572u5aSN5Yi2DQpzdHJfZHVwKHZhbCwgMTozKQ0KYGBgDQoNCiMjIyBgc3RyX3dyYXAoKWAg5o6n5Yi26ZW/5a2X56ym5Liy6L6T5Ye65o6S54mIDQoNCj4gc3RyX3dyYXAoc3RyaW5nLCB3aWR0aCA9IDgwLCBpbmRlbnQgPSAwLCBleGRlbnQgPSAwKQ0KDQp3aWR0aDog6K6+572u5LiA6KGM5omA5Y2g55qE5a695bqm44CCXA0KaW5kZW50OiDmrrXokL3pppbooYznmoTnvKnov5vlgLxcDQpleGRlbnQ6IOauteiQvemdnummluihjOeahOe8qei/m+WAvA0KDQoNCmBgYHtyfQ0KIyDov5nmrrXku6PnoIHlj6/ku6XlnKggY29uc29sZSDkuK3ov5DooYzvvIzkvYbkuI3og73lnKggUm1hcmtkb3duIOS4reebtOaOpei/kOihjA0Kc3RyIDwtICJS6K+t6KiA5L2c5Li657uf6K6h5a2m5LiA6Zeo6K+t6KiA77yM5LiA55u05Zyo5bCP5LyX6aKG5Z+f6Zeq6ICA552A5YWJ6IqS44CC55u05Yiw5aSn5pWw5o2u55qE54iG5Y+R77yMUuivreiogOWPmOaIkOS6huS4gOmXqOeCmeaJi+WPr+eDreeahOaVsOaNruWIhuaekOeahOWIqeWZqOOAgumaj+edgOi2iuadpei2iuWkmueahOW3peeoi+iDjOaZr+eahOS6uueahOWKoOWFpe+8jFLor63oqIDnmoTnpL7ljLrlnKjov4XpgJ/mianlpKfmiJDplb/jgILnjrDlnKjlt7LkuI3ku4Xku4XmmK/nu5/orqHpoobln5/vvIzmlZnogrLvvIzpk7booYzvvIznlLXllYbvvIzkupLogZTnvZHigKYu6YO95Zyo5L2/55SoUuivreiogOOAgiINCg0KDQojIOiuvue9ruWuveW6puS4ujQw5Liq5a2X56ymDQojIGNhdChzdHJfd3JhcChzdHIsIHdpZHRoID0gNDApLCAiXG4iKQ0KDQojIOiuvue9ruWuveW6puS4ujYw5a2X56ym77yM6aaW6KGM57yp6L+bNOWtl+espg0KIyBjYXQoc3RyX3dyYXAodHh0LCB3aWR0aCA9IDYwLCBpbmRlbnQgPSA0KSwgIlxuIikNCg0KIyDorr7nva7lrr3luqbkuLoxMOWtl+espu+8jOmdnummluihjOe8qei/mzTlrZfnrKYNCiMgY2F0KHN0cl93cmFwKHR4dCwgd2lkdGggPSAxMCwgZXhkZW50ID0gNCksICJcbiIpDQpgYGANCg0KIyMjIGBzdHJfc3BsaXQoKWAg5ouG5YiG5a2X56ym5LiyDQoNCj4gc3RyX3NwbGl0KHN0cmluZywgcGF0dGVybiwgbiA9IEluZikgc3RyX3NwbGl0X2ZpeGVkKHN0cmluZywgcGF0dGVybiwgbikNCg0KcGF0dGVybjog5Yy56YWN55qE5a2X56ym44CCXA0Kbjog5YiG5Ymy5Liq5pWwDQoNCmBgYHtyfQ0KdmFsIDwtICJhYmMsMTIzLDIzNCxpdXV1Ig0KDQojIOS7pSzov5vooYzliIblibINCnMxIDwtIHN0cl9zcGxpdCh2YWwsICIsIikNCnMxDQoNCiMg5LulLOi/m+ihjOWIhuWJsu+8jOS4lOWPquS/neeVmTLlnZcNCnMyIDwtIHN0cl9zcGxpdCh2YWwsICIsIiwgMikNCnMyDQoNCiMg5p+l55yLc3RyX3NwbGl0KCnlh73mlbDmk43kvZznmoTnu5PmnpznsbvlnotsaXN0DQpjbGFzcyhzMSkNCg0KIyDnlKhzdHJfc3BsaXRfZml4ZWQoKeWHveaVsOWIhuWJsu+8jOe7k+aenOexu+Wei+aYr21hdHJpeA0KczMgPC0gc3RyX3NwbGl0X2ZpeGVkKHZhbCwgIiwiLCAyKQ0KczMNCmNsYXNzKHMzKQ0KDQoNCmZydWl0cyA8LSBjKA0KICAiYXBwbGVzIGFuZCBvcmFuZ2VzIGFuZCBwZWFycyBhbmQgYmFuYW5hcyIsDQogICJwaW5lYXBwbGVzIGFuZCBtYW5nb3MgYW5kIGd1YXZhcyINCikNCnN0cl9zcGxpdChmcnVpdHMsICIgYW5kICIpDQoNCg0KZ3BsIDwtIHJlYWRMaW5lcyhmaWxlLnBhdGgoUi5ob21lKCksICJDT1BZSU5HIikpDQp3b3JkcyA8LSB1bmxpc3Qoc3Ryc3BsaXQoZ3BsLCAiXFxXIikpICMgXFxXIOaEj+S4uumdnuWNleivjeWtl+espg0Kd29yZHMgPC0gd29yZHNbd29yZHMgIT0gIiJdICMg5Y675o6J56m65a2X56ymDQp0YWlsKHNvcnQodGFibGUodG9sb3dlcih3b3JkcykpKSwgMTApICMg6aKR5pWw5pyA5aSn55qEMTDkuKrljZXor40NCmBgYA0KDQojIyMgYHN0cl9zdWIoKWAg5a2X56ym5Liy5Y+W5a2Q6ZuGDQoNCj4gc3RyX3N1YihzdHJpbmcsIHN0YXJ0ID0gMUwsIGVuZCA9IC0xTCkNCg0KPiBzdHJfc3ViKHN0cmluZywgc3RhcnQgPSAxTCwgZW5kID0gLTFMKSBcPC0gdmFsdWUNCg0K5Lik5Liq5Y+C5pWw5YiG5Yir5Li65aeL5pyr5L2N572u77yM56ym5Y+35Luj6KGo5LuO5ZCO5b6A5YmN5pWw56ys5aSa5bCR5Liq5a2X56ymDQoNCmBgYHtyfQ0KdHh0IDwtICJJIGFtIENvbmFuLiINCg0KIyDliIYy5q615oiq5Y+W5a2X56ym5LiyDQpzdHJfc3ViKHR4dCwgYygxLCA0KSwgYyg2LCA4KSkNCg0KIyDpgJrov4fotJ/lnZDmoIfmiKrlj5blrZfnrKbkuLINCnN0cl9zdWIodHh0LCAtMykNCnN0cl9zdWIodHh0LCBlbmQgPSAtMykNCg0KIyDotYvlgLwNCnggPC0gYygiQXBwbGUiLCAiQmFuYW5hIiwgIlBlYXIiKQ0KIyBzdHJfc3ViKCnlr7l45ZCR6YeP5YyW5pON5L2c77ybc3RyX3RvX2xvd2Vy5YWo6YOo6L2s5o2i5Li65bCP5YaZ5a2X5q+NDQpzdHJfc3ViKHgsIDEsIDEpIDwtIHN0cl90b19sb3dlcihzdHJfc3ViKHgsIDEsIDEpKQ0KeA0KYGBgDQoNCiMjIyBgc3RyX3RvXyooKWAg5aSn5bCP5YaZ5Ye95pWw5pePDQoNCmBzdHJfdG9fdXBwZXIoc3RyaW5nLCBsb2NhbGUgPSAiIilgIOi9rOaNouS4uuWFqOWkp+WGmeWtl+avjQ0KDQpgc3RyX3RvX2xvd2VyKHN0cmluZywgbG9jYWxlID0gIiIpYCDovazmjaLkuLrlhajlsI/lhpnlrZfmr40NCg0KYHN0cl90b190aXRsZShzdHJpbmcsIGxvY2FsZSA9ICIiKWAg5Y2V6K+N6aaW5a2X5q+N5aSn5YaZDQoNCmBzdHJfdG9fc2VudGVuY2UoKWAg5YWo5Y+l6aaW5a2X5q+N5aSn5YaZDQoNCmxvY2FsZTrmjInlk6rnp43or63oqIDkuaDmg6/mjpLluo8NCg0KYGBge3J9DQpkb2cgPC0gIkxvb2sgdGhlIHF1aWNrIGJyb3duIGRvZyEiDQpzdHJfdG9fdXBwZXIoZG9nKQ0Kc3RyX3RvX2xvd2VyKGRvZykNCnN0cl90b190aXRsZShkb2cpDQpzdHJfdG9fc2VudGVuY2UoImxvb2sgdGhlIHF1aWNrIGJyb3duIGRvZyEiKQ0KYGBgDQoNCiMjIyBgc3RyX2NvdW50KClgIOWtl+espuS4suiuoeaVsA0KDQo+IHN0cl9jb3VudChzdHJpbmcsIHBhdHRlcm4gPSAiIikNCg0KYGBge3J9DQojIOazqOaEj+ato+WImeihqOi+vuW8j+eahOeJueauiuWtl+espg0Kc3RyX2NvdW50KGMoImEuIiwgIi4iLCAiLmEuIiwgTkEpLCAiLiIpDQoNCiMg55SoZml4ZWTljLnphY3lrZfnrKYNCnN0cl9jb3VudChjKCJhLiIsICIuIiwgIi5hLiIsIE5BKSwgZml4ZWQoIi4iKSkNCg0KIyDnlKhcXOWMuemFjeWtl+espg0Kc3RyX2NvdW50KGMoImEuIiwgIi4iLCAiLmEuIiwgTkEpLCAiXFwuIikNCmBgYA0KDQojIyMgYHN0cl9zb3J0KClgIOWtl+espuS4suaOkuW6jw0KDQo+IHN0cl9zb3J0KHgsIGRlY3JlYXNpbmcgPSBGQUxTRSwgbmFfbGFzdCA9IFRSVUUsIGxvY2FsZSA9ICIiLCAuLi4pIHN0cl9vcmRlcih4LCBkZWNyZWFzaW5nID0gRkFMU0UsIG5hX2xhc3QgPSBUUlVFLCBsb2NhbGUgPSAiIiwgLi4uKQ0KDQp4OiDlrZfnrKbkuLLvvIzlrZfnrKbkuLLlkJHph4/jgIIgZGVjcmVhc2luZzog5o6S5bqP5pa55ZCR44CCIG5hX2xhc3Q6IE5B5YC855qE5a2Y5pS+5L2N572u77yM5LiA5YWxM+S4quWAvO+8jFRSVUXmlL7liLDmnIDlkI7vvIxGQUxTReaUvuWIsOacgOWJje+8jE5B6L+H5ruk5aSE55CGIGxvY2FsZTog5oyJ5ZOq56eN6K+t6KiA5Lmg5oOv5o6S5bqPDQoNCmBgYHtyfQ0KIyDmjIlBU0NJSeWtl+avjeaOkuW6jw0Kc3RyX3NvcnQoYygiYSIsIDEsIDIsICIxMSIpLCBsb2NhbGUgPSAiZW4iKQ0KDQojIOWAkuW6j+aOkuW6jw0Kc3RyX3NvcnQobGV0dGVycywgZGVjcmVhc2luZyA9IFRSVUUpDQoNCiMg5oyJ5ou86Z+z5o6S5bqPDQpzdHJfc29ydChjKCLkvaAiLCAi5aW9IiwgIueyiSIsICLkuJ0iLCAi5pelIiwgIuW/lyIpLCBsb2NhbGUgPSAiemgiKQ0KYGBgDQoNCuWvuU5B55qE5o6S5bqP5aSE55CG77yaDQoNCmBgYHtyfQ0KIyDmiopOQeaUvuacgOWQjumdog0Kc3RyX3NvcnQoYyhOQSwgIjEiLCBOQSksIG5hX2xhc3QgPSBUUlVFKQ0KDQojIOaKik5B5pS+5pyA5YmN6Z2iDQpzdHJfc29ydChjKE5BLCAiMSIsIE5BKSwgbmFfbGFzdCA9IEZBTFNFKQ0KDQojIOWOu+aOiU5B5YC8DQpzdHJfc29ydChjKE5BLCAiMSIsIE5BKSwgbmFfbGFzdCA9IE5BKQ0KYGBgDQoNCiMjIyBgc3RyX2RldGVjdCgpYCDmo4DmtYvmmK/lkKbljLnphY3vvIzov5Tlm55UL0YNCg0KYGBge3J9DQp2YWwgPC0gYygiYWJjYTQiLCAxMjMsICJjYmEyIikNCg0KIyDmo4Dmn6XlrZfnrKbkuLLlkJHph4/vvIzmmK/lkKbljIXmi6xhDQpzdHJfZGV0ZWN0KHZhbCwgImEiKQ0KDQojIOajgOafpeWtl+espuS4suWQkemHj++8jOaYr+WQpuS7pWHkuLrlvIDlpLQNCnN0cl9kZXRlY3QodmFsLCAiXmEiKQ0KDQoNCiMg5qOA5p+l5a2X56ym5Liy5ZCR6YeP77yM5piv5ZCm5LulYeS4uue7k+Wwvg0Kc3RyX2RldGVjdCh2YWwsICJhJCIpDQpgYGANCg0KIyMjIGBzdHJfc3Vic2V0KClgIOi/lOWbnuaIkOWKn+WMuemFjeeahOWtl+espuS4sg0KDQpgYGB7cn0NCnZhbCA8LSBjKCJhYmMiLCAxMjMsICJjYmEiKQ0KDQojIOWFqOaWh+WMuemFjQ0Kc3RyX3N1YnNldCh2YWwsICJhIikNCg0KDQojIOW8gOWktOWMuemFjQ0Kc3RyX3N1YnNldCh2YWwsICJeYSIpDQoNCg0KIyDnu5PlsL7ljLnphY0NCnN0cl9zdWJzZXQodmFsLCAiYSQiKQ0KYGBgDQoNCiMjIyBgc3RyX21hdGNoKCkvc3RyX2V4dHJhY3QoKWAg5LuO5a2X56ym5Liy5Lit5o+Q5Y+W5Yy56YWN5qih5byPDQoNCuS4u+imgeS4juato+WImeihqOi+vuW8j+mFjeWQiOS9v+eUqA0KDQo+IHN0cl9tYXRjaChzdHJpbmcsIHBhdHRlcm4pIA0KPiBzdHJfbWF0Y2hfYWxsKHN0cmluZywgcGF0dGVybikgDQoNCuS7peWtl+espuS4sm1hdHJpeOagvOW8j+i/lOWbng0KDQpgYGB7cn0NCnZhbCA8LSBjKCJhYmMiLCAxMjMsICJjYmEiKQ0KDQojIOWMuemFjeWtl+espmHvvIzlubbov5Tlm57lr7nlupTnmoTlrZfnrKYNCnN0cl9tYXRjaCh2YWwsICJhIikNCg0KIyDljLnphY3lrZfnrKYwLTnvvIzpmZAx5Liq77yM5bm26L+U5Zue5a+55bqU55qE5a2X56ymDQpzdHJfbWF0Y2godmFsLCAiWzAtOV0iKQ0KDQojIOWMuemFjeWtl+espjAtOe+8jOS4jemZkOaVsOmHj++8jOW5tui/lOWbnuWvueW6lOeahOWtl+espg0Kc3RyX21hdGNoKHZhbCwgIlswLTldKiIpDQoNCiMg6L+U5ZuebWF0cml4DQpzdHJfbWF0Y2hfYWxsKHZhbCwgImEiKQ0Kc3RyX21hdGNoX2FsbCh2YWwsICJbMC05XSIpDQpgYGANCg0KPiBzdHJfZXh0cmFjdChzdHJpbmcsIHBhdHRlcm4pIA0KPiBzdHJfZXh0cmFjdF9hbGwoc3RyaW5nLCBwYXR0ZXJuLCBzaW1wbGlmeSA9IEZBTFNFKQ0KDQpzaW1wbGlmeTogVFJVRei/lOWbnm1hdHJpeO+8jEZBTFNF6L+U5Zue5YiX6KGoDQoNCmBgYHtyfQ0KdmFsIDwtIGMoImFiY2E0IiwgMTIzLCAiY2JhMiIpDQoNCiMg6L+U5Zue5Yy56YWN55qE5pWw5a2XDQpzdHJfZXh0cmFjdCh2YWwsICJcXGQiKQ0KDQojIOi/lOWbnuWMuemFjeeahOWtl+espg0Kc3RyX2V4dHJhY3QodmFsLCAiW2Etel0rIikNCg0KdmFsIDwtIGMoImFiY2E0IiwgMTIzLCAiY2JhMiIpDQpzdHJfZXh0cmFjdF9hbGwodmFsLCAiXFxkIikNCg0Kc3RyX2V4dHJhY3RfYWxsKHZhbCwgIlthLXpdKyIpDQpgYGANCg0KDQojIyMgYHdvcmQoKWAg5LuO5paH5pys5Lit5o+Q5Y+W5Y2V6K+N77yI5Lul5Y2V6K+N5Li65Y2V5L2N55yL5b6F5a2X56ym5Liy77yJDQoNCj4gd29yZChzdHJpbmcsIHN0YXJ0ID0gMUwsIGVuZCA9IHN0YXJ0LCBzZXAgPSBmaXhlZCgiICIpKQ0KDQpzdGFydDog5LuO56ys5Yeg5Liq6K+N5byA5aeL44CCIGVuZDog5Yiw56ys5Yeg5Liq6K+N57uT5p2f44CCIHNlcDog5YiG6K+N6Ze06ZqU5a2X56ym77yM6buY6K6k5Li656m65qC844CCDQoNCmBgYHtyfQ0KdmFsIDwtIGMoIkkgYW0gQ29uYW4uIiwgImh0dHA6Ly9mZW5zLm1lLCBvayIpDQoNCiMg6buY6K6k5Lul56m65qC85YiG5Ymy77yM5Y+W56ys5LiA5Liq5L2N572u55qE5a2X56ym5LiyDQp3b3JkKHZhbCwgMSkNCndvcmQodmFsLCAtMSkNCndvcmQodmFsLCAyLCAtMSkNCg0KIyDku6Us5YiG5Ymy77yM5Y+W56ys5LiA5Liq5L2N572u55qE5a2X56ym5LiyDQp2YWwgPC0gIjExMSwyMjIsMzMzLDQ0NCINCndvcmQodmFsLCAxLCBzZXAgPSBmaXhlZCgiLCIpKQ0Kd29yZCh2YWwsIDMsIHNlcCA9IGZpeGVkKCIsIikpDQpgYGANCg0KIyMjIGBzdHJfcmVwbGFjZSgpYCDlrZfnrKbkuLLmm7/mjaINCg0KPiBzdHJfcmVwbGFjZShzdHJpbmcsIHBhdHRlcm4sIHJlcGxhY2VtZW50KSAgDQo+IHN0cl9yZXBsYWNlX2FsbChzdHJpbmcsIHBhdHRlcm4sIHJlcGxhY2VtZW50KSAgDQo+IHN0cl9yZXBsYWNlX25hKHN0cmluZywgcmVwbGFjZW1lbnQgPSAiTkEiKSwg5bCGTkHvvIjpu5jorqTvvInmm7/mjaLkuLonTkEnDQoNCioq5Y+C5pWwIHJlcGxhY2VtZW50IOWPr+S7peaYr+S4gOS4quWHveaVsO+8jOS9v+abv+aNoueahOinhOWImeabtOWKoOWkjeadgioqDQoNCmBgYHtyfQ0KdmFsIDwtIGMoImFiYyIsIDEyMywgImNiYSIpDQoNCiMg5oqK55uu5qCH5a2X56ym5Liy56ys5LiA5Liq5Ye6546w55qEYeaIlmLvvIzmm7/mjaLkuLotDQpzdHJfcmVwbGFjZSh2YWwsICJbYWJdIiwgIi0iKQ0KDQojIOaKiuebruagh+Wtl+espuS4suaJgOacieWHuueOsOeahGHmiJZi77yM5pu/5o2i5Li6LQ0Kc3RyX3JlcGxhY2VfYWxsKHZhbCwgIlthYl0iLCAiLSIpDQoNCiMg5oqK55uu5qCH5a2X56ym5Liy5omA5pyJ5Ye6546w55qEYe+8jOabv+aNouS4uuiiq+i9rOS5ieeahOWtl+espg0Kc3RyX3JlcGxhY2VfYWxsKHZhbCwgIlthXSIsICJcXCIpDQoNCg0KDQpwaWdJdCA8LSBmdW5jdGlvbihzdHJpbmcpIHsNCiAgc3RyX3JlcGxhY2VfYWxsKHN0cmluZywgIlxcdysiLCBmdW5jdGlvbih3b3JkKSB7DQogICAgc3RyX2Moc3RyX3N1Yih3b3JkLCAyKSwgc3RyX3N1Yih3b3JkLCAxLCAxKSwgImF5IikNCiAgfSkNCn0NCg0KbGlicmFyeSh0ZXN0dGhhdCkNCnRlc3RfdGhhdCgiU2FtcGxlIFRlc3RzIiwgew0KICBleHBlY3RfZXF1YWwocGlnSXQoIlBpZyBsYXRpbiBpcyBjb29sIiksICJpZ1BheSBhdGlubGF5IHNpYXkgb29sY2F5IikNCiAgZXhwZWN0X2VxdWFsKHBpZ0l0KCJIZWxsbyB3b3JsZCAhIiksICJlbGxvSGF5IG9ybGR3YXkgISIpDQp9KQ0KYGBgDQoNCiMjIyBgc3RyX2xvY2F0ZSgpYCDmib7liLDmqKHlvI/lnKjlrZfnrKbkuLLkuK3nmoTlp4vmnKvkvY3nva4NCg0KPiBzdHJfbG9jYXRlKHN0cmluZywgcGF0dGVybikgc3RyX2xvY2F0ZV9hbGwoc3RyaW5nLCBwYXR0ZXJuKQ0KDQpgYGB7cn0NCnZhbCA8LSBjKCJhYmNhIiwgMTIzLCAiY2JhIikNCg0KIyDljLnphY1h5Zyo5a2X56ym5Liy5Lit55qE5L2N572uDQpzdHJfbG9jYXRlKHZhbCwgImEiKQ0KDQojIOeUqOWQkemHj+WMuemFjQ0Kc3RyX2xvY2F0ZSh2YWwsIGMoImEiLCAxMiwgImIiKSkNCg0KIyDku6XlrZfnrKbkuLJtYXRyaXjmoLzlvI/ov5Tlm54NCnN0cl9sb2NhdGVfYWxsKHZhbCwgImEiKQ0KDQojIOWMuemFjWHmiJZi5a2X56ym77yM5Lul5a2X56ym5LiybWF0cml45qC85byP6L+U5ZueDQpzdHJfbG9jYXRlX2FsbCh2YWwsICJbYWJdIikNCmBgYA0KDQojIyMgYHN0cl9jb252YCDlrZfnrKbnvJbnoIHovazmjaINCg0KPiBzdHJfY29udihzdHJpbmcsIGVuY29kaW5nKQ0KDQpgYGB7cn0NCiMg5oqK5Lit5paH5a2X56ym5a2X6IqC5YyWDQp4IDwtIGNoYXJUb1Jhdygi5L2g5aW9IikNCngNCg0KIyDpu5jorqR3aW7ns7vnu5/lrZfnrKbpm4bkuLpHQkvvvIxHQjIzMTLkuLpHQkvlrZfpm4bvvIzovaznoIHmraPluLgNCnN0cl9jb252KHgsICJHQksiKQ0Kc3RyX2NvbnYoeCwgIkdCMjMxMiIpDQoNCiMg6L2sVVRGLTjlpLHotKUNCnN0cl9jb252KHgsICJVVEYtOCIpDQoNCiMg5oqKdW5pY29kZei9rFVURi04DQpzdHJfY29udigiXHU1MzE3XHU0ZWFjIiwgIlVURi04IikNCmBgYA0KDQojIyMgYHN0cl9nbHVlKClgIOaooeadv+Wtl+espuS4sg0KDQpgYGB7cn0NCm5hbWUgPC0gIkZyZWQiDQphZ2UgPC0gNTANCmFubml2ZXJzYXJ5IDwtIGFzLkRhdGUoIjE5OTEtMTAtMTIiKQ0Kc3RyX2dsdWUoDQogICJNeSBuYW1lIGlzIHtuYW1lfSwgIiwNCiAgIm15IGFnZSBuZXh0IHllYXIgaXMge2FnZSArIDF9LCAiLA0KICAiYW5kIG15IGFubml2ZXJzYXJ5IGlzIHtmb3JtYXQoYW5uaXZlcnNhcnksICclQSwgJUIgJWQsICVZJyl9LiINCikNCnN0cl9nbHVlKCJNeSBuYW1lIGlzIHtuYW1lfSwgbm90IHt7bmFtZX19LiIpDQpgYGANCg0KDQoNCiMjIGJhc2U6Og0KDQojIyMgYGNoYXJ0cigpYCDlrZfnrKbmm7/mjaINCg0KYGNoYXJ0cihvbGQsIG5ldywgeClg77yM6Z2e5bi45aW955So55qE5Ye95pWw77yM55u45b2T5LqO5oyJ5LiA5Liq5ZOI5biM6KGo6L+b6KGM5a2X56ym6L2s5o2iDQoNCmBgYHtyIGNoYXJ0cn0NCmNoYXJ0cihvbGQgPSAiYSIsIG5ldyA9ICJjIiwgeCA9ICJhMTIzIikNCiMg5rOo5oSP77yMb2xk5ZKMbmV35Y+C5pWw5Y+v5Lul6YO95piv5a2X56ym5Liy77yMaW5kZXgg5a+55bqU55qE5a2X56ym6L+b6KGM5Lu75oSP5qyh5pWw55qE5pu/5o2iDQpjaGFydHIoIlR0IiwgIlV1IiwgIkF0R0N0dHRBQ0MiKQ0KYGBgDQoNCiMjIyDlrZfnrKbkuLLliIblibINCg0KYHN0cnNwbGl0KHgsIHNwbGl0LCBmaXhlZCA9IEZBTFNFKWANCg0KYGBge3Igc3Ryc3BsaXR9DQojIGZpeGVkPUYsIOesrDLkuKrlj4LmlbDkuLrmraPliJnooajovr7lvI/jgIInLiflj6/ljLnphY3pmaTigJxcbuKAneWSjCJcciLkuYvlpJbnmoTku7vkvZXljZXkuKrlrZfnrKbjgIINCnNwbGl0ZWQgPC0gdW5saXN0KHN0cnNwbGl0KCJhLmIuYyIsICIuIikpDQpzcGxpdGVkDQojIGZpeGVkPVTvvIznrKwy5Liq5Y+C5pWw5Li65paH5pys5a2X56ym5LiyDQpzcGxpdGVkIDwtIHVubGlzdChzdHJzcGxpdCgiYS5iLmMiLCAiLiIsIGZpeGVkID0gVCkpDQpzcGxpdGVkDQpgYGANCg0KIyMjIOWtl+espuS4suWMuemFjQ0KDQrlrZfnrKbkuLLljLnphY3lh73mlbAgZ3JlcChwYXR0ZXJuLCB4LCBmaXhlZD1GQUxTRSnlkoxncmVwbCgpDQoNCmBgYHtyIGdyZXB9DQpjc3RyIDwtIGMoImIiLCAiQSIsICJjIiwgImEuYSIpDQojIGZpeGVkPVTvvIznrKwx5Liq5Y+C5pWw5Li65paH5pys5a2X56ym5LiyDQpncmVwKCIuIiwgY3N0ciwgZml4ZWQgPSBUUlVFKQ0KIyBmaXhlZD1G77yM56ysMeS4quWPguaVsOS4uuato+WImeihqOi+vuW8jw0KZ3JlcCgiLiIsIGNzdHIsIGZpeGVkID0gRikNCiMgZ3JlcOS7hei/lOWbnuWMuemFjemhueeahOS4i+agh++8m+iAjGdyZXBs6L+U5Zue6YC76L6R5ZCR6YeP6KGo56S65pyJ5rKh5pyJ5om+5Yiw5Yy56YWNDQpncmVwbCgiLiIsIGNzdHIsIGZpeGVkID0gVFJVRSkNCiMg55So5LqO5o+Q5Y+W5a2Q6ZuG5piv5LiA5qC355qEDQpjc3RyW2dyZXAoIi4iLCBjc3RyLCBmaXhlZCA9IFRSVUUpXQ0KY3N0cltncmVwbCgiLiIsIGNzdHIsIGZpeGVkID0gVFJVRSldDQpgYGANCg0KIyMjIOWtl+espuS4suabv+aNog0KDQrlrZfnrKbkuLLmm7/mjaLlh73mlbAgc3ViKHBhdHRlcm4sIHJlcGxhY2VtZW50LCB4LCBmaXhlZD1GQUxTRSkNCg0KYGBge3Igc3VifQ0KIyBmaXhlZD1GLCDnrKwx5Liq5Y+C5pWw5Li65q2j5YiZ6KGo6L6+5byP77yMJ1xccyfooajnpLrnqbrnmb0NCnN1YigiXFxzIiwgIi4iLCAiSGVsbG8gVGhlcmUiKQ0KIyBmaXhlZD1ULCDnrKwx5Liq5Y+C5pWw5Li65paH5pys5a2X56ym5Liy77yM5ZyoJ0hlbGxvIFRoZXJlJ+S4reaJvuS4jeWIsCdcXHMnDQpzdWIoIlxccyIsICIuIiwgIkhlbGxvIFRoZXJlIiwgZml4ZWQgPSBUKQ0KIyBzdWIoKeS4jmdzdWIoKeeahOWMuuWIq++8jOabv+aNouS4gOasoeWSjOabv+aNouS7u+aEj+asoe+8iOWPquimgeWMuemFjeaIkOWKn++8iQ0Kc3ViKCJlIiwgIkUiLCAiSGVsbG8gVGhlcmUiLCBmaXhlZCA9IFQpDQpnc3ViKCJlIiwgIkUiLCAiSGVsbG8gVGhlcmUiLCBmaXhlZCA9IFQpDQpgYGA=