Paquetes necesarios
Empezamos por cargar a nuestro espacio de trabajo los paquetes que
usaremos:
- tm, específico para minería de textos.
- wordcloud, para graficar nubes de palabras.
- ggplot2, una gramática de gráficas que expande las funciones base de
R.
- dplyr, con funciones auxiliares para manipular y transformar datos.
En particular, el operador %>% permite escribir funciones más
legibles para seres humanos.
- readr, facilitará leer y escribir documentos.
library(tm)
Cargando paquete requerido: NLP
library(SnowballC)
library(wordcloud)
Cargando paquete requerido: RColorBrewer
library(ggplot2)
Adjuntando el paquete: ‘ggplot2’
The following object is masked from ‘package:NLP’:
annotate
library(dplyr)
Adjuntando el paquete: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(readr)
library(cluster)
Nuestro texto: Niebla
El texto con el que trabajeremos es el texto del libro Niebla de
Miguel de Unamuno que se puede descargar en Gutenberg.
Una vez hemos almacenado este documento a nuestro directorio de
trabajo, procedemos a leerlo usando la función read_lines de readr.
Nuestro interés está en el contenido de este libro y no en los avisos
legales de Gutenberg, prólogo y anotaciones, así que los omitiremos de
la lectura. Empezaremos a leer el documento desde la línea 420, que es
donde termina el prólogo, introducción e índice de Niebla, para ello nos
saltaremos (skip) 419 líneas previas. De manera complementaria,
detendremos la lectura en la línea 8313, que es donde inician los avisos
legales de Gutenberg, por lo tanto leeremos un máximo (nmax) de 8313-419
líneas.
Realizamos estas operaciones y asignamos el resultado al objeto
nov_raw.
nov_raw <- read_lines(file.choose(), skip = 419, n_max = 8313-419)
Preparación del texto
El objeto nov_raw que obtuvimos es uno de tipo character, con 7894
elementos.
str(nov_raw)
chr [1:7894] "NIEBLA" "" "" "" "" "I" "" "" ...
Cada uno de estos elementos corresponde a un renglón de Niebla y
tiene ancho máximo 70 caracteres, pues este es el ancho usado en los
textos electrónicos de Gutenberg. Esta es una cantidad muy pequeña de
texto para encontrar asociaciones entre palabras, por lo que necesitamos
crear elementos con una mayor cantidad de caracteres en cada uno.
Por lo tanto, crearemos elementos del tamaño aproximado a un
párrafo.
Creación de “párrafos”
Creamos un vector llamado diez con 10 repeticiones (rep) de los
números desde 1 hasta el número de renglones en el documento, dividido
entre 10 (length(nov_raw)/10.
Con esto, tendremos un vector con diez 1, luego diez 2, etc, hasta
llegar al número máxico de grupos de diez posibles en función del número
de renglones de nuestro documento.
Usaremos estos números para hacer grupos de diez renglones
consecutivos.
diez <- rep(1:ceiling(length(nov_raw)/10), each = 10)
De este vector, nos quedamos con un número de elementos igual al
número de renglones del objeto nov_raw (length(nov_raw)), para facilitar
combinarlos.
diez <- diez[1:length(nov_raw)]
Combinamos diez con now_raw y los asignamos al objeto nov_text. Así
tenemos una columna con los renglones de texto y otra con un número que
identifica a qué grupo de diez renglones pertenece.
Además, convertimos a data.frame para que las columnas estén
identificadas con un nombre, lo cual será útil en los siguientes
pasos.
nov_text <- cbind(diez, nov_raw) %>% data.frame()
Como sólo necesitamos la columna con los ahora párrafos de texto, con
eso nos quedamos. Aprovechamos para transformar nov_text en una matrix,
pues esto nos facilitará los pasos siguientes.
nov_text <- nov_text %>% select(nov_raw) %>% as.matrix
dim(nov_text)
[1] 7894 1
Limpieza del texto
Necesitamos limpiar el texto de caracteres que son de poca utilidad
en la mineria de textos.
Empezamos por aseguramos de que no queden caracteres especiales de la
codificación, como saltos de línea y tabulaciones, con un poco de ayuda
de Regular Expressions.
nov_text <- gsub("[[:cntrl:]]", " ", nov_text)
Convertimos todo a minúsculas.
nov_text <- tolower(nov_text)
Usamos removeWords con stopwords(“spanish”) para eliminar palabras
vacias, es decir, aquellas con poco valor para el análisis, tales como
algunas preposiciones y muletillas.
nov_text <- removeWords(nov_text, words = stopwords("spanish"))
Nos deshacemos de la puntuación, puesto que fin y fin. son
identificadas como palabras diferentes, lo cual no deseamos.
nov_text <- removePunctuation(nov_text)
En este caso, removemos los números, pues en Niebla no hay fechas y
otras cantidades que deseemos conservar.
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxubm92X3RleHQgPC0gcmVtb3ZlTnVtYmVycyhub3ZfdGV4dClcblxuYGBgIn0= -->
```r
nov_text <- removeNumbers(nov_text)
```
<!-- rnb-source-end -->
```r
nov_text <- removeNumbers(nov_text)
<!-- rnb-source-end -->
<!-- rnb-output-end -->
<!-- rnb-chunk-end -->
<!-- rnb-text-begin -->
Por último eliminamos los espacios vacios excesivos, muchos de ellos introducidos por las transformaciones anteriores.
<!-- rnb-text-end -->
<!-- rnb-chunk-begin -->
<!-- rnb-output-begin eyJkYXRhIjoiXG48IS0tIHJuYi1zb3VyY2UtYmVnaW4gZXlKa1lYUmhJam9pWUdCZ2NseHVibTkyWDNSbGVIUWdQQzBnYzNSeWFYQlhhR2wwWlhOd1lXTmxLRzV2ZGw5MFpYaDBLVnh1WEc1Z1lHQWlmUT09IC0tPlxuXG5gYGByXG5ub3ZfdGV4dCA8LSBzdHJpcFdoaXRlc3BhY2Uobm92X3RleHQpXG5cbmBgYFxuXG48IS0tIHJuYi1zb3VyY2UtZW5kIC0tPlxuIn0= -->
<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxubm92X3RleHQgPC0gc3RyaXBXaGl0ZXNwYWNlKG5vdl90ZXh0KVxuXG5gYGAifQ== -->
```r
nov_text <- stripWhitespace(nov_text)
Análisis del Corpus
Con nuestro documento preparado, procedemos a crear nuestro Corpus,
es decir, esto es nuestro acervo de documentos a analizar.
En nuestro caso, nuestro Corpus se compone de todos los parrafos del
libro Niebla y los asignaremos al objeto nov_corpus usando las funciones
VectorSource y Corpus.
nov_corpus <- Corpus(VectorSource(nov_text))
nov_corpus
<<SimpleCorpus>>
Metadata: corpus specific: 1, document level (indexed): 0
Content: documents: 7894
Como podemos ver, nustro Copus está compuesto por 7894 documentos.
Los siguientes análisis se harán a partir de este Corpus.
# Crear la matriz TDM (Term Document Matrix)
nov_tdm <- TermDocumentMatrix(nov_corpus, control = list(
wordLengths = c(1, Inf),
removeNumbers = TRUE,
removePunctuation = TRUE,
stopwords = stopwords("spanish")
))
Nube de palabras
# Obtener las palabras y frecuencias
word_freqs <- sort(rowSums(as.matrix(nov_tdm)), decreasing = TRUE)
palabras <- names(word_freqs)
frecuencias <- word_freqs
# Crear la nube de palabras
wordcloud(palabras, frecuencias, max.words = 80, random.order = F, colors = brewer.pal(name = "Dark2", n = 8))

Podemos observar que en nuestro Corpus aun se encuentran palabras de
poco interés para el análisis, tales como “usted”, “tal” y “sino”, lo
cual nos indica que debemos realizar una segunda limpieza de nuestros
textos.
Más depuración
Como observamos en las nubes de palabras que generamos, aún tenemos
palabras que aparecen con mucha frecuencia en nuestro texto que en
realidad no son de mucha utilidad para el análisis.
Usaremos la función removeWords indicando en el argumento words que
palabras deseamos eliminar de nuestro Corpus.
Esta función requiere de un vector de caracteres como argumento
principal, así que para esta operación usaremos nuestro objeto
nov_text.
Una vez hecho esto, usaremos nuestra nueva versión de nov_text para
generar nuestro Corpus y para mapearlo como documento de texto
plano.
nov_text <- removeWords(nov_text, words = c("usted", "pues", "tal", "tan", "así", "dijo", "cómo", "sino", "entonces", "aunque", "don", "doña"))
# Crear un nuevo corpus con las palabras eliminadas
nov_corpus <- Corpus(VectorSource(nov_text))
# Generar la matriz TDM
nov_tdm <- TermDocumentMatrix(nov_corpus, control = list(
wordLengths = c(1, Inf),
removeNumbers = TRUE,
removePunctuation = TRUE,
stopwords = stopwords("spanish")
))
Generamos un nube de palabras nueva, en la cual es posible ver una
diferencia significativa en su composición.
# Obtener las palabras y frecuencias
word_freqs <- sort(rowSums(as.matrix(nov_tdm)), decreasing = TRUE)
palabras <- names(word_freqs)
frecuencias <- word_freqs
# Crear la nube de palabras
wordcloud(palabras, frecuencias, max.words = 80, random.order = F, colors = brewer.pal(name = "Dark2", n = 8))

Una última depuración..
# Eliminar las palabras adicionales
nov_text <- removeWords(nov_text, words = c("usted", "pues", "tal", "tan", "así", "dijo", "cómo", "sino", "entonces", "aunque", "don", "doña"))
# Eliminar caracteres "-" "?", "¿"
nov_text <- gsub("[\\-\\?\\¿\\¡\\—¡\\«\\»]", " ", nov_text)
# Crear un nuevo corpus con las palabras eliminadas
nov_corpus <- Corpus(VectorSource(nov_text))
# Generar la matriz TDM
nov_tdm <- TermDocumentMatrix(nov_corpus, control = list(
wordLengths = c(1, Inf),
removeNumbers = TRUE,
removePunctuation = TRUE,
stopwords = stopwords("spanish")
))
# Obtener las palabras y frecuencias
word_freqs <- sort(rowSums(as.matrix(nov_tdm)), decreasing = TRUE)
palabras <- names(word_freqs)
frecuencias <- word_freqs
# Crear la nube de palabras
wordcloud(palabras, frecuencias, max.words = 80, random.order = F, colors = brewer.pal(name = "Dark2", n = 8))

Otra forma de graficarlo
library(wordcloud2)
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
##webshot::install_phantomjs()
d <- data.frame(word = palabras,freq=frecuencias)
wordcloud2(d,shape = "diamond",size = 0.4)
Análisis de sentimientos
library(tidytext)
library(textdata)
library(tidyr)
library(reshape2)
Adjuntando el paquete: ‘reshape2’
The following object is masked from ‘package:tidyr’:
smiths
El paquete tidytext contiene 3 diccionarios distintos:
- affin: asigna a cada palabra un valor entre -5 y 5. Siendo -5 el
máximo de negatividad y +5 el máximo de positividad.
- bing: clasifica las palabras de forma binaria
positivo/negativo.
- nrc: clasifica cada palabra en uno o más de los siguientes
sentimientos: positive, negative, anger, anticipation, disgust, fear,
joy, sadness, surprise, and trust.
nrc<-get_sentiments("nrc")
Base_ana<-d %>% inner_join(nrc,"word") # Unimos el data de sentiemientos con nuestras palabras para que las clasifique según concordancia
Base_ana<-arrange(Base_ana,-freq) #ordenamos de forma descendente por frecuencia
Nube de palabras por sentimiento
b5<-data.frame(Base_ana %>% group_by(word,sentiment) %>% summarise(n=n())) %>% arrange(-n)
`summarise()` has grouped output by 'word'. You can override using the `.groups` argument.
b5 %>% acast(word~sentiment,fill = 0,value.var = "n") %>% #llenar los missing values con cero
comparison.cloud(max.words = 500, #Maximo de palabras a representar
colors = brewer.pal(n = 10,"Paired"), #Cantidad de colores diferentes equivalente a cantidad de sentimientos
title.size = 1,title.colors = "black", #Color de titulos de sentimientos
title.bg.colors = "white") #Resaltado de titulos

Top 10 por sentimiento
Base_ana %>% group_by(sentiment) %>% top_n(10, freq) %>% # Las 10 palabras con mayor frecuencia clasificadas por su respectivo sentimiento
ungroup() %>%
mutate(word=reorder(word,freq)) %>%
ggplot(aes(x=word,y=freq,fill=sentiment))+
geom_col()+ # grafico de columnas
facet_wrap(facets = ~sentiment,nrow = 3,scales = "free")+ #scales="free" para cada grafico de barra me ponga su propio eje x, es decir sus propias palabras
coord_flip() + # De horizontal pasa a vertical
theme_light() + theme(axis.text.y = element_text(size = 7),
axis.text.x = element_text(size = 5)) + theme_bw()

Sentimientos predominantes
Base_ana %>% group_by(sentiment) %>% summarise(n=n()) %>%
ungroup() %>% mutate(sentiment=reorder(sentiment,-n)) %>%
ggplot(aes(x=sentiment,y=n)) + geom_col(aes(fill=sentiment)) +
theme_classic() + labs(title="Cantidad de sentimientos encontrados",
y="Frecuencia",fill="Sentimiento",x="Sentimiento") +
theme(plot.title = element_text(face="bold",hjust = 0.5),
legend.title = element_text(face="bold"))

LS0tDQp0aXRsZTogIk1pbmVyw61hIGRlIFRleHRvIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KLS0tDQoNCiMgUGFxdWV0ZXMgbmVjZXNhcmlvcw0KRW1wZXphbW9zIHBvciBjYXJnYXIgYSBudWVzdHJvIGVzcGFjaW8gZGUgdHJhYmFqbyBsb3MgcGFxdWV0ZXMgcXVlIHVzYXJlbW9zOg0KDQotIHRtLCBlc3BlY8OtZmljbyBwYXJhIG1pbmVyw61hIGRlIHRleHRvcy4NCi0gd29yZGNsb3VkLCBwYXJhIGdyYWZpY2FyIG51YmVzIGRlIHBhbGFicmFzLg0KLSBnZ3Bsb3QyLCB1bmEgZ3JhbcOhdGljYSBkZSBncsOhZmljYXMgcXVlIGV4cGFuZGUgbGFzIGZ1bmNpb25lcyBiYXNlIGRlIFIuDQotIGRwbHlyLCBjb24gZnVuY2lvbmVzIGF1eGlsaWFyZXMgcGFyYSBtYW5pcHVsYXIgeSB0cmFuc2Zvcm1hciBkYXRvcy4gRW4gcGFydGljdWxhciwgZWwgb3BlcmFkb3IgJT4lIHBlcm1pdGUgZXNjcmliaXIgZnVuY2lvbmVzIG3DoXMgbGVnaWJsZXMgcGFyYSBzZXJlcyBodW1hbm9zLg0KLSByZWFkciwgZmFjaWxpdGFyw6EgbGVlciB5IGVzY3JpYmlyIGRvY3VtZW50b3MuDQoNCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodG0pDQpsaWJyYXJ5KFNub3diYWxsQykNCmxpYnJhcnkod29yZGNsb3VkKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGNsdXN0ZXIpDQpgYGANCg0KIyBOdWVzdHJvIHRleHRvOiBOaWVibGENCkVsIHRleHRvIGNvbiBlbCBxdWUgdHJhYmFqZXJlbW9zIGVzIGVsIHRleHRvIGRlbCBsaWJybyBOaWVibGEgZGUgTWlndWVsIGRlIFVuYW11bm8gcXVlIHNlIHB1ZWRlIGRlc2NhcmdhciBlbiBHdXRlbmJlcmcuDQoNClVuYSB2ZXogaGVtb3MgYWxtYWNlbmFkbyBlc3RlIGRvY3VtZW50byBhIG51ZXN0cm8gZGlyZWN0b3JpbyBkZSB0cmFiYWpvLCBwcm9jZWRlbW9zIGEgbGVlcmxvIHVzYW5kbyBsYSBmdW5jacOzbiByZWFkX2xpbmVzIGRlIHJlYWRyLg0KDQpOdWVzdHJvIGludGVyw6lzIGVzdMOhIGVuIGVsIGNvbnRlbmlkbyBkZSBlc3RlIGxpYnJvIHkgbm8gZW4gbG9zIGF2aXNvcyBsZWdhbGVzIGRlIEd1dGVuYmVyZywgcHLDs2xvZ28geSBhbm90YWNpb25lcywgYXPDrSBxdWUgbG9zIG9taXRpcmVtb3MgZGUgbGEgbGVjdHVyYS4gRW1wZXphcmVtb3MgYSBsZWVyIGVsIGRvY3VtZW50byBkZXNkZSBsYSBsw61uZWEgNDIwLCBxdWUgZXMgZG9uZGUgdGVybWluYSBlbCBwcsOzbG9nbywgaW50cm9kdWNjacOzbiBlIMOtbmRpY2UgZGUgTmllYmxhLCBwYXJhIGVsbG8gbm9zIHNhbHRhcmVtb3MgKHNraXApIDQxOSBsw61uZWFzIHByZXZpYXMuIERlIG1hbmVyYSBjb21wbGVtZW50YXJpYSwgZGV0ZW5kcmVtb3MgbGEgbGVjdHVyYSBlbiBsYSBsw61uZWEgODMxMywgcXVlIGVzIGRvbmRlIGluaWNpYW4gbG9zIGF2aXNvcyBsZWdhbGVzIGRlIEd1dGVuYmVyZywgcG9yIGxvIHRhbnRvIGxlZXJlbW9zIHVuIG3DoXhpbW8gKG5tYXgpIGRlIDgzMTMtNDE5IGzDrW5lYXMuDQoNClJlYWxpemFtb3MgZXN0YXMgb3BlcmFjaW9uZXMgeSBhc2lnbmFtb3MgZWwgcmVzdWx0YWRvIGFsIG9iamV0byBub3ZfcmF3Lg0KDQpgYGB7cn0NCm5vdl9yYXcgPC0gcmVhZF9saW5lcyhmaWxlLmNob29zZSgpLCBza2lwID0gNDE5LCBuX21heCA9IDgzMTMtNDE5KQ0KYGBgDQoNCiMgUHJlcGFyYWNpw7NuIGRlbCB0ZXh0bw0KRWwgb2JqZXRvIG5vdl9yYXcgcXVlIG9idHV2aW1vcyBlcyB1bm8gZGUgdGlwbyBjaGFyYWN0ZXIsIGNvbiA3ODk0IGVsZW1lbnRvcy4NCg0KYGBge3J9DQpzdHIobm92X3JhdykNCmBgYA0KDQpDYWRhIHVubyBkZSBlc3RvcyBlbGVtZW50b3MgY29ycmVzcG9uZGUgYSB1biByZW5nbMOzbiBkZSBOaWVibGEgeSB0aWVuZSBhbmNobyBtw6F4aW1vIDcwIGNhcmFjdGVyZXMsIHB1ZXMgZXN0ZSBlcyBlbCBhbmNobyB1c2FkbyBlbiBsb3MgdGV4dG9zIGVsZWN0csOzbmljb3MgZGUgR3V0ZW5iZXJnLiBFc3RhIGVzIHVuYSBjYW50aWRhZCBtdXkgcGVxdWXDsWEgZGUgdGV4dG8gcGFyYSBlbmNvbnRyYXIgYXNvY2lhY2lvbmVzIGVudHJlIHBhbGFicmFzLCBwb3IgbG8gcXVlIG5lY2VzaXRhbW9zIGNyZWFyIGVsZW1lbnRvcyBjb24gdW5hIG1heW9yIGNhbnRpZGFkIGRlIGNhcmFjdGVyZXMgZW4gY2FkYSB1bm8uDQoNClBvciBsbyB0YW50bywgY3JlYXJlbW9zIGVsZW1lbnRvcyBkZWwgdGFtYcOxbyBhcHJveGltYWRvIGEgdW4gcMOhcnJhZm8uDQoNCiMgQ3JlYWNpw7NuIGRlIOKAnHDDoXJyYWZvc+KAnQ0KQ3JlYW1vcyB1biB2ZWN0b3IgbGxhbWFkbyBkaWV6IGNvbiAxMCByZXBldGljaW9uZXMgKHJlcCkgZGUgbG9zIG7Dum1lcm9zIGRlc2RlIDEgaGFzdGEgZWwgbsO6bWVybyBkZSByZW5nbG9uZXMgZW4gZWwgZG9jdW1lbnRvLCBkaXZpZGlkbyBlbnRyZSAxMCAobGVuZ3RoKG5vdl9yYXcpLzEwLg0KDQpDb24gZXN0bywgdGVuZHJlbW9zIHVuIHZlY3RvciBjb24gZGlleiAxLCBsdWVnbyBkaWV6IDIsIGV0YywgaGFzdGEgbGxlZ2FyIGFsIG7Dum1lcm8gbcOheGljbyBkZSBncnVwb3MgZGUgZGlleiBwb3NpYmxlcyBlbiBmdW5jacOzbiBkZWwgbsO6bWVybyBkZSByZW5nbG9uZXMgZGUgbnVlc3RybyBkb2N1bWVudG8uDQoNClVzYXJlbW9zIGVzdG9zIG7Dum1lcm9zIHBhcmEgaGFjZXIgZ3J1cG9zIGRlIGRpZXogcmVuZ2xvbmVzIGNvbnNlY3V0aXZvcy4NCg0KYGBge3J9DQpkaWV6IDwtIHJlcCgxOmNlaWxpbmcobGVuZ3RoKG5vdl9yYXcpLzEwKSwgZWFjaCA9IDEwKQ0KYGBgDQoNCkRlIGVzdGUgdmVjdG9yLCBub3MgcXVlZGFtb3MgY29uIHVuIG7Dum1lcm8gZGUgZWxlbWVudG9zIGlndWFsIGFsIG7Dum1lcm8gZGUgcmVuZ2xvbmVzIGRlbCBvYmpldG8gbm92X3JhdyAobGVuZ3RoKG5vdl9yYXcpKSwgcGFyYSBmYWNpbGl0YXIgY29tYmluYXJsb3MuDQoNCmBgYHtyfQ0KZGlleiA8LSBkaWV6WzE6bGVuZ3RoKG5vdl9yYXcpXQ0KYGBgDQoNCkNvbWJpbmFtb3MgZGlleiBjb24gbm93X3JhdyB5IGxvcyBhc2lnbmFtb3MgYWwgb2JqZXRvIG5vdl90ZXh0LiBBc8OtIHRlbmVtb3MgdW5hIGNvbHVtbmEgY29uIGxvcyByZW5nbG9uZXMgZGUgdGV4dG8geSBvdHJhIGNvbiB1biBuw7ptZXJvIHF1ZSBpZGVudGlmaWNhIGEgcXXDqSBncnVwbyBkZSBkaWV6IHJlbmdsb25lcyBwZXJ0ZW5lY2UuDQoNCkFkZW3DoXMsIGNvbnZlcnRpbW9zIGEgZGF0YS5mcmFtZSBwYXJhIHF1ZSBsYXMgY29sdW1uYXMgZXN0w6luIGlkZW50aWZpY2FkYXMgY29uIHVuIG5vbWJyZSwgbG8gY3VhbCBzZXLDoSDDunRpbCBlbiBsb3Mgc2lndWllbnRlcyBwYXNvcy4NCg0KYGBge3J9DQpub3ZfdGV4dCA8LSBjYmluZChkaWV6LCBub3ZfcmF3KSAlPiUgZGF0YS5mcmFtZSgpDQpgYGANCg0KDQpDb21vIHPDs2xvIG5lY2VzaXRhbW9zIGxhIGNvbHVtbmEgY29uIGxvcyBhaG9yYSBww6FycmFmb3MgZGUgdGV4dG8sIGNvbiBlc28gbm9zIHF1ZWRhbW9zLiBBcHJvdmVjaGFtb3MgcGFyYSB0cmFuc2Zvcm1hciBub3ZfdGV4dCBlbiB1bmEgbWF0cml4LCBwdWVzIGVzdG8gbm9zIGZhY2lsaXRhcsOhIGxvcyBwYXNvcyBzaWd1aWVudGVzLg0KDQpgYGB7cn0NCm5vdl90ZXh0IDwtIG5vdl90ZXh0ICU+JSBzZWxlY3Qobm92X3JhdykgJT4lIGFzLm1hdHJpeA0KDQpkaW0obm92X3RleHQpDQpgYGANCg0KIyBMaW1waWV6YSBkZWwgdGV4dG8NCk5lY2VzaXRhbW9zIGxpbXBpYXIgZWwgdGV4dG8gZGUgY2FyYWN0ZXJlcyBxdWUgc29uIGRlIHBvY2EgdXRpbGlkYWQgZW4gbGEgbWluZXJpYSBkZSB0ZXh0b3MuDQoNCkVtcGV6YW1vcyBwb3IgYXNlZ3VyYW1vcyBkZSBxdWUgbm8gcXVlZGVuIGNhcmFjdGVyZXMgZXNwZWNpYWxlcyBkZSBsYSBjb2RpZmljYWNpw7NuLCBjb21vIHNhbHRvcyBkZSBsw61uZWEgeSB0YWJ1bGFjaW9uZXMsIGNvbiB1biBwb2NvIGRlIGF5dWRhIGRlIFJlZ3VsYXIgRXhwcmVzc2lvbnMuDQoNCmBgYHtyfQ0Kbm92X3RleHQgPC0gZ3N1YigiW1s6Y250cmw6XV0iLCAiICIsIG5vdl90ZXh0KQ0KYGBgDQoNCkNvbnZlcnRpbW9zIHRvZG8gYSBtaW7DunNjdWxhcy4NCg0KYGBge3J9DQpub3ZfdGV4dCA8LSB0b2xvd2VyKG5vdl90ZXh0KQ0KYGBgDQoNClVzYW1vcyByZW1vdmVXb3JkcyBjb24gc3RvcHdvcmRzKCJzcGFuaXNoIikgcGFyYSBlbGltaW5hciBwYWxhYnJhcyB2YWNpYXMsIGVzIGRlY2lyLCBhcXVlbGxhcyBjb24gcG9jbyB2YWxvciBwYXJhIGVsIGFuw6FsaXNpcywgdGFsZXMgY29tbyBhbGd1bmFzIHByZXBvc2ljaW9uZXMgeSBtdWxldGlsbGFzLg0KDQpgYGB7cn0NCm5vdl90ZXh0IDwtIHJlbW92ZVdvcmRzKG5vdl90ZXh0LCB3b3JkcyA9IHN0b3B3b3Jkcygic3BhbmlzaCIpKQ0KYGBgDQoNCk5vcyBkZXNoYWNlbW9zIGRlIGxhIHB1bnR1YWNpw7NuLCBwdWVzdG8gcXVlIGZpbiB5IGZpbi4gc29uIGlkZW50aWZpY2FkYXMgY29tbyBwYWxhYnJhcyBkaWZlcmVudGVzLCBsbyBjdWFsIG5vIGRlc2VhbW9zLg0KDQpgYGB7cn0NCm5vdl90ZXh0IDwtIHJlbW92ZVB1bmN0dWF0aW9uKG5vdl90ZXh0KQ0KYGBgDQoNCkVuIGVzdGUgY2FzbywgcmVtb3ZlbW9zIGxvcyBuw7ptZXJvcywgcHVlcyBlbiBOaWVibGEgbm8gaGF5IGZlY2hhcyB5IG90cmFzIGNhbnRpZGFkZXMgcXVlIGRlc2VlbW9zIGNvbnNlcnZhci4NCg0KYGBge3J9DQpub3ZfdGV4dCA8LSByZW1vdmVOdW1iZXJzKG5vdl90ZXh0KQ0KDQpgYGANCg0KUG9yIMO6bHRpbW8gZWxpbWluYW1vcyBsb3MgZXNwYWNpb3MgdmFjaW9zIGV4Y2VzaXZvcywgbXVjaG9zIGRlIGVsbG9zIGludHJvZHVjaWRvcyBwb3IgbGFzIHRyYW5zZm9ybWFjaW9uZXMgYW50ZXJpb3Jlcy4NCg0KYGBge3J9DQpub3ZfdGV4dCA8LSBzdHJpcFdoaXRlc3BhY2Uobm92X3RleHQpDQoNCmBgYA0KDQojIEFuw6FsaXNpcyBkZWwgQ29ycHVzDQpDb24gbnVlc3RybyBkb2N1bWVudG8gcHJlcGFyYWRvLCBwcm9jZWRlbW9zIGEgY3JlYXIgbnVlc3RybyBDb3JwdXMsIGVzIGRlY2lyLCBlc3RvIGVzIG51ZXN0cm8gYWNlcnZvIGRlIGRvY3VtZW50b3MgYSBhbmFsaXphci4NCg0KRW4gbnVlc3RybyBjYXNvLCBudWVzdHJvIENvcnB1cyBzZSBjb21wb25lIGRlIHRvZG9zIGxvcyBwYXJyYWZvcyBkZWwgbGlicm8gTmllYmxhIHkgbG9zIGFzaWduYXJlbW9zIGFsIG9iamV0byBub3ZfY29ycHVzIHVzYW5kbyBsYXMgZnVuY2lvbmVzIFZlY3RvclNvdXJjZSB5IENvcnB1cy4NCg0KYGBge3J9DQpub3ZfY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2Uobm92X3RleHQpKQ0KDQpub3ZfY29ycHVzDQpgYGANCg0KQ29tbyBwb2RlbW9zIHZlciwgbnVzdHJvIENvcHVzIGVzdMOhIGNvbXB1ZXN0byBwb3IgNzg5NCBkb2N1bWVudG9zLiBMb3Mgc2lndWllbnRlcyBhbsOhbGlzaXMgc2UgaGFyw6FuIGEgcGFydGlyIGRlIGVzdGUgQ29ycHVzLg0KDQpgYGB7cn0NCiMgQ3JlYXIgbGEgbWF0cml6IFRETSAoVGVybSBEb2N1bWVudCBNYXRyaXgpDQpub3ZfdGRtIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChub3ZfY29ycHVzLCBjb250cm9sID0gbGlzdCgNCiAgd29yZExlbmd0aHMgPSBjKDEsIEluZiksDQogIHJlbW92ZU51bWJlcnMgPSBUUlVFLA0KICByZW1vdmVQdW5jdHVhdGlvbiA9IFRSVUUsDQogIHN0b3B3b3JkcyA9IHN0b3B3b3Jkcygic3BhbmlzaCIpDQopKQ0KYGBgDQoNCg0KIyBOdWJlIGRlIHBhbGFicmFzDQoNCmBgYHtyfQ0KIyBPYnRlbmVyIGxhcyBwYWxhYnJhcyB5IGZyZWN1ZW5jaWFzDQp3b3JkX2ZyZXFzIDwtIHNvcnQocm93U3Vtcyhhcy5tYXRyaXgobm92X3RkbSkpLCBkZWNyZWFzaW5nID0gVFJVRSkNCnBhbGFicmFzIDwtIG5hbWVzKHdvcmRfZnJlcXMpDQpmcmVjdWVuY2lhcyA8LSB3b3JkX2ZyZXFzDQoNCiMgQ3JlYXIgbGEgbnViZSBkZSBwYWxhYnJhcw0Kd29yZGNsb3VkKHBhbGFicmFzLCBmcmVjdWVuY2lhcywgbWF4LndvcmRzID0gODAsIHJhbmRvbS5vcmRlciA9IEYsIGNvbG9ycyA9IGJyZXdlci5wYWwobmFtZSA9ICJEYXJrMiIsIG4gPSA4KSkNCmBgYA0KUG9kZW1vcyBvYnNlcnZhciBxdWUgZW4gbnVlc3RybyBDb3JwdXMgYXVuIHNlIGVuY3VlbnRyYW4gcGFsYWJyYXMgZGUgcG9jbyBpbnRlcsOpcyBwYXJhIGVsIGFuw6FsaXNpcywgdGFsZXMgY29tbyDigJx1c3RlZOKAnSwg4oCcdGFs4oCdIHkg4oCcc2lub+KAnSwgbG8gY3VhbCBub3MgaW5kaWNhIHF1ZSBkZWJlbW9zIHJlYWxpemFyIHVuYSBzZWd1bmRhIGxpbXBpZXphIGRlIG51ZXN0cm9zIHRleHRvcy4NCg0KDQojIE3DoXMgZGVwdXJhY2nDs24NCkNvbW8gb2JzZXJ2YW1vcyBlbiBsYXMgbnViZXMgZGUgcGFsYWJyYXMgcXVlIGdlbmVyYW1vcywgYcO6biB0ZW5lbW9zIHBhbGFicmFzIHF1ZSBhcGFyZWNlbiBjb24gbXVjaGEgZnJlY3VlbmNpYSBlbiBudWVzdHJvIHRleHRvIHF1ZSBlbiByZWFsaWRhZCBubyBzb24gZGUgbXVjaGEgdXRpbGlkYWQgcGFyYSBlbCBhbsOhbGlzaXMuDQoNClVzYXJlbW9zIGxhIGZ1bmNpw7NuIHJlbW92ZVdvcmRzIGluZGljYW5kbyBlbiBlbCBhcmd1bWVudG8gd29yZHMgcXVlIHBhbGFicmFzIGRlc2VhbW9zIGVsaW1pbmFyIGRlIG51ZXN0cm8gQ29ycHVzLg0KDQpFc3RhIGZ1bmNpw7NuIHJlcXVpZXJlIGRlIHVuIHZlY3RvciBkZSBjYXJhY3RlcmVzIGNvbW8gYXJndW1lbnRvIHByaW5jaXBhbCwgYXPDrSBxdWUgcGFyYSBlc3RhIG9wZXJhY2nDs24gdXNhcmVtb3MgbnVlc3RybyBvYmpldG8gbm92X3RleHQuDQoNClVuYSB2ZXogaGVjaG8gZXN0bywgdXNhcmVtb3MgbnVlc3RyYSBudWV2YSB2ZXJzacOzbiBkZSBub3ZfdGV4dCBwYXJhIGdlbmVyYXIgbnVlc3RybyBDb3JwdXMgeSBwYXJhIG1hcGVhcmxvIGNvbW8gZG9jdW1lbnRvIGRlIHRleHRvIHBsYW5vLg0KDQoNCmBgYHtyfQ0Kbm92X3RleHQgPC0gcmVtb3ZlV29yZHMobm92X3RleHQsIHdvcmRzID0gYygidXN0ZWQiLCAicHVlcyIsICJ0YWwiLCAidGFuIiwgImFzw60iLCAiZGlqbyIsICJjw7NtbyIsICJzaW5vIiwgImVudG9uY2VzIiwgImF1bnF1ZSIsICJkb24iLCAiZG/DsWEiKSkNCg0KIyBDcmVhciB1biBudWV2byBjb3JwdXMgY29uIGxhcyBwYWxhYnJhcyBlbGltaW5hZGFzDQpub3ZfY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2Uobm92X3RleHQpKQ0KDQojIEdlbmVyYXIgbGEgbWF0cml6IFRETQ0Kbm92X3RkbSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgobm92X2NvcnB1cywgY29udHJvbCA9IGxpc3QoDQogICAgd29yZExlbmd0aHMgPSBjKDEsIEluZiksDQogICAgcmVtb3ZlTnVtYmVycyA9IFRSVUUsDQogICAgcmVtb3ZlUHVuY3R1YXRpb24gPSBUUlVFLA0KICAgIHN0b3B3b3JkcyA9IHN0b3B3b3Jkcygic3BhbmlzaCIpDQopKQ0KYGBgDQpHZW5lcmFtb3MgdW4gbnViZSBkZSBwYWxhYnJhcyBudWV2YSwgZW4gbGEgY3VhbCBlcyBwb3NpYmxlIHZlciB1bmEgZGlmZXJlbmNpYSBzaWduaWZpY2F0aXZhIGVuIHN1IGNvbXBvc2ljacOzbi4NCg0KYGBge3J9DQojIE9idGVuZXIgbGFzIHBhbGFicmFzIHkgZnJlY3VlbmNpYXMNCndvcmRfZnJlcXMgPC0gc29ydChyb3dTdW1zKGFzLm1hdHJpeChub3ZfdGRtKSksIGRlY3JlYXNpbmcgPSBUUlVFKQ0KcGFsYWJyYXMgPC0gbmFtZXMod29yZF9mcmVxcykNCmZyZWN1ZW5jaWFzIDwtIHdvcmRfZnJlcXMNCg0KIyBDcmVhciBsYSBudWJlIGRlIHBhbGFicmFzDQp3b3JkY2xvdWQocGFsYWJyYXMsIGZyZWN1ZW5jaWFzLCBtYXgud29yZHMgPSA4MCwgcmFuZG9tLm9yZGVyID0gRiwgY29sb3JzID0gYnJld2VyLnBhbChuYW1lID0gIkRhcmsyIiwgbiA9IDgpKQ0KYGBgDQoNCiMgVW5hIMO6bHRpbWEgZGVwdXJhY2nDs24uLg0KDQpgYGB7cn0NCiMgRWxpbWluYXIgbGFzIHBhbGFicmFzIGFkaWNpb25hbGVzIA0Kbm92X3RleHQgPC0gcmVtb3ZlV29yZHMobm92X3RleHQsIHdvcmRzID0gYygidXN0ZWQiLCAicHVlcyIsICJ0YWwiLCAidGFuIiwgImFzw60iLCAiZGlqbyIsICJjw7NtbyIsICJzaW5vIiwgImVudG9uY2VzIiwgImF1bnF1ZSIsICJkb24iLCAiZG/DsWEiKSkNCg0KIyBFbGltaW5hciBjYXJhY3RlcmVzICItIiAiPyIsICLCvyINCm5vdl90ZXh0IDwtIGdzdWIoIltcXC1cXD9cXMK/XFzCoVxc4oCUwqFcXMKrXFzCu10iLCAiICIsIG5vdl90ZXh0KSANCg0KIyBDcmVhciB1biBudWV2byBjb3JwdXMgY29uIGxhcyBwYWxhYnJhcyBlbGltaW5hZGFzDQpub3ZfY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2Uobm92X3RleHQpKQ0KDQojIEdlbmVyYXIgbGEgbWF0cml6IFRETQ0Kbm92X3RkbSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgobm92X2NvcnB1cywgY29udHJvbCA9IGxpc3QoDQogICAgd29yZExlbmd0aHMgPSBjKDEsIEluZiksDQogICAgcmVtb3ZlTnVtYmVycyA9IFRSVUUsDQogICAgcmVtb3ZlUHVuY3R1YXRpb24gPSBUUlVFLA0KICAgIHN0b3B3b3JkcyA9IHN0b3B3b3Jkcygic3BhbmlzaCIpDQopKQ0KDQojIE9idGVuZXIgbGFzIHBhbGFicmFzIHkgZnJlY3VlbmNpYXMNCndvcmRfZnJlcXMgPC0gc29ydChyb3dTdW1zKGFzLm1hdHJpeChub3ZfdGRtKSksIGRlY3JlYXNpbmcgPSBUUlVFKQ0KcGFsYWJyYXMgPC0gbmFtZXMod29yZF9mcmVxcykNCmZyZWN1ZW5jaWFzIDwtIHdvcmRfZnJlcXMNCg0KIyBDcmVhciBsYSBudWJlIGRlIHBhbGFicmFzDQp3b3JkY2xvdWQocGFsYWJyYXMsIGZyZWN1ZW5jaWFzLCBtYXgud29yZHMgPSA4MCwgcmFuZG9tLm9yZGVyID0gRiwgY29sb3JzID0gYnJld2VyLnBhbChuYW1lID0gIkRhcmsyIiwgbiA9IDgpKQ0KYGBgDQoNCk90cmEgZm9ybWEgZGUgZ3JhZmljYXJsbw0KYGBge3Igd2FybmluZz1GQUxTRSAsZWNobz1UUlVFfQ0KbGlicmFyeSh3b3JkY2xvdWQyKQ0KIyN3ZWJzaG90OjppbnN0YWxsX3BoYW50b21qcygpDQpkIDwtIGRhdGEuZnJhbWUod29yZCA9IHBhbGFicmFzLGZyZXE9ZnJlY3VlbmNpYXMpDQp3b3JkY2xvdWQyKGQsc2hhcGUgPSAiZGlhbW9uZCIsc2l6ZSA9IDAuNCkNCmBgYA0KDQoNCg0KDQojIEFuw6FsaXNpcyBkZSBzZW50aW1pZW50b3MNCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KHRleHRkYXRhKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkocmVzaGFwZTIpDQpgYGANCg0KRWwgcGFxdWV0ZSB0aWR5dGV4dCBjb250aWVuZSAzIGRpY2Npb25hcmlvcyBkaXN0aW50b3M6DQoNCi0gYWZmaW46IGFzaWduYSBhIGNhZGEgcGFsYWJyYSB1biB2YWxvciBlbnRyZSAtNSB5IDUuIFNpZW5kbyAtNSBlbCBtw6F4aW1vIGRlIG5lZ2F0aXZpZGFkIHkgKzUgZWwgbcOheGltbyBkZSBwb3NpdGl2aWRhZC4NCi0gYmluZzogY2xhc2lmaWNhIGxhcyBwYWxhYnJhcyBkZSBmb3JtYSBiaW5hcmlhIHBvc2l0aXZvL25lZ2F0aXZvLg0KLSBucmM6IGNsYXNpZmljYSBjYWRhIHBhbGFicmEgZW4gdW5vIG8gbcOhcyBkZSBsb3Mgc2lndWllbnRlcyBzZW50aW1pZW50b3M6IHBvc2l0aXZlLCBuZWdhdGl2ZSwgYW5nZXIsIGFudGljaXBhdGlvbiwgZGlzZ3VzdCwgZmVhciwgam95LCBzYWRuZXNzLCBzdXJwcmlzZSwgYW5kIHRydXN0Lg0KDQpgYGB7cn0NCm5yYzwtZ2V0X3NlbnRpbWVudHMoIm5yYyIpDQpCYXNlX2FuYTwtZCAlPiUgaW5uZXJfam9pbihucmMsIndvcmQiKSAjIFVuaW1vcyBlbCBkYXRhIGRlIHNlbnRpZW1pZW50b3MgY29uIG51ZXN0cmFzIHBhbGFicmFzIHBhcmEgcXVlIGxhcyBjbGFzaWZpcXVlIHNlZ8O6biBjb25jb3JkYW5jaWENCkJhc2VfYW5hPC1hcnJhbmdlKEJhc2VfYW5hLC1mcmVxKSAjb3JkZW5hbW9zIGRlIGZvcm1hIGRlc2NlbmRlbnRlIHBvciBmcmVjdWVuY2lhDQpgYGANCiMgTnViZSBkZSBwYWxhYnJhcyBwb3Igc2VudGltaWVudG8NCg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmI1PC1kYXRhLmZyYW1lKEJhc2VfYW5hICU+JSBncm91cF9ieSh3b3JkLHNlbnRpbWVudCkgJT4lIHN1bW1hcmlzZShuPW4oKSkpICU+JSBhcnJhbmdlKC1uKQ0KDQpiNSAlPiUgYWNhc3Qod29yZH5zZW50aW1lbnQsZmlsbCA9IDAsdmFsdWUudmFyID0gIm4iKSAlPiUgI2xsZW5hciBsb3MgbWlzc2luZyB2YWx1ZXMgY29uIGNlcm8NCiAgY29tcGFyaXNvbi5jbG91ZChtYXgud29yZHMgPSA1MDAsICNNYXhpbW8gZGUgcGFsYWJyYXMgYSByZXByZXNlbnRhcg0KICAgICAgICAgICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwobiA9IDEwLCJQYWlyZWQiKSwgI0NhbnRpZGFkIGRlIGNvbG9yZXMgZGlmZXJlbnRlcyBlcXVpdmFsZW50ZSBhIGNhbnRpZGFkIGRlIHNlbnRpbWllbnRvcw0KICAgICAgICAgICAgICAgICAgIHRpdGxlLnNpemUgPSAxLHRpdGxlLmNvbG9ycyA9ICJibGFjayIsICNDb2xvciBkZSB0aXR1bG9zIGRlIHNlbnRpbWllbnRvcyANCiAgICAgICAgICAgICAgICAgICB0aXRsZS5iZy5jb2xvcnMgPSAid2hpdGUiKSAjUmVzYWx0YWRvIGRlIHRpdHVsb3MNCmBgYA0KDQpUb3AgMTAgcG9yIHNlbnRpbWllbnRvDQoNCmBgYHtyfQ0KQmFzZV9hbmEgJT4lIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lIHRvcF9uKDEwLCBmcmVxKSAlPiUgIyBMYXMgMTAgcGFsYWJyYXMgY29uIG1heW9yIGZyZWN1ZW5jaWEgY2xhc2lmaWNhZGFzIHBvciBzdSByZXNwZWN0aXZvIHNlbnRpbWllbnRvDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHdvcmQ9cmVvcmRlcih3b3JkLGZyZXEpKSAlPiUgDQogIGdncGxvdChhZXMoeD13b3JkLHk9ZnJlcSxmaWxsPXNlbnRpbWVudCkpKw0KICBnZW9tX2NvbCgpKyAjIGdyYWZpY28gZGUgY29sdW1uYXMNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB+c2VudGltZW50LG5yb3cgPSAzLHNjYWxlcyA9ICJmcmVlIikrICNzY2FsZXM9ImZyZWUiIHBhcmEgY2FkYSBncmFmaWNvIGRlIGJhcnJhIG1lIHBvbmdhIHN1IHByb3BpbyBlamUgeCwgZXMgZGVjaXIgc3VzIHByb3BpYXMgcGFsYWJyYXMNCiAgY29vcmRfZmxpcCgpICsgIyBEZSBob3Jpem9udGFsIHBhc2EgYSB2ZXJ0aWNhbA0KICB0aGVtZV9saWdodCgpICsgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDUpKSArIHRoZW1lX2J3KCkNCmBgYA0KU2VudGltaWVudG9zIHByZWRvbWluYW50ZXMNCmBgYHtyfQ0KQmFzZV9hbmEgJT4lICBncm91cF9ieShzZW50aW1lbnQpICU+JSBzdW1tYXJpc2Uobj1uKCkpICU+JSANCiAgdW5ncm91cCgpICU+JSBtdXRhdGUoc2VudGltZW50PXJlb3JkZXIoc2VudGltZW50LC1uKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHg9c2VudGltZW50LHk9bikpICsgZ2VvbV9jb2woYWVzKGZpbGw9c2VudGltZW50KSkgKyANCiAgdGhlbWVfY2xhc3NpYygpICsgbGFicyh0aXRsZT0iQ2FudGlkYWQgZGUgc2VudGltaWVudG9zIGVuY29udHJhZG9zIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICB5PSJGcmVjdWVuY2lhIixmaWxsPSJTZW50aW1pZW50byIseD0iU2VudGltaWVudG8iKSArIA0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2U9ImJvbGQiLGhqdXN0ID0gMC41KSwNCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2U9ImJvbGQiKSkNCg0KYGBgDQo=