Text Mining Fundamentals

Paso 1 - Preparación del código

1.1 - Establecer el idioma predeterminado para que sea consideraro por el sistema

Sys.setenv(LANG = "en")

1.2 - Instalación de librerías (en caso de ser necesario) y Cargar librerías

#install.packages("tinytex")
# A few examples on how to Install packages
#install.packages("tm")  # for text mining
#install.packages("SnowballC") # for text manipulation
#install.packages("wordcloud") # word-cloud generator
library(tm) 
## Loading required package: NLP
library(SnowballC)
library(wordcloud)
## Loading required package: RColorBrewer
library(RColorBrewer)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)
library(stringr)
library(udpipe)
library(lattice)
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(ggplot2)
## 
## Attaching package: 'ggplot2'
## The following object is masked from 'package:NLP':
## 
##     annotate

La librería Udpipe proporciona algunos modelos preentrenados para algunos idiomas y se pueden descargar a la computadora utilizando el comando udpipe_download_model() para posteriormente cargarlo a tu ambiente de R utilizando el comando udpipe_load_model().

model <- udpipe_download_model(language = "english")
## Downloading udpipe model from https://raw.githubusercontent.com/jwijffels/udpipe.models.ud.2.5/master/inst/udpipe-ud-2.5-191206/english-ewt-ud-2.5-191206.udpipe to /private/var/folders/0m/n2h82y8x40l03_rndfw0bt640000gn/T/net.whatsapp.WhatsApp/documents/1E619178-64A6-4B87-A5DF-318C1A7CF99E/english-ewt-ud-2.5-191206.udpipe
##  - This model has been trained on version 2.5 of data from https://universaldependencies.org
##  - The model is distributed under the CC-BY-SA-NC license: https://creativecommons.org/licenses/by-nc-sa/4.0
##  - Visit https://github.com/jwijffels/udpipe.models.ud.2.5 for model license details.
##  - For a list of all models and their licenses (most models you can download with this package have either a CC-BY-SA or a CC-BY-SA-NC license) read the documentation at ?udpipe_download_model. For building your own models: visit the documentation by typing vignette('udpipe-train', package = 'udpipe')
## Downloading finished, model stored at '/private/var/folders/0m/n2h82y8x40l03_rndfw0bt640000gn/T/net.whatsapp.WhatsApp/documents/1E619178-64A6-4B87-A5DF-318C1A7CF99E/english-ewt-ud-2.5-191206.udpipe'
udmodel_english <- udpipe_load_model(file = 'english-ewt-ud-2.5-191206.udpipe')

Paso 2 - Importar la base de datos que será analizada y entender el Contexto del Dataset

(https://www.kaggle.com/datasets/meetnagadia/amazon-kindle-book-review-for-sentiment-analysis)

bd <- read.csv("/Users/hugoenrique/Desktop/Universidad/8vo Semestre/Planeación Estratégica/M2/R Scripts/all_kindle_review.csv", header = T, stringsAsFactors = F)
glimpse(bd)
## Rows: 12,000
## Columns: 11
## $ X              <int> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
## $ Unnamed..0     <int> 11539, 5957, 9146, 7038, 1776, 3744, 13641, 4448, 2797,…
## $ asin           <chr> "B0033UV8HI", "B002HJV4DE", "B002ZG96I4", "B002QHWOEU",…
## $ helpful        <chr> "[8, 10]", "[1, 1]", "[0, 0]", "[1, 3]", "[0, 1]", "[6,…
## $ rating         <int> 3, 5, 3, 3, 4, 5, 2, 4, 5, 4, 1, 4, 1, 4, 5, 2, 4, 1, 5…
## $ reviewText     <chr> "Jace Rankin may be short, but he's nothing to mess wit…
## $ reviewTime     <chr> "09 2, 2010", "10 8, 2013", "04 11, 2014", "07 5, 2014"…
## $ reviewerID     <chr> "A3HHXRELK8BHQG", "A2RGNZ0TRF578I", "A3S0H2HV6U1I7F", "…
## $ reviewerName   <chr> "Ridley", "Holly Butler", "Merissa", "Cleargrace", "Rjo…
## $ summary        <chr> "Entertaining But Average", "Terrific menage scenes!", …
## $ unixReviewTime <int> 1283385600, 1381190400, 1397174400, 1404518400, 1356912…

Contexto del Dataset:

Es un subconjunto de las reseñas de productos de la categoría Amazon Kindle Store, cubriendo desde mayo de 1996 hasta julio de 2014.

Contenido:

Tiene un como total de reseñas 982,619, donde cada usuario ha escrito al menos 5 reseñas y cada producto tiene al menos 5 reseñas.

Columnas del dataset:

  • asin: ID del producto (ejemplo: B000FA64PK)
  • helpful: Calificación de utilidad de la reseña (por ejemplo: 2/3)
  • rating: Puntuación del producto (1 a 5 estrellas)
  • reviewText: Texto del comentario
  • reviewTime: Fecha en formato de texto
  • reviewerID: ID del usuario que reseña
  • reviewerName: Nombre del usuario
  • summary: Resumen/título de la reseña

Paso 3 - Histogramas relevantes.

Histograma de los años (review_date)

bd_2 <- bd %>%
  mutate(
    review_date = mdy(reviewTime),          
    year = str_sub(review_date,1,4),
    month = str_sub(review_date,6,7),
    date = str_sub(review_date,9,10))

bd_2 %>% group_by(year) %>% count() %>% ggplot() + geom_bar(aes(x = year, y = n, group = 1), stat = "identity", fill = "steelblue") +labs(title = "Cantidad de reseñas por año", x = "Año", y = "Número de reseñas") + theme_minimal()

Histograma de los meses (review_date)

bd_2 %>% group_by(month) %>% count() %>% ggplot() + geom_bar(aes(x = month, y = n), stat = "identity", fill = "lightgreen") +labs(title = "Cantidad de reseñas por mes", x = "Mes", y = "Número de reseñas") + theme_minimal()

Histograma de la calificación (rating)

# Convertir de texto o factor a numérico
bd_2$rating <- as.numeric(as.character(bd_2$rating))
bd_2 %>% group_by(rating) %>% count() %>% ggplot() + geom_bar(aes(x = rating, y = n), stat = "identity", fill = "lightpink") +labs(title = "Cantidad de Puntuación del producto (1 a 5) ", x = "Puntuacion", y = "Cantidad de Puntuación") + theme_minimal()

Primer comparativo (Año 2013 del mes Enero a Junio)

Paso 4 - Primer Filtro

Se aplico un filtro donde nos enfocamos en las reseñas del año 2013 durante los meses de Enero a Junio.

bd_2 <- bd_2 %>% mutate(month = as.numeric(month))
filtro_1 <- bd_2 %>% filter(year == 2013 & month >= 01 & month <= 06)

filtro_1 %>% group_by(month) %>% count() %>% ggplot() + geom_bar(aes(x = month, y = n), stat = "identity", fill = "steelblue") + labs(title = "Cantidad de reseñas 2013 (Enero a Junio)", x = "Mes", y = "Cantidad de reseñas") + theme_minimal()

Paso 5 - Análisis de Texto Primer Filtro

5.1 - Limpieza General del Texto

# Crear corpus a partir de la columna reviewText del dataframe filtro_1
corpus1 <- VCorpus(VectorSource(filtro_1$reviewText))
# Convertir a minúsculas
corpus1 <- tm_map(corpus1, content_transformer(tolower))

# Quitar números
corpus1 <- tm_map(corpus1, removeNumbers)

# Quitar puntuación
corpus1 <- tm_map(corpus1, removePunctuation)

# Quitar espacios en blanco extra
corpus1 <- tm_map(corpus1, stripWhitespace)

5. 2 - Encontrar palabras más frecuentes (mostrar tabla)

Para encontrar las palabras más frecuentes se usa la ‘Matriz de Terminos Documento’, básicamente es una tabla que contiene la frecuencia de las palabras presentes en el documento que se analiza.

dtm <- TermDocumentMatrix(corpus1)
m <- as.matrix(dtm)
v <- sort(rowSums(m),decreasing=TRUE)
d <- data.frame(word = names(v),freq=v)
head(d, 50)

5.3 - Omitir stopwords frecuentes

# Quitar stopwords en inglés
corpus1 <- tm_map(corpus1, removeWords, stopwords("english"))

5.4 - Encontrar de nuevo palabras más frecuentes (mostrar tabla)

dtm1 <- TermDocumentMatrix(corpus1)
m1 <- as.matrix(dtm1)
v1 <- sort(rowSums(m1),decreasing=TRUE)
d1 <- data.frame(word = names(v1),freq=v1)
head(d1, 50)

5.5 - Buscar asociaciones relevantes para al menos 10 de las palabras más frecuentes en sus textos

findFreqTerms(dtm1, lowfreq = 20)
##   [1] "able"          "absolutely"    "across"        "action"       
##   [5] "actually"      "add"           "added"         "admit"        
##   [9] "adult"         "adults"        "adventure"     "age"          
##  [13] "ago"           "almost"        "alone"         "along"        
##  [17] "alpha"         "already"       "also"          "although"     
##  [21] "always"        "amazing"       "amazon"        "amount"       
##  [25] "annoying"      "another"       "anyone"        "anything"     
##  [29] "around"        "attention"     "attraction"    "author"       
##  [33] "authors"       "available"     "away"          "awesome"      
##  [37] "back"          "background"    "bad"           "based"        
##  [41] "beautiful"     "became"        "become"        "becomes"      
##  [45] "bed"           "begin"         "beginning"     "believable"   
##  [49] "believe"       "best"          "better"        "beyond"       
##  [53] "big"           "bit"           "black"         "book"         
##  [57] "books"         "boring"        "bother"        "bought"       
##  [61] "boy"           "break"         "bring"         "brother"      
##  [65] "brothers"      "brought"       "buy"           "call"         
##  [69] "called"        "came"          "can"           "cant"         
##  [73] "care"          "case"          "caught"        "certain"      
##  [77] "certainly"     "chance"        "change"        "chapter"      
##  [81] "chapters"      "character"     "characters"    "check"        
##  [85] "children"      "christian"     "classic"       "clear"        
##  [89] "close"         "collection"    "come"          "comes"        
##  [93] "coming"        "complete"      "completely"    "concept"      
##  [97] "confused"      "confusing"     "connection"    "consider"     
## [101] "continue"      "continues"     "couldnt"       "couple"       
## [105] "course"        "cover"         "crazy"         "create"       
## [109] "cup"           "cute"          "dark"          "day"          
## [113] "days"          "deal"          "death"         "decent"       
## [117] "decided"       "definitely"    "depth"         "description"  
## [121] "descriptions"  "despite"       "detail"        "details"      
## [125] "developed"     "development"   "dialogue"      "didnt"        
## [129] "different"     "difficult"     "disappointed"  "doesnt"       
## [133] "done"          "dont"          "downloaded"    "dragon"       
## [137] "dragons"       "drama"         "drawn"         "due"          
## [141] "early"         "earth"         "easily"        "easy"         
## [145] "ebook"         "editing"       "editor"        "either"       
## [149] "else"          "emotions"      "end"           "ended"        
## [153] "ending"        "ends"          "enjoy"         "enjoyable"    
## [157] "enjoyed"       "enough"        "entertaining"  "entire"       
## [161] "erotic"        "erotica"       "errors"        "especially"   
## [165] "etc"           "even"          "ever"          "every"        
## [169] "everyone"      "everything"    "evil"          "exactly"      
## [173] "example"       "excellent"     "exciting"      "expect"       
## [177] "expected"      "expecting"     "experience"    "extremely"    
## [181] "eyes"          "face"          "fact"          "fairly"       
## [185] "fairy"         "fall"          "familiar"      "family"       
## [189] "fan"           "fantasy"       "far"           "fast"         
## [193] "father"        "favorite"      "feel"          "feeling"      
## [197] "feelings"      "feels"         "fell"          "felt"         
## [201] "female"        "fiction"       "figure"        "filled"       
## [205] "finally"       "find"          "finding"       "finds"        
## [209] "finish"        "finished"      "first"         "five"         
## [213] "follow"        "forced"        "form"          "forward"      
## [217] "found"         "four"          "free"          "friend"       
## [221] "friends"       "full"          "fun"           "funny"        
## [225] "future"        "gave"          "gay"           "genre"        
## [229] "get"           "gets"          "getting"       "girl"         
## [233] "give"          "given"         "gives"         "giving"       
## [237] "glad"          "god"           "goes"          "going"        
## [241] "gone"          "good"          "got"           "grammar"      
## [245] "grammatical"   "graphic"       "great"         "group"        
## [249] "guess"         "guy"           "guys"          "hadnt"        
## [253] "half"          "handsome"      "happen"        "happened"     
## [257] "happens"       "happy"         "hard"          "hate"         
## [261] "havent"        "head"          "heart"         "heat"         
## [265] "held"          "hell"          "help"          "hero"         
## [269] "heroine"       "hes"           "high"          "highly"       
## [273] "history"       "hold"          "home"          "honestly"     
## [277] "hooked"        "hope"          "hoping"        "horror"       
## [281] "hot"           "hours"         "house"         "however"      
## [285] "huge"          "human"         "humans"        "humor"        
## [289] "husband"       "idea"          "ideas"         "ill"          
## [293] "immediately"   "important"     "information"   "instead"      
## [297] "interest"      "interested"    "interesting"   "introduced"   
## [301] "involved"      "isnt"          "issue"         "issues"       
## [305] "ive"           "jack"          "jake"          "james"        
## [309] "job"           "just"          "keep"          "keeps"        
## [313] "kept"          "kids"          "kill"          "killer"       
## [317] "kind"          "kinda"         "kindle"        "king"         
## [321] "knew"          "know"          "known"         "knows"        
## [325] "lack"          "language"      "last"          "later"        
## [329] "lead"          "learn"         "least"         "leave"        
## [333] "left"          "length"        "less"          "let"          
## [337] "lets"          "level"         "life"          "light"        
## [341] "like"          "liked"         "line"          "lines"        
## [345] "list"          "literally"     "little"        "live"         
## [349] "lives"         "living"        "long"          "longer"       
## [353] "look"          "looking"       "lost"          "lot"          
## [357] "lots"          "love"          "loved"         "loves"        
## [361] "loving"        "made"          "magic"         "main"         
## [365] "make"          "makes"         "making"        "male"         
## [369] "man"           "many"          "marry"         "mate"         
## [373] "may"           "maybe"         "mean"          "means"        
## [377] "meet"          "meets"         "men"           "mention"      
## [381] "mentioned"     "met"           "might"         "mind"         
## [385] "missing"       "money"         "months"        "mother"       
## [389] "move"          "moves"         "movie"         "much"         
## [393] "must"          "mysteries"     "mystery"       "name"         
## [397] "need"          "needed"        "needs"         "never"        
## [401] "new"           "next"          "nice"          "night"        
## [405] "none"          "note"          "nothing"       "novel"        
## [409] "novella"       "novels"        "now"           "number"       
## [413] "often"         "okay"          "old"           "older"        
## [417] "one"           "ones"          "opinion"       "order"        
## [421] "original"      "others"        "otherwise"     "outside"      
## [425] "overall"       "pace"          "page"          "pages"        
## [429] "paid"          "paranormal"    "part"          "particularly" 
## [433] "parts"         "pass"          "past"          "pay"          
## [437] "people"        "perfect"       "perhaps"       "person"       
## [441] "pick"          "picked"        "place"         "play"         
## [445] "please"        "plot"          "plots"         "plus"         
## [449] "point"         "poor"          "potential"     "predictable"  
## [453] "premise"       "pretty"        "previous"      "price"        
## [457] "probably"      "problem"       "problems"      "purchase"     
## [461] "purchased"     "purpose"       "put"           "quick"        
## [465] "quickly"       "quite"         "rather"        "rating"       
## [469] "read"          "reader"        "readers"       "reading"      
## [473] "reads"         "ready"         "real"          "realistic"    
## [477] "realize"       "really"        "reason"        "recommend"    
## [481] "recommended"   "relationship"  "remember"      "research"     
## [485] "rest"          "return"        "review"        "reviewers"    
## [489] "reviews"       "right"         "romance"       "romantic"     
## [493] "run"           "rushed"        "sad"           "said"         
## [497] "save"          "saw"           "say"           "saying"       
## [501] "says"          "scene"         "scenes"        "school"       
## [505] "scifi"         "second"        "see"           "seeing"       
## [509] "seem"          "seemed"        "seems"         "seen"         
## [513] "sense"         "sequel"        "serial"        "series"       
## [517] "serious"       "seriously"     "set"           "setting"      
## [521] "several"       "sex"           "sexual"        "sexy"         
## [525] "shes"          "shifter"       "short"         "show"         
## [529] "shows"         "side"          "silly"         "simple"       
## [533] "simply"        "since"         "sister"        "situation"    
## [537] "slow"          "small"         "solid"         "someone"      
## [541] "something"     "sometimes"     "somewhat"      "soon"         
## [545] "sorry"         "sort"          "space"         "spent"        
## [549] "stand"         "star"          "stars"         "start"        
## [553] "started"       "starts"        "stay"          "steamy"       
## [557] "still"         "stop"          "stories"       "story"        
## [561] "storyline"     "straight"      "strange"       "strong"       
## [565] "stuff"         "stupid"        "style"         "super"        
## [569] "supposed"      "sure"          "surprised"     "suspense"     
## [573] "sweet"         "take"          "taken"         "takes"        
## [577] "taking"        "tale"          "tales"         "talk"         
## [581] "talking"       "tell"          "telling"       "tells"        
## [585] "terrible"      "thank"         "thats"         "theres"       
## [589] "theyre"        "thing"         "things"        "think"        
## [593] "thinking"      "third"         "thoroughly"    "though"       
## [597] "thought"       "three"         "throughout"    "thrown"       
## [601] "time"          "times"         "title"         "together"     
## [605] "told"          "took"          "top"           "totally"      
## [609] "tried"         "trouble"       "true"          "truly"        
## [613] "try"           "trying"        "turn"          "turned"       
## [617] "turns"         "twist"         "twists"        "two"          
## [621] "type"          "typos"         "understand"    "unfortunately"
## [625] "unique"        "unless"        "use"           "used"         
## [629] "uses"          "using"         "usual"         "usually"      
## [633] "vampire"       "vampires"      "verne"         "version"      
## [637] "wait"          "waiting"       "want"          "wanted"       
## [641] "wanting"       "wants"         "war"           "wasnt"        
## [645] "waste"         "way"           "weird"         "well"         
## [649] "went"          "whole"         "wife"          "will"         
## [653] "willing"       "wish"          "without"       "wolf"         
## [657] "woman"         "women"         "wonder"        "wonderful"    
## [661] "wont"          "word"          "words"         "work"         
## [665] "worked"        "working"       "works"         "world"        
## [669] "worst"         "worth"         "wouldnt"       "write"        
## [673] "writer"        "writers"       "writes"        "writing"      
## [677] "written"       "wrong"         "wrote"         "year"         
## [681] "years"         "yes"           "yet"           "youll"        
## [685] "young"         "youre"         "zombies"
findAssocs(dtm1, terms = "book", corlimit = 0.5)
## $book
## numeric(0)
barplot(d1[1:20,]$freq, las = 2, names.arg = d1[1:20,]$word,
        col ="lightblue", main ="Palabras más Frecuentes F1",
        ylab = "Frecuencias")

La palabra con mayor dominio es “book”, siendo el término más relevante y frecuente. Al inicio de la lista se agrupan los conceptos fundamentales (“book”, “story”, “read”). A lo largo de la lista, las palabras van disminuyendo. Esto muestra que, después de los conceptos clave, aparece un vocabulario más variado, que sirve para dar opinión y describir los elementos de las historias.

5.6 - Desglosar las palabras frecuentes de acuerdo con su clasificación y generar sus respectivas gráficas de barras:

# Aplicar anotación lingüística con udpipe sobre la columna reviewText
s1 <- udpipe_annotate(udmodel_english, x = filtro_1$reviewText)
#  Convertir a data frame
x1 <- as.data.frame(s1)
stats1 <- txt_freq(x1$upos)
stats1$key <- factor(stats1$key, levels = rev(stats1$key))
barchart(key ~ freq, data = stats1, col = "orange", 
         main = "UPOS (Universal Parts of Speech)\n Frecuencia de Ocurrencia F1", 
         xlab = "Frecuencia")

En el analisis morfosintatico La categoría con mayor dominio es NOUN/Sustantivo, es el tipo de palabra más relevante y frecuente, indicando que el texto se centra en nombrar personas, lugares y cosas. Al inicio de la lista se agrupan las partes fundamentales que construyen el núcleo de las oraciones. La frecuencia de las categorías va disminuyendo. Esto refleja una estructura lingüística típica: el texto se construye sobre una base de sustantivos y verbos

o Sustantivos

stats1 <- subset(x1, upos %in% c("NOUN")) 
stats1 <- txt_freq(stats1$token)
stats1$key <- factor(stats1$key, levels = rev(stats1$key))
barchart(key ~ freq, data = head(stats1, 20), col = "cadetblue", 
         main = "Sustantivos más Comunes F1", xlab = "Frecuencia")

Los sustantivos más frecuentes como book, story, characters, series y author reflejan que las reseñas de los usuarios giran principalmente en torno al contenido narrativo más que al dispositivo o la plataforma.

o Adjetivos

stats1 <- subset(x1, upos %in% c("ADJ")) 
stats1 <- txt_freq(stats1$token)
stats1$key <- factor(stats1$key, levels = rev(stats1$key))
barchart(key ~ freq, data = head(stats1, 20), col = "purple", 
         main = "Adjetivos más Comunes F1", xlab = "Freq")

Los adjetivos dominantes como good, great, interesting, y better muestran una tendencia positiva general en las reseñas, lo que indica alta satisfacción con los libros analizados asi como la presencia de bad y different, aunque menos frecuente, evidencia una minoría crítica, posiblemente asociada a reseñas negativas

o Verbos

stats1 <- subset(x1, upos %in% c("VERB")) 
stats1 <- txt_freq(stats1$token)
stats1$key <- factor(stats1$key, levels = rev(stats1$key))
barchart(key ~ freq, data = head(stats1, 20), col = "gold", 
         main = "Verbos más Comunes F1", xlab = "Frecuencia")

Los verbos más frecuentes, encabezados por read, have, get, like, love y enjoyed, reflejan que las reseñas están centradas en la experiencia de lectura personal y en la valoración emocional del libro.

5.7 - Aplicar el algoritmo RAKE para detectar frases comúnmente utilizadas en tu base de datos.

Extracción automática de palabras claves utilizando un algoritmo preentrenado: RAKE (Rapid Automatic Keyword Extraction). En esencia, dicho algoritmo busca determinar frases clave dentro de un texto al analizar la frecuencia con las que ciertas palabras aparecen de manera conjunta dentro del texto en cuestión.

stats1 <- keywords_rake(x = x1, term = "lemma", group = "doc_id", 
                       relevant = x1$upos %in% c("NOUN", "ADJ"))
stats1$key <- factor(stats1$keyword, levels = rev(stats1$keyword))
barchart(key ~ rake, data = head(subset(stats1, freq > 3), 20), col = "red", 
         main = "Keywords identificados por RAKE F1", 
         xlab = "Rake")

Las frases más comunes identificadas por RAKE, como “grammatical error”, “panic attack”, “female lead” o “historical romance”, muestran una combinación de temas narrativos y percepciones críticas

x1$phrase_tag <- as_phrasemachine(x1$upos, type = "upos")
stats1 <- keywords_phrases(x = x1$phrase_tag, term = tolower(x1$token), 
                          pattern = "(A|N)*N(P+D*(A|N)*N)*", 
                          is_regex = TRUE, detailed = FALSE)
stats1 <- subset(stats1, ngram > 1 & freq > 3)
stats1$key <- factor(stats1$keyword, levels = rev(stats1$keyword))
barchart(key ~ freq, data = head(stats1, 20), col = "magenta", 
         main = "Keywords - Frases nominales F1", xlab = "Frecuencia")

Las frases más frecuentes como “that I”, “story line”, “short story”, “sex scenes”, “main character” y “good book” reflejan que los usuarios se enfocan principalmente en aspectos narrativos y de contenido asi también se enfocan en opiniones de los lectores.

5.8 - Generar una o dos nubes de palabras.

Nube con minimo de frecuencia de 50 veces la palabra

set.seed(20250402)
wordcloud(words = d1$word, freq = d1$freq, min.freq = 50,
          max.words=Inf, random.order=T, rot.per=0.5, 
          colors=brewer.pal(8, "Dark2"))

Se observa una gran variedad léxica, donde destacan palabras como book, story, characters, series, good, reading, love, y just. Esto muestra que el corpus de reseñas es amplio y abarca múltiples temas relacionados con la lectura, trama, personajes y emociones.

Nube con minimo de frecuencia de 150 veces la palabra

set.seed(20250402)
wordcloud(words = d1$word, freq = d1$freq, min.freq = 150,
          max.words=Inf, random.order=T, rot.per=0.5, 
          colors=brewer.pal(8, "Dark2"))

En esta nube se concentran palabras como book, story, read, characters, y good, lo que sugiere que la mayoría de los comentarios giran en torno a la experiencia general de lectura y la calidad narrativa.

Segundo comparativo (Año 2013 del mes Julio a Diciembre)

Paso 4 - Primer Filtro

Se aplico un filtro donde nos enfocamos en las reseñas del año 2013 durante los meses de Julio a Diciembre.

filtro_2 <- bd_2 %>% filter(year == 2013 & month >= 07 & month <= 12)

filtro_2 %>% group_by(month) %>% count() %>% ggplot() + geom_bar(aes(x = month, y = n), stat = "identity", fill = "steelblue") + labs(title = "Cantidad de reseñas 2013 (Julio a Diciembre)", x = "Mes", y = "Cantidad de reseñas") + theme_minimal()

Paso 5 - Análisis de Texto Primer Filtro

5.1 - Limpieza General del Texto

# Crear corpus a partir de la columna reviewText del dataframe filtro_1
corpus2 <- VCorpus(VectorSource(filtro_2$reviewText))
# Convertir a minúsculas
corpus2 <- tm_map(corpus2, content_transformer(tolower))

# Quitar números
corpus2 <- tm_map(corpus2, removeNumbers)

# Quitar puntuación
corpus2 <- tm_map(corpus2, removePunctuation)

# Quitar espacios en blanco extra
corpus2 <- tm_map(corpus2, stripWhitespace)

5. 2 - Encontrar palabras más frecuentes (mostrar tabla)

Para encontrar las palabras más frecuentes se usa la ‘Matriz de Terminos Documento’, básicamente es una tabla que contiene la frecuencia de las palabras presentes en el documento que se analiza.

dtm2 <- TermDocumentMatrix(corpus2)
m2 <- as.matrix(dtm2)
v2 <- sort(rowSums(m2),decreasing=TRUE)
d2 <- data.frame(word = names(v2),freq=v2)
head(d2, 50)

5.3 - Omitir stopwords frecuentes

# Quitar stopwords en inglés
corpus2 <- tm_map(corpus2, removeWords, stopwords("english"))

5.4 - Encontrar de nuevo palabras más frecuentes (mostrar tabla)

dtm3 <- TermDocumentMatrix(corpus2)
m3 <- as.matrix(dtm3)
v3 <- sort(rowSums(m3),decreasing=TRUE)
d3 <- data.frame(word = names(v3),freq=v3)
head(d3, 50)

5.5 - Buscar asociaciones relevantes para al menos 10 de las palabras más frecuentes en sus textos

findFreqTerms(dtm3, lowfreq = 20)
##   [1] "able"         "absolutely"   "across"       "action"       "actually"    
##   [6] "add"          "admit"        "age"          "ago"          "almost"      
##  [11] "along"        "alpha"        "already"      "also"         "although"    
##  [16] "always"       "amazing"      "amazon"       "another"      "anyone"      
##  [21] "anything"     "around"       "attention"    "author"       "authors"     
##  [26] "away"         "back"         "bad"          "based"        "bdsm"        
##  [31] "beautiful"    "become"       "becomes"      "bed"          "beginning"   
##  [36] "believable"   "believe"      "best"         "better"       "beyond"      
##  [41] "big"          "bit"          "blake"        "book"         "books"       
##  [46] "boring"       "bought"       "brett"        "bring"        "brother"     
##  [51] "buy"          "call"         "came"         "can"          "cant"        
##  [56] "care"         "case"         "certainly"    "chance"       "change"      
##  [61] "chapter"      "chapters"     "character"    "characters"   "check"       
##  [66] "chemistry"    "child"        "children"     "christmas"    "classic"     
##  [71] "collection"   "come"         "comes"        "coming"       "complete"    
##  [76] "completely"   "continue"     "copy"         "couldnt"      "couple"      
##  [81] "course"       "cover"        "cute"         "dark"         "daughter"    
##  [86] "day"          "days"         "decent"       "decided"      "definitely"  
##  [91] "demons"       "depth"        "described"    "description"  "descriptions"
##  [96] "details"      "developed"    "development"  "dialogue"     "didnt"       
## [101] "different"    "difficult"    "disappointed" "doesnt"       "done"        
## [106] "dont"         "due"          "earth"        "easy"         "edge"        
## [111] "editing"      "either"       "else"         "emotional"    "end"         
## [116] "ended"        "ending"       "ends"         "enjoy"        "enjoyable"   
## [121] "enjoyed"      "enough"       "entertaining" "entire"       "erotic"      
## [126] "erotica"      "errors"       "especially"   "even"         "ever"        
## [131] "every"        "everyone"     "everything"   "evil"         "excellent"   
## [136] "exciting"     "expect"       "expected"     "expecting"    "eyes"        
## [141] "fact"         "fairly"       "family"       "fan"          "fantasy"     
## [146] "far"          "fast"         "father"       "favorite"     "feel"        
## [151] "feeling"      "feelings"     "felt"         "female"       "fiction"     
## [156] "fighting"     "figure"       "finally"      "find"         "finding"     
## [161] "finds"        "finish"       "finished"     "first"        "five"        
## [166] "follow"       "forward"      "found"        "four"         "free"        
## [171] "friend"       "friends"      "full"         "fun"          "funny"       
## [176] "future"       "gave"         "gay"          "get"          "gets"        
## [181] "getting"      "girl"         "give"         "given"        "gives"       
## [186] "glad"         "goes"         "going"        "good"         "got"         
## [191] "grammar"      "great"        "group"        "guess"        "guy"         
## [196] "guys"         "half"         "happen"       "happened"     "happens"     
## [201] "happy"        "hard"         "hate"         "havent"       "heart"       
## [206] "hell"         "help"         "hero"         "heroine"      "hes"         
## [211] "high"         "highly"       "history"      "hold"         "home"        
## [216] "honest"       "hope"         "hoping"       "hot"          "house"       
## [221] "however"      "human"        "humans"       "hunter"       "husband"     
## [226] "idea"         "ill"          "immediately"  "instead"      "interest"    
## [231] "interested"   "interesting"  "involved"     "isnt"         "issues"      
## [236] "ive"          "job"          "just"         "kallysten"    "kate"        
## [241] "keep"         "keeps"        "kept"         "kind"         "kindle"      
## [246] "knew"         "know"         "known"        "knows"        "language"    
## [251] "last"         "later"        "lead"         "learn"        "least"       
## [256] "leave"        "left"         "less"         "let"          "life"        
## [261] "light"        "like"         "liked"        "line"         "lisa"        
## [266] "little"       "live"         "lives"        "living"       "long"        
## [271] "longer"       "look"         "looked"       "looking"      "lost"        
## [276] "lot"          "lots"         "love"         "loved"        "loves"       
## [281] "made"         "magic"        "main"         "make"         "makes"       
## [286] "making"       "male"         "man"          "many"         "marc"        
## [291] "marriage"     "married"      "mate"         "mates"        "matter"      
## [296] "may"          "maybe"        "mean"         "meet"         "men"         
## [301] "met"          "might"        "mind"         "mistress"     "money"       
## [306] "mother"       "move"         "movie"        "moving"       "much"        
## [311] "must"         "mystery"      "name"         "need"         "needed"      
## [316] "needs"        "never"        "new"          "next"         "nice"        
## [321] "night"        "nothing"      "novel"        "novella"      "novels"      
## [326] "now"          "number"       "okay"         "old"          "older"       
## [331] "one"          "ones"         "order"        "others"       "overall"     
## [336] "pack"         "page"         "pages"        "pain"         "part"        
## [341] "parts"        "past"         "pay"          "people"       "perfect"     
## [346] "perhaps"      "person"       "pick"         "place"        "play"        
## [351] "plot"         "point"        "points"       "poor"         "potential"   
## [356] "predictable"  "premise"      "pretty"       "price"        "probably"    
## [361] "problem"      "problems"     "put"          "quick"        "quickly"     
## [366] "quite"        "rather"       "rating"       "read"         "reader"      
## [371] "readers"      "reading"      "real"         "really"       "reason"      
## [376] "recommend"    "relationship" "remember"     "rest"         "review"      
## [381] "reviews"      "ridiculous"   "right"        "romance"      "rushed"      
## [386] "said"         "save"         "say"          "scene"        "scenes"      
## [391] "school"       "second"       "secret"       "see"          "seem"        
## [396] "seemed"       "seems"        "seen"         "sense"        "series"      
## [401] "seriously"    "set"          "several"      "sex"          "sexual"      
## [406] "sexy"         "shes"         "short"        "show"         "shows"       
## [411] "side"         "sin"          "since"        "sister"       "slow"        
## [416] "small"        "someone"      "something"    "sometimes"    "soon"        
## [421] "sorry"        "sort"         "soul"         "star"         "stars"       
## [426] "start"        "started"      "starts"       "stay"         "steamy"      
## [431] "still"        "stop"         "stories"      "story"        "storyline"   
## [436] "strange"      "strong"       "stuff"        "style"        "supposed"    
## [441] "sure"         "surprised"    "suspense"     "sweet"        "take"        
## [446] "takes"        "tale"         "tales"        "talk"         "tell"        
## [451] "thats"        "theres"       "theyre"       "thing"        "things"      
## [456] "think"        "thinking"     "third"        "though"       "thought"     
## [461] "three"        "throughout"   "time"         "times"        "together"    
## [466] "told"         "took"         "top"          "totally"      "towards"     
## [471] "tried"        "true"         "truly"        "try"          "trying"      
## [476] "turn"         "turns"        "twist"        "twists"       "two"         
## [481] "type"         "understand"   "unique"       "use"          "used"        
## [486] "usually"      "vampire"      "vampires"     "version"      "wait"        
## [491] "want"         "wanted"       "wanting"      "wants"        "war"         
## [496] "wasnt"        "waste"        "way"          "well"         "went"        
## [501] "west"         "whats"        "whole"        "will"         "wish"        
## [506] "within"       "without"      "woman"        "women"        "wonderful"   
## [511] "wont"         "word"         "words"        "work"         "works"       
## [516] "world"        "worst"        "worth"        "wouldnt"      "write"       
## [521] "writer"       "writing"      "written"      "wrong"        "year"        
## [526] "years"        "yes"          "yet"          "youll"        "young"       
## [531] "youre"
findAssocs(dtm3, terms = "book", corlimit = 0.2)
## $book
##      like     first      also    author      hint contained      even      much 
##      0.27      0.24      0.23      0.23      0.22      0.21      0.21      0.21
barplot(d3[1:20,]$freq, las = 2, names.arg = d3[1:20,]$word,
        col ="lightblue", main ="Palabras más Frecuentes F2",
        ylab = "Frecuencias")

En el segundo semestre de 2013, las palabras más frecuentes siguen una tendencia muy similar al primer filtro, con “book”, “story”, “read” y “one” como los términos dominantes. Esto confirma que los usuarios continúan centrando sus reseñas en la experiencia de lectura y en la calidad narrativa.

5.6 - Desglosar las palabras frecuentes de acuerdo con su clasificación y generar sus respectivas gráficas de barras:

# Aplicar anotación lingüística con udpipe sobre la columna reviewText
s2 <- udpipe_annotate(udmodel_english, x = filtro_2$reviewText)
#  Convertir a data frame
x2 <- as.data.frame(s2)
stats2 <- txt_freq(x2$upos)
stats2$key <- factor(stats2$key, levels = rev(stats2$key))
barchart(key ~ freq, data = stats2, col = "orange", 
         main = "UPOS (Universal Parts of Speech)\n Frecuencia de Ocurrencia F2", 
         xlab = "Frecuencia")

El análisis morfosintáctico muestra que las categorías más frecuentes en las reseñas son los sustantivos (NOUN), seguidos por verbos (VERB) y pronombres (PRON). Este patrón confirma que los usuarios estructuran sus reseñas en torno a temas concretos (libros, historias, personajes) acompañados de acciones e impresiones personales.Los demas valores son indicadores de un lenguaje natural

o Sustantivos

stats2 <- subset(x2, upos %in% c("NOUN")) 
stats2 <- txt_freq(stats2$token)
stats2$key <- factor(stats2$key, levels = rev(stats2$key))
barchart(key ~ freq, data = head(stats2, 20), col = "cadetblue", 
         main = "Sustantivos más Comunes F2", xlab = "Frecuencia")

se vuelve a confirmar una continuidad en el foco narrativo ya observado en el primer filtro. Los lectores siguen centrando sus reseñas en los elementos fundamentales de la experiencia literaria: el libro en sí, la historia, los personajes y el autor.

o Adjetivos

stats2 <- subset(x2, upos %in% c("ADJ")) 
stats2 <- txt_freq(stats2$token)
stats2$key <- factor(stats2$key, levels = rev(stats2$key))
barchart(key ~ freq, data = head(stats2, 20), col = "purple", 
         main = "Adjetivos más Comunes F2", xlab = "Frecuencia")

Los adjetivos predominantes como good, great, interesting, y better evidencian que la percepción general del público sigue siendo positiva, destacando la calidad y disfrute de las obras. La alta frecuencia de good y great reafirma la satisfacción lectora, mientras que interesting sugiere un interés sostenido en la trama o temática.

o Verbos

stats2 <- subset(x2, upos %in% c("VERB")) 
stats2 <- txt_freq(stats2$token)
stats2$key <- factor(stats2$key, levels = rev(stats2$key))
barchart(key ~ freq, data = head(stats2, 20), col = "gold", 
         main = "Verbos más Comunes F2", xlab = "Frecuencia")

Muy parecido a el Filtro 1 El verbo más frecuente, read, domina ampliamente la distribución, seguido de have, get, had y reading, lo que confirma que las reseñas giran en torno a la acción de leer y la experiencia de lectura. Esto refuerza la idea de que los usuarios no solo evalúan el producto, sino que narran su vivencia personal con el libro.

5.7 - Aplicar el algoritmo RAKE para detectar frases comúnmente utilizadas en tu base de datos.

Extracción automática de palabras claves utilizando un algoritmo preentrenado: RAKE (Rapid Automatic Keyword Extraction). En esencia, dicho algoritmo busca determinar frases clave dentro de un texto al analizar la frecuencia con las que ciertas palabras aparecen de manera conjunta dentro del texto en cuestión.

stats2 <- keywords_rake(x = x2, term = "lemma", group = "doc_id", 
                       relevant = x2$upos %in% c("NOUN", "ADJ"))
stats2$key <- factor(stats2$keyword, levels = rev(stats2$keyword))
barchart(key ~ rake, data = head(subset(stats2, freq > 3), 20), col = "red", 
         main = "Keywords identificados por RAKE", 
         xlab = "Rake")

Las frases extraídas por RAKE revelan los temas más específicos y recurrentes en las reseñas del segundo semestre. Entre las más destacadas se encuentran “high school”, “fairy tale”, “write style”, y “old fashioned”, lo que indica una fuerte presencia de obras ambientadas en contextos juveniles.

x2$phrase_tag <- as_phrasemachine(x2$upos, type = "upos")
stats2 <- keywords_phrases(x = x2$phrase_tag, term = tolower(x2$token), 
                          pattern = "(A|N)*N(P+D*(A|N)*N)*", 
                          is_regex = TRUE, detailed = FALSE)
stats2 <- subset(stats2, ngram > 1 & freq > 3)
stats2$key <- factor(stats2$keyword, levels = rev(stats2$keyword))
barchart(key ~ freq, data = head(stats2, 20), col = "magenta", 
         main = "Keywords - Frases nominales F2 ", xlab = "Frecuencia")

Las frases nominales más frecuentes, como “short story”, “good read”, “sex scenes”, “great story”, y “story line”, revelan que las reseñas del segundo semestre mantienen un foco fuerte en la narrativa, el entretenimiento y la temática romántica o erótica.

5.8 - Generar una o dos nubes de palabras.

Nube con minimo de frecuencia de 50 veces la palabra

set.seed(20250402)
wordcloud(words = d3$word, freq = d3$freq, min.freq = 50,
          max.words=Inf, random.order=T, rot.per=0.5, 
          colors=brewer.pal(8, "Dark2"))

se observa una amplia variedad de términos como book, story, love, character, great, good, like, author y series, lo que refleja la diversidad temática y emocional de las reseñas. Los lectores hablan tanto de la historia y sus personajes como de la calidad narrativa

Nube con minimo de frecuencia de 100 veces la palabra

set.seed(20250402)
wordcloud(words = d3$word, freq = d3$freq, min.freq = 100,
          max.words=Inf, random.order=T, rot.per=0.5, 
          colors=brewer.pal(8, "Dark2"))

la nube se concentra en los términos más representativos: book, story, good, like, love, y characters, evidenciando los ejes principales del discurso de los usuarios. Este filtro reduce el ruido y deja ver con claridad que las reseñas giran principalmente en torno a la calidad del libro, la trama, y la satisfacción emocional del lector.

Comparacion de ambos filtros

Filtro 1 (Enero – Junio 2013)

Enfoque en historia, personajes y autor; menor atención a temas técnicos.

Lenguaje positivo y descriptivo, con adjetivos como good, great e interesting.

Reseñas centradas en la calidad literaria y la estructura narrativa.

Aparición de críticas sobre formato y errores gramaticales, reflejando una etapa de adaptación al entorno digital.

Predomina un tono racional y analítico, más enfocado en la valoración del contenido que en la emoción.

Filtro 2 (Julio – Diciembre 2013)

Lenguaje más emocional y expresivo, centrado en géneros románticos y juveniles.

Palabras clave: love, romance, quick read, fairy tale y high school.

Mayor énfasis en la conexión afectiva con los personajes y la trama.

Disminuyen las críticas técnicas; los lectores se enfocan en disfrutar y recomendar.

Tono cercano y entusiasta, reflejando una comunidad lectora madura y comprometida con la experiencia narrativa.

LS0tCnRpdGxlOiAiQWN0aXZpZGFkIDIgLSBBbmFsaXNpcyBkZSBUZXh0byIKYXV0aG9yOiAiRXF1aXBvIDMiCmRhdGU6IApvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRydWUKICAgIGNvZGVfZG93bmxvYWQ6IFRydWUKICAgIHRoZW1lOiBjb3NtbwplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyBUZXh0IE1pbmluZyBGdW5kYW1lbnRhbHMKCiMjIFBhc28gMSAtIFByZXBhcmFjacOzbiBkZWwgY8OzZGlnbwoKIyMjIDEuMSAtIEVzdGFibGVjZXIgZWwgaWRpb21hIHByZWRldGVybWluYWRvIHBhcmEgcXVlIHNlYSBjb25zaWRlcmFybyBwb3IgZWwgc2lzdGVtYQoKYGBge3J9ClN5cy5zZXRlbnYoTEFORyA9ICJlbiIpCmBgYAoKIyMjIDEuMiAtIEluc3RhbGFjacOzbiBkZSBsaWJyZXLDrWFzIChlbiBjYXNvIGRlIHNlciBuZWNlc2FyaW8pIHkgQ2FyZ2FyIGxpYnJlcsOtYXMKCmBgYHtyfQojaW5zdGFsbC5wYWNrYWdlcygidGlueXRleCIpCiMgQSBmZXcgZXhhbXBsZXMgb24gaG93IHRvIEluc3RhbGwgcGFja2FnZXMKI2luc3RhbGwucGFja2FnZXMoInRtIikgICMgZm9yIHRleHQgbWluaW5nCiNpbnN0YWxsLnBhY2thZ2VzKCJTbm93YmFsbEMiKSAjIGZvciB0ZXh0IG1hbmlwdWxhdGlvbgojaW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIikgIyB3b3JkLWNsb3VkIGdlbmVyYXRvcgpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRtKSAKbGlicmFyeShTbm93YmFsbEMpCmxpYnJhcnkod29yZGNsb3VkKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHVkcGlwZSkKbGlicmFyeShsYXR0aWNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ3Bsb3QyKQpgYGAKTGEgbGlicmVyw61hIFVkcGlwZSBwcm9wb3JjaW9uYSBhbGd1bm9zIG1vZGVsb3MgcHJlZW50cmVuYWRvcyBwYXJhIGFsZ3Vub3MgaWRpb21hcyB5IHNlIHB1ZWRlbiBkZXNjYXJnYXIgYSBsYSBjb21wdXRhZG9yYSB1dGlsaXphbmRvIGVsIGNvbWFuZG8gdWRwaXBlX2Rvd25sb2FkX21vZGVsKCkgcGFyYSBwb3N0ZXJpb3JtZW50ZSBjYXJnYXJsbyBhIHR1IGFtYmllbnRlIGRlIFIgdXRpbGl6YW5kbyBlbCBjb21hbmRvIHVkcGlwZV9sb2FkX21vZGVsKCkuCgpgYGB7cn0KbW9kZWwgPC0gdWRwaXBlX2Rvd25sb2FkX21vZGVsKGxhbmd1YWdlID0gImVuZ2xpc2giKQp1ZG1vZGVsX2VuZ2xpc2ggPC0gdWRwaXBlX2xvYWRfbW9kZWwoZmlsZSA9ICdlbmdsaXNoLWV3dC11ZC0yLjUtMTkxMjA2LnVkcGlwZScpCmBgYAoKIyMgUGFzbyAyIC0gSW1wb3J0YXIgbGEgYmFzZSBkZSBkYXRvcyBxdWUgc2Vyw6EgYW5hbGl6YWRhIHkgZW50ZW5kZXIgZWwgQ29udGV4dG8gZGVsIERhdGFzZXQKCihodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL21lZXRuYWdhZGlhL2FtYXpvbi1raW5kbGUtYm9vay1yZXZpZXctZm9yLXNlbnRpbWVudC1hbmFseXNpcykgIAoKYGBge3J9CmJkIDwtIHJlYWQuY3N2KCIvVXNlcnMvaHVnb2VucmlxdWUvRGVza3RvcC9Vbml2ZXJzaWRhZC84dm8gU2VtZXN0cmUvUGxhbmVhY2lvzIFuIEVzdHJhdGXMgWdpY2EvTTIvUiBTY3JpcHRzL2FsbF9raW5kbGVfcmV2aWV3LmNzdiIsIGhlYWRlciA9IFQsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpnbGltcHNlKGJkKQpgYGAKCiMjIyBDb250ZXh0byBkZWwgRGF0YXNldDogCgpFcyB1biBzdWJjb25qdW50byBkZSBsYXMgcmVzZcOxYXMgZGUgcHJvZHVjdG9zIGRlIGxhIGNhdGVnb3LDrWEgQW1hem9uIEtpbmRsZSBTdG9yZSwgY3VicmllbmRvIGRlc2RlIG1heW8gZGUgMTk5NiBoYXN0YSBqdWxpbyBkZSAyMDE0LgoKQ29udGVuaWRvOiAKClRpZW5lIHVuIGNvbW8gdG90YWwgZGUgcmVzZcOxYXMgOTgyLDYxOSwgZG9uZGUgY2FkYSB1c3VhcmlvIGhhIGVzY3JpdG8gYWwgbWVub3MgNSByZXNlw7FhcyB5IGNhZGEgcHJvZHVjdG8gdGllbmUgYWwgbWVub3MgNSByZXNlw7Fhcy4KCkNvbHVtbmFzIGRlbCBkYXRhc2V0OgoKLSBhc2luOiBJRCBkZWwgcHJvZHVjdG8gKGVqZW1wbG86IEIwMDBGQTY0UEspCi0gaGVscGZ1bDogQ2FsaWZpY2FjacOzbiBkZSB1dGlsaWRhZCBkZSBsYSByZXNlw7FhIChwb3IgZWplbXBsbzogMi8zKQotIHJhdGluZzogUHVudHVhY2nDs24gZGVsIHByb2R1Y3RvICgxIGEgNSBlc3RyZWxsYXMpCi0gcmV2aWV3VGV4dDogVGV4dG8gZGVsIGNvbWVudGFyaW8KLSByZXZpZXdUaW1lOiBGZWNoYSBlbiBmb3JtYXRvIGRlIHRleHRvCi0gcmV2aWV3ZXJJRDogSUQgZGVsIHVzdWFyaW8gcXVlIHJlc2XDsWEKLSByZXZpZXdlck5hbWU6IE5vbWJyZSBkZWwgdXN1YXJpbwotIHN1bW1hcnk6IFJlc3VtZW4vdMOtdHVsbyBkZSBsYSByZXNlw7FhCgojIyBQYXNvIDMgLSBIaXN0b2dyYW1hcyByZWxldmFudGVzLiAKCiMjIyBIaXN0b2dyYW1hIGRlIGxvcyBhw7FvcyAocmV2aWV3X2RhdGUpCgpgYGB7cn0KYmRfMiA8LSBiZCAlPiUKICBtdXRhdGUoCiAgICByZXZpZXdfZGF0ZSA9IG1keShyZXZpZXdUaW1lKSwgICAgICAgICAgCiAgICB5ZWFyID0gc3RyX3N1YihyZXZpZXdfZGF0ZSwxLDQpLAogICAgbW9udGggPSBzdHJfc3ViKHJldmlld19kYXRlLDYsNyksCiAgICBkYXRlID0gc3RyX3N1YihyZXZpZXdfZGF0ZSw5LDEwKSkKCmJkXzIgJT4lIGdyb3VwX2J5KHllYXIpICU+JSBjb3VudCgpICU+JSBnZ3Bsb3QoKSArIGdlb21fYmFyKGFlcyh4ID0geWVhciwgeSA9IG4sIGdyb3VwID0gMSksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIpICtsYWJzKHRpdGxlID0gIkNhbnRpZGFkIGRlIHJlc2XDsWFzIHBvciBhw7FvIiwgeCA9ICJBw7FvIiwgeSA9ICJOw7ptZXJvIGRlIHJlc2XDsWFzIikgKyB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyMgSGlzdG9ncmFtYSBkZSBsb3MgbWVzZXMgKHJldmlld19kYXRlKQpgYGB7cn0KYmRfMiAlPiUgZ3JvdXBfYnkobW9udGgpICU+JSBjb3VudCgpICU+JSBnZ3Bsb3QoKSArIGdlb21fYmFyKGFlcyh4ID0gbW9udGgsIHkgPSBuKSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRncmVlbiIpICtsYWJzKHRpdGxlID0gIkNhbnRpZGFkIGRlIHJlc2XDsWFzIHBvciBtZXMiLCB4ID0gIk1lcyIsIHkgPSAiTsO6bWVybyBkZSByZXNlw7FhcyIpICsgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMjIEhpc3RvZ3JhbWEgZGUgbGEgY2FsaWZpY2FjacOzbiAocmF0aW5nKQpgYGB7cn0KIyBDb252ZXJ0aXIgZGUgdGV4dG8gbyBmYWN0b3IgYSBudW3DqXJpY28KYmRfMiRyYXRpbmcgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoYmRfMiRyYXRpbmcpKQpiZF8yICU+JSBncm91cF9ieShyYXRpbmcpICU+JSBjb3VudCgpICU+JSBnZ3Bsb3QoKSArIGdlb21fYmFyKGFlcyh4ID0gcmF0aW5nLCB5ID0gbiksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0cGluayIpICtsYWJzKHRpdGxlID0gIkNhbnRpZGFkIGRlIFB1bnR1YWNpw7NuIGRlbCBwcm9kdWN0byAoMSBhIDUpICIsIHggPSAiUHVudHVhY2lvbiIsIHkgPSAiQ2FudGlkYWQgZGUgUHVudHVhY2nDs24iKSArIHRoZW1lX21pbmltYWwoKQpgYGAKCiMgUHJpbWVyIGNvbXBhcmF0aXZvIChBw7FvIDIwMTMgZGVsIG1lcyBFbmVybyBhIEp1bmlvKQojIyBQYXNvIDQgLSBQcmltZXIgRmlsdHJvIAoKU2UgYXBsaWNvIHVuIGZpbHRybyBkb25kZSBub3MgZW5mb2NhbW9zIGVuIGxhcyByZXNlw7FhcyBkZWwgYcOxbyAyMDEzIGR1cmFudGUgbG9zIG1lc2VzIGRlIEVuZXJvIGEgSnVuaW8uCgpgYGB7cn0KYmRfMiA8LSBiZF8yICU+JSBtdXRhdGUobW9udGggPSBhcy5udW1lcmljKG1vbnRoKSkKZmlsdHJvXzEgPC0gYmRfMiAlPiUgZmlsdGVyKHllYXIgPT0gMjAxMyAmIG1vbnRoID49IDAxICYgbW9udGggPD0gMDYpCgpmaWx0cm9fMSAlPiUgZ3JvdXBfYnkobW9udGgpICU+JSBjb3VudCgpICU+JSBnZ3Bsb3QoKSArIGdlb21fYmFyKGFlcyh4ID0gbW9udGgsIHkgPSBuKSwgc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKyBsYWJzKHRpdGxlID0gIkNhbnRpZGFkIGRlIHJlc2XDsWFzIDIwMTMgKEVuZXJvIGEgSnVuaW8pIiwgeCA9ICJNZXMiLCB5ID0gIkNhbnRpZGFkIGRlIHJlc2XDsWFzIikgKyB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyBQYXNvIDUgLSBBbsOhbGlzaXMgZGUgVGV4dG8gUHJpbWVyIEZpbHRybwoKIyMjIDUuMSAtIExpbXBpZXphIEdlbmVyYWwgZGVsIFRleHRvCgpgYGB7cn0KIyBDcmVhciBjb3JwdXMgYSBwYXJ0aXIgZGUgbGEgY29sdW1uYSByZXZpZXdUZXh0IGRlbCBkYXRhZnJhbWUgZmlsdHJvXzEKY29ycHVzMSA8LSBWQ29ycHVzKFZlY3RvclNvdXJjZShmaWx0cm9fMSRyZXZpZXdUZXh0KSkKYGBgCgpgYGB7cn0KIyBDb252ZXJ0aXIgYSBtaW7DunNjdWxhcwpjb3JwdXMxIDwtIHRtX21hcChjb3JwdXMxLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQoKIyBRdWl0YXIgbsO6bWVyb3MKY29ycHVzMSA8LSB0bV9tYXAoY29ycHVzMSwgcmVtb3ZlTnVtYmVycykKCiMgUXVpdGFyIHB1bnR1YWNpw7NuCmNvcnB1czEgPC0gdG1fbWFwKGNvcnB1czEsIHJlbW92ZVB1bmN0dWF0aW9uKQoKIyBRdWl0YXIgZXNwYWNpb3MgZW4gYmxhbmNvIGV4dHJhCmNvcnB1czEgPC0gdG1fbWFwKGNvcnB1czEsIHN0cmlwV2hpdGVzcGFjZSkKYGBgCgojIyMgNS4gMiAtIEVuY29udHJhciBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMgKG1vc3RyYXIgdGFibGEpIAoKUGFyYSBlbmNvbnRyYXIgbGFzIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcyBzZSB1c2EgbGEgJ01hdHJpeiBkZSBUZXJtaW5vcyBEb2N1bWVudG8nLCBiw6FzaWNhbWVudGUgZXMgdW5hIHRhYmxhIHF1ZSBjb250aWVuZSBsYSBmcmVjdWVuY2lhIGRlIGxhcyBwYWxhYnJhcyBwcmVzZW50ZXMgZW4gZWwgZG9jdW1lbnRvIHF1ZSBzZSBhbmFsaXphLgoKYGBge3J9CmR0bSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzMSkKbSA8LSBhcy5tYXRyaXgoZHRtKQp2IDwtIHNvcnQocm93U3VtcyhtKSxkZWNyZWFzaW5nPVRSVUUpCmQgPC0gZGF0YS5mcmFtZSh3b3JkID0gbmFtZXModiksZnJlcT12KQpoZWFkKGQsIDUwKQpgYGAKCiMjIyA1LjMgLSBPbWl0aXIgc3RvcHdvcmRzIGZyZWN1ZW50ZXMgCmBgYHtyfQojIFF1aXRhciBzdG9wd29yZHMgZW4gaW5nbMOpcwpjb3JwdXMxIDwtIHRtX21hcChjb3JwdXMxLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpCmBgYAoKIyMjIDUuNCAtIEVuY29udHJhciBkZSBudWV2byBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMgKG1vc3RyYXIgdGFibGEpIApgYGB7cn0KZHRtMSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzMSkKbTEgPC0gYXMubWF0cml4KGR0bTEpCnYxIDwtIHNvcnQocm93U3VtcyhtMSksZGVjcmVhc2luZz1UUlVFKQpkMSA8LSBkYXRhLmZyYW1lKHdvcmQgPSBuYW1lcyh2MSksZnJlcT12MSkKaGVhZChkMSwgNTApCmBgYAoKIyMjIDUuNSAtIEJ1c2NhciBhc29jaWFjaW9uZXMgcmVsZXZhbnRlcyBwYXJhIGFsIG1lbm9zIDEwIGRlIGxhcyBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMgZW4gc3VzIHRleHRvcwpgYGB7cn0KZmluZEZyZXFUZXJtcyhkdG0xLCBsb3dmcmVxID0gMjApCmBgYAoKYGBge3J9CmZpbmRBc3NvY3MoZHRtMSwgdGVybXMgPSAiYm9vayIsIGNvcmxpbWl0ID0gMC41KQpgYGAKCmBgYHtyfQpiYXJwbG90KGQxWzE6MjAsXSRmcmVxLCBsYXMgPSAyLCBuYW1lcy5hcmcgPSBkMVsxOjIwLF0kd29yZCwKICAgICAgICBjb2wgPSJsaWdodGJsdWUiLCBtYWluID0iUGFsYWJyYXMgbcOhcyBGcmVjdWVudGVzIEYxIiwKICAgICAgICB5bGFiID0gIkZyZWN1ZW5jaWFzIikKYGBgCgpMYSBwYWxhYnJhIGNvbiBtYXlvciBkb21pbmlvIGVzICJib29rIiwgc2llbmRvIGVsIHTDqXJtaW5vIG3DoXMgcmVsZXZhbnRlIHkgZnJlY3VlbnRlLgpBbCBpbmljaW8gZGUgbGEgbGlzdGEgc2UgYWdydXBhbiBsb3MgY29uY2VwdG9zIGZ1bmRhbWVudGFsZXMgKCJib29rIiwgInN0b3J5IiwgInJlYWQiKS4KQSBsbyBsYXJnbyBkZSBsYSBsaXN0YSwgbGFzIHBhbGFicmFzIHZhbiBkaXNtaW51eWVuZG8uIEVzdG8gbXVlc3RyYSBxdWUsIGRlc3B1w6lzIGRlIGxvcyBjb25jZXB0b3MgY2xhdmUsIGFwYXJlY2UgdW4gdm9jYWJ1bGFyaW8gbcOhcyB2YXJpYWRvLCBxdWUgc2lydmUgcGFyYSBkYXIgb3BpbmnDs24geSBkZXNjcmliaXIgbG9zIGVsZW1lbnRvcyBkZSBsYXMgaGlzdG9yaWFzLgoKIyMjIDUuNiAtIERlc2dsb3NhciBsYXMgcGFsYWJyYXMgZnJlY3VlbnRlcyBkZSBhY3VlcmRvIGNvbiBzdSBjbGFzaWZpY2FjacOzbiB5IGdlbmVyYXIgc3VzIHJlc3BlY3RpdmFzIGdyw6FmaWNhcyBkZSBiYXJyYXM6IApgYGB7cn0KIyBBcGxpY2FyIGFub3RhY2nDs24gbGluZ8O8w61zdGljYSBjb24gdWRwaXBlIHNvYnJlIGxhIGNvbHVtbmEgcmV2aWV3VGV4dApzMSA8LSB1ZHBpcGVfYW5ub3RhdGUodWRtb2RlbF9lbmdsaXNoLCB4ID0gZmlsdHJvXzEkcmV2aWV3VGV4dCkKIyAgQ29udmVydGlyIGEgZGF0YSBmcmFtZQp4MSA8LSBhcy5kYXRhLmZyYW1lKHMxKQpgYGAKCmBgYHtyfQpzdGF0czEgPC0gdHh0X2ZyZXEoeDEkdXBvcykKc3RhdHMxJGtleSA8LSBmYWN0b3Ioc3RhdHMxJGtleSwgbGV2ZWxzID0gcmV2KHN0YXRzMSRrZXkpKQpiYXJjaGFydChrZXkgfiBmcmVxLCBkYXRhID0gc3RhdHMxLCBjb2wgPSAib3JhbmdlIiwgCiAgICAgICAgIG1haW4gPSAiVVBPUyAoVW5pdmVyc2FsIFBhcnRzIG9mIFNwZWVjaClcbiBGcmVjdWVuY2lhIGRlIE9jdXJyZW5jaWEgRjEiLCAKICAgICAgICAgeGxhYiA9ICJGcmVjdWVuY2lhIikKYGBgCkVuIGVsIGFuYWxpc2lzIG1vcmZvc2ludGF0aWNvCkxhIGNhdGVnb3LDrWEgY29uIG1heW9yIGRvbWluaW8gZXMgTk9VTi9TdXN0YW50aXZvLCBlcyBlbCB0aXBvIGRlIHBhbGFicmEgbcOhcyByZWxldmFudGUgeSBmcmVjdWVudGUsIGluZGljYW5kbyBxdWUgZWwgdGV4dG8gc2UgY2VudHJhIGVuIG5vbWJyYXIgcGVyc29uYXMsIGx1Z2FyZXMgeSBjb3Nhcy4KQWwgaW5pY2lvIGRlIGxhIGxpc3RhIHNlIGFncnVwYW4gbGFzIHBhcnRlcyBmdW5kYW1lbnRhbGVzIHF1ZSBjb25zdHJ1eWVuIGVsIG7DumNsZW8gZGUgbGFzIG9yYWNpb25lcy4KTGEgZnJlY3VlbmNpYSBkZSBsYXMgY2F0ZWdvcsOtYXMgdmEgZGlzbWludXllbmRvLiBFc3RvIHJlZmxlamEgdW5hIGVzdHJ1Y3R1cmEgbGluZ8O8w61zdGljYSB0w61waWNhOiBlbCB0ZXh0byBzZSBjb25zdHJ1eWUgc29icmUgdW5hIGJhc2UgZGUgc3VzdGFudGl2b3MgeSB2ZXJib3MKCiMjIyMgbyBTdXN0YW50aXZvcyAKYGBge3J9CnN0YXRzMSA8LSBzdWJzZXQoeDEsIHVwb3MgJWluJSBjKCJOT1VOIikpIApzdGF0czEgPC0gdHh0X2ZyZXEoc3RhdHMxJHRva2VuKQpzdGF0czEka2V5IDwtIGZhY3RvcihzdGF0czEka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMxJGtleSkpCmJhcmNoYXJ0KGtleSB+IGZyZXEsIGRhdGEgPSBoZWFkKHN0YXRzMSwgMjApLCBjb2wgPSAiY2FkZXRibHVlIiwgCiAgICAgICAgIG1haW4gPSAiU3VzdGFudGl2b3MgbcOhcyBDb211bmVzIEYxIiwgeGxhYiA9ICJGcmVjdWVuY2lhIikKYGBgCkxvcyBzdXN0YW50aXZvcyBtw6FzIGZyZWN1ZW50ZXMgY29tbyBib29rLCBzdG9yeSwgY2hhcmFjdGVycywgc2VyaWVzIHkgYXV0aG9yIHJlZmxlamFuIHF1ZSBsYXMgcmVzZcOxYXMgZGUgbG9zIHVzdWFyaW9zIGdpcmFuIHByaW5jaXBhbG1lbnRlIGVuIHRvcm5vIGFsIGNvbnRlbmlkbyBuYXJyYXRpdm8gbcOhcyBxdWUgYWwgZGlzcG9zaXRpdm8gbyBsYSBwbGF0YWZvcm1hLgoKIyMjIyBvIEFkamV0aXZvcyAKYGBge3J9CnN0YXRzMSA8LSBzdWJzZXQoeDEsIHVwb3MgJWluJSBjKCJBREoiKSkgCnN0YXRzMSA8LSB0eHRfZnJlcShzdGF0czEkdG9rZW4pCnN0YXRzMSRrZXkgPC0gZmFjdG9yKHN0YXRzMSRrZXksIGxldmVscyA9IHJldihzdGF0czEka2V5KSkKYmFyY2hhcnQoa2V5IH4gZnJlcSwgZGF0YSA9IGhlYWQoc3RhdHMxLCAyMCksIGNvbCA9ICJwdXJwbGUiLCAKICAgICAgICAgbWFpbiA9ICJBZGpldGl2b3MgbcOhcyBDb211bmVzIEYxIiwgeGxhYiA9ICJGcmVxIikKYGBgCkxvcyBhZGpldGl2b3MgZG9taW5hbnRlcyBjb21vIGdvb2QsIGdyZWF0LCBpbnRlcmVzdGluZywgeSBiZXR0ZXIgbXVlc3RyYW4gdW5hIHRlbmRlbmNpYSBwb3NpdGl2YSBnZW5lcmFsIGVuIGxhcyByZXNlw7FhcywgbG8gcXVlIGluZGljYSBhbHRhIHNhdGlzZmFjY2nDs24gY29uIGxvcyBsaWJyb3MgYW5hbGl6YWRvcyBhc2kgY29tbyBsYSBwcmVzZW5jaWEgZGUgYmFkIHkgZGlmZmVyZW50LCBhdW5xdWUgbWVub3MgZnJlY3VlbnRlLCBldmlkZW5jaWEgdW5hIG1pbm9yw61hIGNyw610aWNhLCBwb3NpYmxlbWVudGUgYXNvY2lhZGEgYSByZXNlw7FhcyBuZWdhdGl2YXMgCgojIyMjIG8gVmVyYm9zIApgYGB7cn0Kc3RhdHMxIDwtIHN1YnNldCh4MSwgdXBvcyAlaW4lIGMoIlZFUkIiKSkgCnN0YXRzMSA8LSB0eHRfZnJlcShzdGF0czEkdG9rZW4pCnN0YXRzMSRrZXkgPC0gZmFjdG9yKHN0YXRzMSRrZXksIGxldmVscyA9IHJldihzdGF0czEka2V5KSkKYmFyY2hhcnQoa2V5IH4gZnJlcSwgZGF0YSA9IGhlYWQoc3RhdHMxLCAyMCksIGNvbCA9ICJnb2xkIiwgCiAgICAgICAgIG1haW4gPSAiVmVyYm9zIG3DoXMgQ29tdW5lcyBGMSIsIHhsYWIgPSAiRnJlY3VlbmNpYSIpCmBgYApMb3MgdmVyYm9zIG3DoXMgZnJlY3VlbnRlcywgZW5jYWJlemFkb3MgcG9yIHJlYWQsIGhhdmUsIGdldCwgbGlrZSwgbG92ZSB5IGVuam95ZWQsIHJlZmxlamFuIHF1ZSBsYXMgcmVzZcOxYXMgZXN0w6FuIGNlbnRyYWRhcyBlbiBsYSBleHBlcmllbmNpYSBkZSBsZWN0dXJhIHBlcnNvbmFsIHkgZW4gbGEgdmFsb3JhY2nDs24gZW1vY2lvbmFsIGRlbCBsaWJyby4gCgoKIyMjIDUuNyAtIEFwbGljYXIgZWwgYWxnb3JpdG1vIFJBS0UgcGFyYSBkZXRlY3RhciBmcmFzZXMgY29tw7pubWVudGUgdXRpbGl6YWRhcyBlbiB0dSBiYXNlIGRlIGRhdG9zLiAKRXh0cmFjY2nDs24gYXV0b23DoXRpY2EgZGUgcGFsYWJyYXMgY2xhdmVzIHV0aWxpemFuZG8gdW4gYWxnb3JpdG1vIHByZWVudHJlbmFkbzogUkFLRSAoUmFwaWQgQXV0b21hdGljIEtleXdvcmQgRXh0cmFjdGlvbikuIEVuIGVzZW5jaWEsIGRpY2hvIGFsZ29yaXRtbyBidXNjYSBkZXRlcm1pbmFyIGZyYXNlcyBjbGF2ZSBkZW50cm8gZGUgdW4gdGV4dG8gYWwgYW5hbGl6YXIgbGEgZnJlY3VlbmNpYSBjb24gbGFzIHF1ZSBjaWVydGFzIHBhbGFicmFzIGFwYXJlY2VuIGRlIG1hbmVyYSBjb25qdW50YSBkZW50cm8gZGVsIHRleHRvIGVuIGN1ZXN0acOzbi4KCmBgYHtyfQpzdGF0czEgPC0ga2V5d29yZHNfcmFrZSh4ID0geDEsIHRlcm0gPSAibGVtbWEiLCBncm91cCA9ICJkb2NfaWQiLCAKICAgICAgICAgICAgICAgICAgICAgICByZWxldmFudCA9IHgxJHVwb3MgJWluJSBjKCJOT1VOIiwgIkFESiIpKQpzdGF0czEka2V5IDwtIGZhY3RvcihzdGF0czEka2V5d29yZCwgbGV2ZWxzID0gcmV2KHN0YXRzMSRrZXl3b3JkKSkKYmFyY2hhcnQoa2V5IH4gcmFrZSwgZGF0YSA9IGhlYWQoc3Vic2V0KHN0YXRzMSwgZnJlcSA+IDMpLCAyMCksIGNvbCA9ICJyZWQiLCAKICAgICAgICAgbWFpbiA9ICJLZXl3b3JkcyBpZGVudGlmaWNhZG9zIHBvciBSQUtFIEYxIiwgCiAgICAgICAgIHhsYWIgPSAiUmFrZSIpCmBgYApMYXMgZnJhc2VzIG3DoXMgY29tdW5lcyBpZGVudGlmaWNhZGFzIHBvciBSQUtFLCBjb21vIOKAnGdyYW1tYXRpY2FsIGVycm9y4oCdLCDigJxwYW5pYyBhdHRhY2vigJ0sIOKAnGZlbWFsZSBsZWFk4oCdIG8g4oCcaGlzdG9yaWNhbCByb21hbmNl4oCdLCBtdWVzdHJhbiB1bmEgY29tYmluYWNpw7NuIGRlIHRlbWFzIG5hcnJhdGl2b3MgeSBwZXJjZXBjaW9uZXMgY3LDrXRpY2FzCgpgYGB7cn0KeDEkcGhyYXNlX3RhZyA8LSBhc19waHJhc2VtYWNoaW5lKHgxJHVwb3MsIHR5cGUgPSAidXBvcyIpCnN0YXRzMSA8LSBrZXl3b3Jkc19waHJhc2VzKHggPSB4MSRwaHJhc2VfdGFnLCB0ZXJtID0gdG9sb3dlcih4MSR0b2tlbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiKEF8TikqTihQK0QqKEF8TikqTikqIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgaXNfcmVnZXggPSBUUlVFLCBkZXRhaWxlZCA9IEZBTFNFKQpzdGF0czEgPC0gc3Vic2V0KHN0YXRzMSwgbmdyYW0gPiAxICYgZnJlcSA+IDMpCnN0YXRzMSRrZXkgPC0gZmFjdG9yKHN0YXRzMSRrZXl3b3JkLCBsZXZlbHMgPSByZXYoc3RhdHMxJGtleXdvcmQpKQpiYXJjaGFydChrZXkgfiBmcmVxLCBkYXRhID0gaGVhZChzdGF0czEsIDIwKSwgY29sID0gIm1hZ2VudGEiLCAKICAgICAgICAgbWFpbiA9ICJLZXl3b3JkcyAtIEZyYXNlcyBub21pbmFsZXMgRjEiLCB4bGFiID0gIkZyZWN1ZW5jaWEiKQpgYGAKTGFzIGZyYXNlcyBtw6FzIGZyZWN1ZW50ZXMgY29tbyDigJx0aGF0IEnigJ0sIOKAnHN0b3J5IGxpbmXigJ0sIOKAnHNob3J0IHN0b3J54oCdLCDigJxzZXggc2NlbmVz4oCdLCDigJxtYWluIGNoYXJhY3RlcuKAnSB5IOKAnGdvb2QgYm9va+KAnSByZWZsZWphbiBxdWUgbG9zIHVzdWFyaW9zIHNlIGVuZm9jYW4gcHJpbmNpcGFsbWVudGUgZW4gYXNwZWN0b3MgbmFycmF0aXZvcyB5IGRlIGNvbnRlbmlkbyBhc2kgdGFtYmnDqW4gc2UgZW5mb2NhbiBlbiBvcGluaW9uZXMgZGUgbG9zIGxlY3RvcmVzLgoKIyMjIDUuOCAtIEdlbmVyYXIgdW5hIG8gZG9zIG51YmVzIGRlIHBhbGFicmFzLiAKCk51YmUgY29uIG1pbmltbyBkZSBmcmVjdWVuY2lhIGRlIDUwIHZlY2VzIGxhIHBhbGFicmEKYGBge3J9CnNldC5zZWVkKDIwMjUwNDAyKQp3b3JkY2xvdWQod29yZHMgPSBkMSR3b3JkLCBmcmVxID0gZDEkZnJlcSwgbWluLmZyZXEgPSA1MCwKICAgICAgICAgIG1heC53b3Jkcz1JbmYsIHJhbmRvbS5vcmRlcj1ULCByb3QucGVyPTAuNSwgCiAgICAgICAgICBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkKYGBgCiBTZSBvYnNlcnZhIHVuYSBncmFuIHZhcmllZGFkIGzDqXhpY2EsIGRvbmRlIGRlc3RhY2FuIHBhbGFicmFzIGNvbW8gYm9vaywgc3RvcnksIGNoYXJhY3RlcnMsIHNlcmllcywgZ29vZCwgcmVhZGluZywgbG92ZSwgeSBqdXN0LiBFc3RvIG11ZXN0cmEgcXVlIGVsIGNvcnB1cyBkZSByZXNlw7FhcyBlcyBhbXBsaW8geSBhYmFyY2EgbcO6bHRpcGxlcyB0ZW1hcyByZWxhY2lvbmFkb3MgY29uIGxhIGxlY3R1cmEsIHRyYW1hLCBwZXJzb25hamVzIHkgZW1vY2lvbmVzLgogCk51YmUgY29uIG1pbmltbyBkZSBmcmVjdWVuY2lhIGRlIDE1MCB2ZWNlcyBsYSBwYWxhYnJhCmBgYHtyfQpzZXQuc2VlZCgyMDI1MDQwMikKd29yZGNsb3VkKHdvcmRzID0gZDEkd29yZCwgZnJlcSA9IGQxJGZyZXEsIG1pbi5mcmVxID0gMTUwLAogICAgICAgICAgbWF4LndvcmRzPUluZiwgcmFuZG9tLm9yZGVyPVQsIHJvdC5wZXI9MC41LCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQpgYGAKRW4gZXN0YSBudWJlIHNlIGNvbmNlbnRyYW4gcGFsYWJyYXMgY29tbyBib29rLCBzdG9yeSwgcmVhZCwgY2hhcmFjdGVycywgeSBnb29kLCBsbyBxdWUgc3VnaWVyZSBxdWUgbGEgbWF5b3LDrWEgZGUgbG9zIGNvbWVudGFyaW9zIGdpcmFuIGVuIHRvcm5vIGEgbGEgZXhwZXJpZW5jaWEgZ2VuZXJhbCBkZSBsZWN0dXJhIHkgbGEgY2FsaWRhZCBuYXJyYXRpdmEuCgojIFNlZ3VuZG8gY29tcGFyYXRpdm8gKEHDsW8gMjAxMyBkZWwgbWVzIEp1bGlvIGEgRGljaWVtYnJlKQojIyBQYXNvIDQgLSBQcmltZXIgRmlsdHJvIAoKU2UgYXBsaWNvIHVuIGZpbHRybyBkb25kZSBub3MgZW5mb2NhbW9zIGVuIGxhcyByZXNlw7FhcyBkZWwgYcOxbyAyMDEzIGR1cmFudGUgbG9zIG1lc2VzIGRlIEp1bGlvIGEgRGljaWVtYnJlLgoKYGBge3J9CmZpbHRyb18yIDwtIGJkXzIgJT4lIGZpbHRlcih5ZWFyID09IDIwMTMgJiBtb250aCA+PSAwNyAmIG1vbnRoIDw9IDEyKQoKZmlsdHJvXzIgJT4lIGdyb3VwX2J5KG1vbnRoKSAlPiUgY291bnQoKSAlPiUgZ2dwbG90KCkgKyBnZW9tX2JhcihhZXMoeCA9IG1vbnRoLCB5ID0gbiksIHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIpICsgbGFicyh0aXRsZSA9ICJDYW50aWRhZCBkZSByZXNlw7FhcyAyMDEzIChKdWxpbyBhIERpY2llbWJyZSkiLCB4ID0gIk1lcyIsIHkgPSAiQ2FudGlkYWQgZGUgcmVzZcOxYXMiKSArIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIFBhc28gNSAtIEFuw6FsaXNpcyBkZSBUZXh0byBQcmltZXIgRmlsdHJvCgojIyMgNS4xIC0gTGltcGllemEgR2VuZXJhbCBkZWwgVGV4dG8KCmBgYHtyfQojIENyZWFyIGNvcnB1cyBhIHBhcnRpciBkZSBsYSBjb2x1bW5hIHJldmlld1RleHQgZGVsIGRhdGFmcmFtZSBmaWx0cm9fMQpjb3JwdXMyIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKGZpbHRyb18yJHJldmlld1RleHQpKQpgYGAKCmBgYHtyfQojIENvbnZlcnRpciBhIG1pbsO6c2N1bGFzCmNvcnB1czIgPC0gdG1fbWFwKGNvcnB1czIsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpCgojIFF1aXRhciBuw7ptZXJvcwpjb3JwdXMyIDwtIHRtX21hcChjb3JwdXMyLCByZW1vdmVOdW1iZXJzKQoKIyBRdWl0YXIgcHVudHVhY2nDs24KY29ycHVzMiA8LSB0bV9tYXAoY29ycHVzMiwgcmVtb3ZlUHVuY3R1YXRpb24pCgojIFF1aXRhciBlc3BhY2lvcyBlbiBibGFuY28gZXh0cmEKY29ycHVzMiA8LSB0bV9tYXAoY29ycHVzMiwgc3RyaXBXaGl0ZXNwYWNlKQpgYGAKCiMjIyA1LiAyIC0gRW5jb250cmFyIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcyAobW9zdHJhciB0YWJsYSkgCgpQYXJhIGVuY29udHJhciBsYXMgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIHNlIHVzYSBsYSAnTWF0cml6IGRlIFRlcm1pbm9zIERvY3VtZW50bycsIGLDoXNpY2FtZW50ZSBlcyB1bmEgdGFibGEgcXVlIGNvbnRpZW5lIGxhIGZyZWN1ZW5jaWEgZGUgbGFzIHBhbGFicmFzIHByZXNlbnRlcyBlbiBlbCBkb2N1bWVudG8gcXVlIHNlIGFuYWxpemEuCgpgYGB7cn0KZHRtMiA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzMikKbTIgPC0gYXMubWF0cml4KGR0bTIpCnYyIDwtIHNvcnQocm93U3VtcyhtMiksZGVjcmVhc2luZz1UUlVFKQpkMiA8LSBkYXRhLmZyYW1lKHdvcmQgPSBuYW1lcyh2MiksZnJlcT12MikKaGVhZChkMiwgNTApCmBgYAoKIyMjIDUuMyAtIE9taXRpciBzdG9wd29yZHMgZnJlY3VlbnRlcyAKYGBge3J9CiMgUXVpdGFyIHN0b3B3b3JkcyBlbiBpbmdsw6lzCmNvcnB1czIgPC0gdG1fbWFwKGNvcnB1czIsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkKYGBgCgojIyMgNS40IC0gRW5jb250cmFyIGRlIG51ZXZvIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcyAobW9zdHJhciB0YWJsYSkgCmBgYHtyfQpkdG0zIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChjb3JwdXMyKQptMyA8LSBhcy5tYXRyaXgoZHRtMykKdjMgPC0gc29ydChyb3dTdW1zKG0zKSxkZWNyZWFzaW5nPVRSVUUpCmQzIDwtIGRhdGEuZnJhbWUod29yZCA9IG5hbWVzKHYzKSxmcmVxPXYzKQpoZWFkKGQzLCA1MCkKYGBgCgojIyMgNS41IC0gQnVzY2FyIGFzb2NpYWNpb25lcyByZWxldmFudGVzIHBhcmEgYWwgbWVub3MgMTAgZGUgbGFzIHBhbGFicmFzIG3DoXMgZnJlY3VlbnRlcyBlbiBzdXMgdGV4dG9zCmBgYHtyfQpmaW5kRnJlcVRlcm1zKGR0bTMsIGxvd2ZyZXEgPSAyMCkKYGBgCgpgYGB7cn0KZmluZEFzc29jcyhkdG0zLCB0ZXJtcyA9ICJib29rIiwgY29ybGltaXQgPSAwLjIpCmBgYAoKYGBge3J9CmJhcnBsb3QoZDNbMToyMCxdJGZyZXEsIGxhcyA9IDIsIG5hbWVzLmFyZyA9IGQzWzE6MjAsXSR3b3JkLAogICAgICAgIGNvbCA9ImxpZ2h0Ymx1ZSIsIG1haW4gPSJQYWxhYnJhcyBtw6FzIEZyZWN1ZW50ZXMgRjIiLAogICAgICAgIHlsYWIgPSAiRnJlY3VlbmNpYXMiKQpgYGAKRW4gZWwgc2VndW5kbyBzZW1lc3RyZSBkZSAyMDEzLCBsYXMgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIHNpZ3VlbiB1bmEgdGVuZGVuY2lhIG11eSBzaW1pbGFyIGFsIHByaW1lciBmaWx0cm8sIGNvbiDigJxib29r4oCdLCDigJxzdG9yeeKAnSwg4oCccmVhZOKAnSB5IOKAnG9uZeKAnSBjb21vIGxvcyB0w6lybWlub3MgZG9taW5hbnRlcy4gRXN0byBjb25maXJtYSBxdWUgbG9zIHVzdWFyaW9zIGNvbnRpbsO6YW4gY2VudHJhbmRvIHN1cyByZXNlw7FhcyBlbiBsYSBleHBlcmllbmNpYSBkZSBsZWN0dXJhIHkgZW4gbGEgY2FsaWRhZCBuYXJyYXRpdmEuCgojIyMgNS42IC0gRGVzZ2xvc2FyIGxhcyBwYWxhYnJhcyBmcmVjdWVudGVzIGRlIGFjdWVyZG8gY29uIHN1IGNsYXNpZmljYWNpw7NuIHkgZ2VuZXJhciBzdXMgcmVzcGVjdGl2YXMgZ3LDoWZpY2FzIGRlIGJhcnJhczogCmBgYHtyfQojIEFwbGljYXIgYW5vdGFjacOzbiBsaW5nw7zDrXN0aWNhIGNvbiB1ZHBpcGUgc29icmUgbGEgY29sdW1uYSByZXZpZXdUZXh0CnMyIDwtIHVkcGlwZV9hbm5vdGF0ZSh1ZG1vZGVsX2VuZ2xpc2gsIHggPSBmaWx0cm9fMiRyZXZpZXdUZXh0KQojICBDb252ZXJ0aXIgYSBkYXRhIGZyYW1lCngyIDwtIGFzLmRhdGEuZnJhbWUoczIpCmBgYAoKYGBge3J9CnN0YXRzMiA8LSB0eHRfZnJlcSh4MiR1cG9zKQpzdGF0czIka2V5IDwtIGZhY3RvcihzdGF0czIka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMyJGtleSkpCmJhcmNoYXJ0KGtleSB+IGZyZXEsIGRhdGEgPSBzdGF0czIsIGNvbCA9ICJvcmFuZ2UiLCAKICAgICAgICAgbWFpbiA9ICJVUE9TIChVbml2ZXJzYWwgUGFydHMgb2YgU3BlZWNoKVxuIEZyZWN1ZW5jaWEgZGUgT2N1cnJlbmNpYSBGMiIsIAogICAgICAgICB4bGFiID0gIkZyZWN1ZW5jaWEiKQpgYGAKRWwgYW7DoWxpc2lzIG1vcmZvc2ludMOhY3RpY28gbXVlc3RyYSBxdWUgbGFzIGNhdGVnb3LDrWFzIG3DoXMgZnJlY3VlbnRlcyBlbiBsYXMgcmVzZcOxYXMgc29uIGxvcyBzdXN0YW50aXZvcyAoTk9VTiksIHNlZ3VpZG9zIHBvciB2ZXJib3MgKFZFUkIpIHkgcHJvbm9tYnJlcyAoUFJPTikuIEVzdGUgcGF0csOzbiBjb25maXJtYSBxdWUgbG9zIHVzdWFyaW9zIGVzdHJ1Y3R1cmFuIHN1cyByZXNlw7FhcyBlbiB0b3JubyBhIHRlbWFzIGNvbmNyZXRvcyAobGlicm9zLCBoaXN0b3JpYXMsIHBlcnNvbmFqZXMpIGFjb21wYcOxYWRvcyBkZSBhY2Npb25lcyBlIGltcHJlc2lvbmVzIHBlcnNvbmFsZXMuTG9zIGRlbWFzIHZhbG9yZXMgc29uIGluZGljYWRvcmVzIGRlIHVuIGxlbmd1YWplIG5hdHVyYWwgCgojIyMjIG8gU3VzdGFudGl2b3MgCmBgYHtyfQpzdGF0czIgPC0gc3Vic2V0KHgyLCB1cG9zICVpbiUgYygiTk9VTiIpKSAKc3RhdHMyIDwtIHR4dF9mcmVxKHN0YXRzMiR0b2tlbikKc3RhdHMyJGtleSA8LSBmYWN0b3Ioc3RhdHMyJGtleSwgbGV2ZWxzID0gcmV2KHN0YXRzMiRrZXkpKQpiYXJjaGFydChrZXkgfiBmcmVxLCBkYXRhID0gaGVhZChzdGF0czIsIDIwKSwgY29sID0gImNhZGV0Ymx1ZSIsIAogICAgICAgICBtYWluID0gIlN1c3RhbnRpdm9zIG3DoXMgQ29tdW5lcyBGMiIsIHhsYWIgPSAiRnJlY3VlbmNpYSIpCmBgYApzZSB2dWVsdmUgYSBjb25maXJtYXIgdW5hIGNvbnRpbnVpZGFkIGVuIGVsIGZvY28gbmFycmF0aXZvIHlhIG9ic2VydmFkbyBlbiBlbCBwcmltZXIgZmlsdHJvLiBMb3MgbGVjdG9yZXMgc2lndWVuIGNlbnRyYW5kbyBzdXMgcmVzZcOxYXMgZW4gbG9zIGVsZW1lbnRvcyBmdW5kYW1lbnRhbGVzIGRlIGxhIGV4cGVyaWVuY2lhIGxpdGVyYXJpYTogZWwgbGlicm8gZW4gc8OtLCBsYSBoaXN0b3JpYSwgbG9zIHBlcnNvbmFqZXMgeSBlbCBhdXRvci4KCiMjIyMgbyBBZGpldGl2b3MgCmBgYHtyfQpzdGF0czIgPC0gc3Vic2V0KHgyLCB1cG9zICVpbiUgYygiQURKIikpIApzdGF0czIgPC0gdHh0X2ZyZXEoc3RhdHMyJHRva2VuKQpzdGF0czIka2V5IDwtIGZhY3RvcihzdGF0czIka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMyJGtleSkpCmJhcmNoYXJ0KGtleSB+IGZyZXEsIGRhdGEgPSBoZWFkKHN0YXRzMiwgMjApLCBjb2wgPSAicHVycGxlIiwgCiAgICAgICAgIG1haW4gPSAiQWRqZXRpdm9zIG3DoXMgQ29tdW5lcyBGMiIsIHhsYWIgPSAiRnJlY3VlbmNpYSIpCmBgYApMb3MgYWRqZXRpdm9zIHByZWRvbWluYW50ZXMgY29tbyBnb29kLCBncmVhdCwgaW50ZXJlc3RpbmcsIHkgYmV0dGVyIGV2aWRlbmNpYW4gcXVlIGxhIHBlcmNlcGNpw7NuIGdlbmVyYWwgZGVsIHDDumJsaWNvIHNpZ3VlIHNpZW5kbyBwb3NpdGl2YSwgZGVzdGFjYW5kbyBsYSBjYWxpZGFkIHkgZGlzZnJ1dGUgZGUgbGFzIG9icmFzLiBMYSBhbHRhIGZyZWN1ZW5jaWEgZGUgZ29vZCB5IGdyZWF0IHJlYWZpcm1hIGxhIHNhdGlzZmFjY2nDs24gbGVjdG9yYSwgbWllbnRyYXMgcXVlIGludGVyZXN0aW5nIHN1Z2llcmUgdW4gaW50ZXLDqXMgc29zdGVuaWRvIGVuIGxhIHRyYW1hIG8gdGVtw6F0aWNhLgoKIyMjIyBvIFZlcmJvcyAKYGBge3J9CnN0YXRzMiA8LSBzdWJzZXQoeDIsIHVwb3MgJWluJSBjKCJWRVJCIikpIApzdGF0czIgPC0gdHh0X2ZyZXEoc3RhdHMyJHRva2VuKQpzdGF0czIka2V5IDwtIGZhY3RvcihzdGF0czIka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMyJGtleSkpCmJhcmNoYXJ0KGtleSB+IGZyZXEsIGRhdGEgPSBoZWFkKHN0YXRzMiwgMjApLCBjb2wgPSAiZ29sZCIsIAogICAgICAgICBtYWluID0gIlZlcmJvcyBtw6FzIENvbXVuZXMgRjIiLCB4bGFiID0gIkZyZWN1ZW5jaWEiKQpgYGAKTXV5IHBhcmVjaWRvIGEgZWwgRmlsdHJvIDEgRWwgdmVyYm8gbcOhcyBmcmVjdWVudGUsIHJlYWQsIGRvbWluYSBhbXBsaWFtZW50ZSBsYSBkaXN0cmlidWNpw7NuLCBzZWd1aWRvIGRlIGhhdmUsIGdldCwgaGFkIHkgcmVhZGluZywgbG8gcXVlIGNvbmZpcm1hIHF1ZSBsYXMgcmVzZcOxYXMgZ2lyYW4gZW4gdG9ybm8gYSBsYSBhY2Npw7NuIGRlIGxlZXIgeSBsYSBleHBlcmllbmNpYSBkZSBsZWN0dXJhLiBFc3RvIHJlZnVlcnphIGxhIGlkZWEgZGUgcXVlIGxvcyB1c3VhcmlvcyBubyBzb2xvIGV2YWzDumFuIGVsIHByb2R1Y3RvLCBzaW5vIHF1ZSBuYXJyYW4gc3Ugdml2ZW5jaWEgcGVyc29uYWwgY29uIGVsIGxpYnJvLgoKIyMjIDUuNyAtIEFwbGljYXIgZWwgYWxnb3JpdG1vIFJBS0UgcGFyYSBkZXRlY3RhciBmcmFzZXMgY29tw7pubWVudGUgdXRpbGl6YWRhcyBlbiB0dSBiYXNlIGRlIGRhdG9zLiAKRXh0cmFjY2nDs24gYXV0b23DoXRpY2EgZGUgcGFsYWJyYXMgY2xhdmVzIHV0aWxpemFuZG8gdW4gYWxnb3JpdG1vIHByZWVudHJlbmFkbzogUkFLRSAoUmFwaWQgQXV0b21hdGljIEtleXdvcmQgRXh0cmFjdGlvbikuIEVuIGVzZW5jaWEsIGRpY2hvIGFsZ29yaXRtbyBidXNjYSBkZXRlcm1pbmFyIGZyYXNlcyBjbGF2ZSBkZW50cm8gZGUgdW4gdGV4dG8gYWwgYW5hbGl6YXIgbGEgZnJlY3VlbmNpYSBjb24gbGFzIHF1ZSBjaWVydGFzIHBhbGFicmFzIGFwYXJlY2VuIGRlIG1hbmVyYSBjb25qdW50YSBkZW50cm8gZGVsIHRleHRvIGVuIGN1ZXN0acOzbi4KCmBgYHtyfQpzdGF0czIgPC0ga2V5d29yZHNfcmFrZSh4ID0geDIsIHRlcm0gPSAibGVtbWEiLCBncm91cCA9ICJkb2NfaWQiLCAKICAgICAgICAgICAgICAgICAgICAgICByZWxldmFudCA9IHgyJHVwb3MgJWluJSBjKCJOT1VOIiwgIkFESiIpKQpzdGF0czIka2V5IDwtIGZhY3RvcihzdGF0czIka2V5d29yZCwgbGV2ZWxzID0gcmV2KHN0YXRzMiRrZXl3b3JkKSkKYmFyY2hhcnQoa2V5IH4gcmFrZSwgZGF0YSA9IGhlYWQoc3Vic2V0KHN0YXRzMiwgZnJlcSA+IDMpLCAyMCksIGNvbCA9ICJyZWQiLCAKICAgICAgICAgbWFpbiA9ICJLZXl3b3JkcyBpZGVudGlmaWNhZG9zIHBvciBSQUtFIiwgCiAgICAgICAgIHhsYWIgPSAiUmFrZSIpCmBgYApMYXMgZnJhc2VzIGV4dHJhw61kYXMgcG9yIFJBS0UgcmV2ZWxhbiBsb3MgdGVtYXMgbcOhcyBlc3BlY8OtZmljb3MgeSByZWN1cnJlbnRlcyBlbiBsYXMgcmVzZcOxYXMgZGVsIHNlZ3VuZG8gc2VtZXN0cmUuIEVudHJlIGxhcyBtw6FzIGRlc3RhY2FkYXMgc2UgZW5jdWVudHJhbiDigJxoaWdoIHNjaG9vbOKAnSwg4oCcZmFpcnkgdGFsZeKAnSwg4oCcd3JpdGUgc3R5bGXigJ0sIHkg4oCcb2xkIGZhc2hpb25lZOKAnSwgbG8gcXVlIGluZGljYSB1bmEgZnVlcnRlIHByZXNlbmNpYSBkZSBvYnJhcyBhbWJpZW50YWRhcyBlbiBjb250ZXh0b3MganV2ZW5pbGVzLgoKYGBge3J9CngyJHBocmFzZV90YWcgPC0gYXNfcGhyYXNlbWFjaGluZSh4MiR1cG9zLCB0eXBlID0gInVwb3MiKQpzdGF0czIgPC0ga2V5d29yZHNfcGhyYXNlcyh4ID0geDIkcGhyYXNlX3RhZywgdGVybSA9IHRvbG93ZXIoeDIkdG9rZW4pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIihBfE4pKk4oUCtEKihBfE4pKk4pKiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGlzX3JlZ2V4ID0gVFJVRSwgZGV0YWlsZWQgPSBGQUxTRSkKc3RhdHMyIDwtIHN1YnNldChzdGF0czIsIG5ncmFtID4gMSAmIGZyZXEgPiAzKQpzdGF0czIka2V5IDwtIGZhY3RvcihzdGF0czIka2V5d29yZCwgbGV2ZWxzID0gcmV2KHN0YXRzMiRrZXl3b3JkKSkKYmFyY2hhcnQoa2V5IH4gZnJlcSwgZGF0YSA9IGhlYWQoc3RhdHMyLCAyMCksIGNvbCA9ICJtYWdlbnRhIiwgCiAgICAgICAgIG1haW4gPSAiS2V5d29yZHMgLSBGcmFzZXMgbm9taW5hbGVzIEYyICIsIHhsYWIgPSAiRnJlY3VlbmNpYSIpCmBgYApMYXMgZnJhc2VzIG5vbWluYWxlcyBtw6FzIGZyZWN1ZW50ZXMsIGNvbW8g4oCcc2hvcnQgc3RvcnnigJ0sIOKAnGdvb2QgcmVhZOKAnSwg4oCcc2V4IHNjZW5lc+KAnSwg4oCcZ3JlYXQgc3RvcnnigJ0sIHkg4oCcc3RvcnkgbGluZeKAnSwgcmV2ZWxhbiBxdWUgbGFzIHJlc2XDsWFzIGRlbCBzZWd1bmRvIHNlbWVzdHJlIG1hbnRpZW5lbiB1biBmb2NvIGZ1ZXJ0ZSBlbiBsYSBuYXJyYXRpdmEsIGVsIGVudHJldGVuaW1pZW50byB5IGxhIHRlbcOhdGljYSByb23DoW50aWNhIG8gZXLDs3RpY2EuCgojIyMgNS44IC0gR2VuZXJhciB1bmEgbyBkb3MgbnViZXMgZGUgcGFsYWJyYXMuIAoKTnViZSBjb24gbWluaW1vIGRlIGZyZWN1ZW5jaWEgZGUgNTAgdmVjZXMgbGEgcGFsYWJyYQpgYGB7cn0Kc2V0LnNlZWQoMjAyNTA0MDIpCndvcmRjbG91ZCh3b3JkcyA9IGQzJHdvcmQsIGZyZXEgPSBkMyRmcmVxLCBtaW4uZnJlcSA9IDUwLAogICAgICAgICAgbWF4LndvcmRzPUluZiwgcmFuZG9tLm9yZGVyPVQsIHJvdC5wZXI9MC41LCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQpgYGAKc2Ugb2JzZXJ2YSB1bmEgYW1wbGlhIHZhcmllZGFkIGRlIHTDqXJtaW5vcyBjb21vIGJvb2ssIHN0b3J5LCBsb3ZlLCBjaGFyYWN0ZXIsIGdyZWF0LCBnb29kLCBsaWtlLCBhdXRob3IgeSBzZXJpZXMsIGxvIHF1ZSByZWZsZWphIGxhIGRpdmVyc2lkYWQgdGVtw6F0aWNhIHkgZW1vY2lvbmFsIGRlIGxhcyByZXNlw7Fhcy4gTG9zIGxlY3RvcmVzIGhhYmxhbiB0YW50byBkZSBsYSBoaXN0b3JpYSB5IHN1cyBwZXJzb25hamVzIGNvbW8gZGUgbGEgY2FsaWRhZCBuYXJyYXRpdmEKCk51YmUgY29uIG1pbmltbyBkZSBmcmVjdWVuY2lhIGRlIDEwMCB2ZWNlcyBsYSBwYWxhYnJhCmBgYHtyfQpzZXQuc2VlZCgyMDI1MDQwMikKd29yZGNsb3VkKHdvcmRzID0gZDMkd29yZCwgZnJlcSA9IGQzJGZyZXEsIG1pbi5mcmVxID0gMTAwLAogICAgICAgICAgbWF4LndvcmRzPUluZiwgcmFuZG9tLm9yZGVyPVQsIHJvdC5wZXI9MC41LCAKICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQpgYGAKbGEgbnViZSBzZSBjb25jZW50cmEgZW4gbG9zIHTDqXJtaW5vcyBtw6FzIHJlcHJlc2VudGF0aXZvczogYm9vaywgc3RvcnksIGdvb2QsIGxpa2UsIGxvdmUsIHkgY2hhcmFjdGVycywgZXZpZGVuY2lhbmRvIGxvcyBlamVzIHByaW5jaXBhbGVzIGRlbCBkaXNjdXJzbyBkZSBsb3MgdXN1YXJpb3MuIEVzdGUgZmlsdHJvIHJlZHVjZSBlbCBydWlkbyB5IGRlamEgdmVyIGNvbiBjbGFyaWRhZCBxdWUgbGFzIHJlc2XDsWFzIGdpcmFuIHByaW5jaXBhbG1lbnRlIGVuIHRvcm5vIGEgbGEgY2FsaWRhZCBkZWwgbGlicm8sIGxhIHRyYW1hLCB5IGxhIHNhdGlzZmFjY2nDs24gZW1vY2lvbmFsIGRlbCBsZWN0b3IuCgojIyMgQ29tcGFyYWNpb24gZGUgYW1ib3MgZmlsdHJvcyAKCgpGaWx0cm8gMSAoRW5lcm8g4oCTIEp1bmlvIDIwMTMpCgpFbmZvcXVlIGVuIGhpc3RvcmlhLCBwZXJzb25hamVzIHkgYXV0b3I7IG1lbm9yIGF0ZW5jacOzbiBhIHRlbWFzIHTDqWNuaWNvcy4KCkxlbmd1YWplIHBvc2l0aXZvIHkgZGVzY3JpcHRpdm8sIGNvbiBhZGpldGl2b3MgY29tbyBnb29kLCBncmVhdCBlIGludGVyZXN0aW5nLgoKUmVzZcOxYXMgY2VudHJhZGFzIGVuIGxhIGNhbGlkYWQgbGl0ZXJhcmlhIHkgbGEgZXN0cnVjdHVyYSBuYXJyYXRpdmEuCgpBcGFyaWNpw7NuIGRlIGNyw610aWNhcyBzb2JyZSBmb3JtYXRvIHkgZXJyb3JlcyBncmFtYXRpY2FsZXMsIHJlZmxlamFuZG8gdW5hIGV0YXBhIGRlIGFkYXB0YWNpw7NuIGFsIGVudG9ybm8gZGlnaXRhbC4KClByZWRvbWluYSB1biB0b25vIHJhY2lvbmFsIHkgYW5hbMOtdGljbywgbcOhcyBlbmZvY2FkbyBlbiBsYSB2YWxvcmFjacOzbiBkZWwgY29udGVuaWRvIHF1ZSBlbiBsYSBlbW9jacOzbi4KCiBGaWx0cm8gMiAoSnVsaW8g4oCTIERpY2llbWJyZSAyMDEzKQoKTGVuZ3VhamUgbcOhcyBlbW9jaW9uYWwgeSBleHByZXNpdm8sIGNlbnRyYWRvIGVuIGfDqW5lcm9zIHJvbcOhbnRpY29zIHkganV2ZW5pbGVzLgoKUGFsYWJyYXMgY2xhdmU6IGxvdmUsIHJvbWFuY2UsIHF1aWNrIHJlYWQsIGZhaXJ5IHRhbGUgeSBoaWdoIHNjaG9vbC4KCk1heW9yIMOpbmZhc2lzIGVuIGxhIGNvbmV4acOzbiBhZmVjdGl2YSBjb24gbG9zIHBlcnNvbmFqZXMgeSBsYSB0cmFtYS4KCkRpc21pbnV5ZW4gbGFzIGNyw610aWNhcyB0w6ljbmljYXM7IGxvcyBsZWN0b3JlcyBzZSBlbmZvY2FuIGVuIGRpc2ZydXRhciB5IHJlY29tZW5kYXIuCgpUb25vIGNlcmNhbm8geSBlbnR1c2lhc3RhLCByZWZsZWphbmRvIHVuYSBjb211bmlkYWQgbGVjdG9yYSBtYWR1cmEgeSBjb21wcm9tZXRpZGEgY29uIGxhIGV4cGVyaWVuY2lhIG5hcnJhdGl2YS4=