Teoría

La minería de texto (Text Mining) es el proceso de extraer información útil, patrones o conocimiento de textos no estructurados.

Consta de 3 etapas:
1. Obtener datos: El reconocimiento óptico de caracteres (OCR en ingles) es una tecnología que permite convertir imágenes de texto en texto editable. También es conocido como extracción de texto de imagenes.
2. Explorar datos: Representación gráfica o visual de los datos para su interpretación. Los métodos más comunes son el análisis de sentimientos, la nube de palabras y el topic modeling.
3. Análisis predictivo: Son las técnicas y modelos estadísticos para predecir resultados futuros. Los modelos mas usados son el Random Forest, Redes Neuronales y Regresiones.

Instalar paquetes y descargar librerías

# install.packages("tidyverse") # Data wrangling (Manipulación de datos)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.5.0     ✔ tibble    3.2.1
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# install.packages("tesseract") # OCR (Reconocimiento optico de caracteres)
library(tesseract)
# install.packages("magick") # Trabajar con imagenes de formato PNG
library(magick)
## Linking to ImageMagick 6.9.12.93
## Enabled features: cairo, fontconfig, freetype, heic, lcms, pango, raw, rsvg, webp
## Disabled features: fftw, ghostscript, x11
# install.packages("officer") # Nos permite exportar en formatos de office (Word, etc)
library(officer)
# install.packages("pdftools") # Nos permite leer PDFs.
library(pdftools)
## Using poppler version 23.04.0
# install.packages("purrr") # Para la función map (Aplicar funcion a cada uno de los vectores)
library(purrr)
# install.packages("tm") # Text mining
library(tm)
## Loading required package: NLP
## 
## Attaching package: 'NLP'
## 
## The following object is masked from 'package:ggplot2':
## 
##     annotate
# install.packages("RColorBrewer") # Manejar colores
library(RColorBrewer)
# install.packages("wordcloud") # Nube de palabras (Las grandes se repiten mas y viceversa)
library(wordcloud)
# install.packages("topicmodels") # Modelos de temas (De que habla el texto)
library(topicmodels)
# install.packages("ggplot2") # Graficas con más diseño
library(ggplot2)

1. Obtener Datos mediante OCR

De imagen PNG a texto WORD

# imagen1 <- image_read("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/imagen1.PNG")
# texto1 <- ocr(imagen1)
# texto1
# doc1 <- read_docx() # Crea documento de word en blanco
# doc1 <- doc1 %>% body_add_par(texto1, style="Normal") # Pega el texto en el word
# print(doc1, target = "Texto1.docx") # Descarga el archivo en word en la computadora

Imagen PNG a Texto WORD en español

Consultar idiomas disponibles

# imagen2 <- image_read("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/imagen2.PNG")
# tesseract_download("spa")
# texto2 <- ocr(imagen2, engine = tesseract("spa"))
# texto2
# doc2 <- read_docx() # Crea documento de word en blanco
# doc2 <- doc2 %>% body_add_par(texto2, style="Normal") # Pega el texto en el word
# print(doc2, target = "Texto2.docx") # Descarga el archivo en word en la computadora

De PDF a texto en WORD

# pdf1 <- pdf_convert("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/pdf1.pdf", dpi = 600) %>% map(ocr)

Actividad 1. Novela “IT”

De PDF a imagen PNG

# pdfeso <- pdf_convert("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/eso3.pdf", dpi = 600) %>% map(ocr)

De imagen PNG a texto WORD

# imagen3 <- image_read("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/eso3_1.PNG")
# tesseract_download("spa")
# texto3 <- ocr(imagen3, engine = tesseract("spa"))

# imagen4 <- image_read("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/eso3_2.PNG")
# tesseract_download("spa")
# texto4 <- ocr(imagen4, engine = tesseract("spa"))

# imagen5 <- image_read("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/eso3_3.PNG")
# tesseract_download("spa")
# texto5 <- ocr(imagen5, engine = tesseract("spa"))

# Agregar los textos al documento de Word
# Completo <- read_docx()

# Completo <- Completo %>%
#  body_add_par(texto3, style = "Normal") %>%
#  body_add_par(texto4, style = "Normal") %>%
#  body_add_par(texto5, style = "Normal")

# print(Completo, target = "EsoCapitulo3.docx")

2. Exploración de datos

Analisis de frecuencias

text <- readLines("http://www.sthda.com/sthda/RDoc/example-files/martin-luther-king-i-have-a-dream-speech.txt")

corpus <- Corpus(VectorSource(text)) # Pone cada renglón en una celda del vector
#inspect(corpus)
corpus <- tm_map(corpus, content_transformer(tolower)) # Pone todo en minúsculas
## Warning in tm_map.SimpleCorpus(corpus, content_transformer(tolower)):
## transformation drops documents
corpus <- tm_map(corpus, removePunctuation)
## Warning in tm_map.SimpleCorpus(corpus, removePunctuation): transformation drops
## documents
corpus <- tm_map(corpus, removeNumbers)
## Warning in tm_map.SimpleCorpus(corpus, removeNumbers): transformation drops
## documents
corpus <- tm_map(corpus, removeWords, stopwords("en")) # Elimina palabras que no hablan del tema
## Warning in tm_map.SimpleCorpus(corpus, removeWords, stopwords("en")):
## transformation drops documents
# corpus <- tm_map(corpus, removeWords, c("dream", "will")) Elimina las palabras puntuales

tdm <- TermDocumentMatrix(corpus) 
m <- as.matrix(tdm) # Cuenta las veces que aparece cada palabra

frecuencia <- sort(rowSums(m), decreasing = TRUE) # Cuenta la frecuencia de cada palabra en el texto completo 

frecuencia_df <- data.frame(word=names(frecuencia), freq = frecuencia) # Convierte la frecuencia en un data frame

ggplot(head(frecuencia_df, 10), aes(x=reorder(word, -freq), y=freq)) +
            geom_bar(stat = "identity", fill = "skyblue") +
            geom_text(aes(label = freq), vjust = -0.5) +
            labs(title= "Top 10 palabras más frecuentes", x= "Palabras", subtitle= "Discurso de Martin Luther King", y= "Frecuencia") +
            ylim(0,20)

Nube de palabras

# El procesamiento de datos antes de la nue de palabras es igual que en el análisis de frecuencias, desde importar el texto hasta convertir la frecuencia en un data frame "frecuencia_df"
set.seed(123)
wordcloud(word = frecuencia_df$word, freq = frecuencia_df$freq, min.freq = 1, random.order = FALSE, colors = brewer.pal(8, "Spectral"))

Ejercicio 2. Novela IT

Analisis de frecuencias

text1 <- read_docx("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/EsoCapitulo3.docx")

texto_sin_acentos <- iconv(text1, to = "ASCII//TRANSLIT")

corpus1 <- Corpus(VectorSource(texto_sin_acentos)) # Pone cada renglón en una celda del vector
#inspect(corpus)
corpus1 <- tm_map(corpus1, content_transformer(tolower)) # Pone todo en minúsculas
## Warning in tm_map.SimpleCorpus(corpus1, content_transformer(tolower)):
## transformation drops documents
corpus1 <- tm_map(corpus1, removePunctuation)
## Warning in tm_map.SimpleCorpus(corpus1, removePunctuation): transformation
## drops documents
corpus1 <- tm_map(corpus1, removeNumbers)
## Warning in tm_map.SimpleCorpus(corpus1, removeNumbers): transformation drops
## documents
corpus1 <- tm_map(corpus1, removeWords, stopwords("spanish")) # Elimina palabras que no hablan del tema
## Warning in tm_map.SimpleCorpus(corpus1, removeWords, stopwords("spanish")):
## transformation drops documents
# corpus <- tm_map(corpus, removeWords, c("dream", "will")) Elimina las palabras puntuales

tdm1 <- TermDocumentMatrix(corpus1) 
m1 <- as.matrix(tdm1) # Cuenta las veces que aparece cada palabra

frecuencia1 <- sort(rowSums(m1), decreasing = TRUE) # Cuenta la frecuencia de cada palabra en el texto completo 

frecuencia_df1 <- data.frame(word=names(frecuencia1), freq = frecuencia1) # Convierte la frecuencia en un data frame

ggplot(head(frecuencia_df1, 10), aes(x=reorder(word, -freq), y=freq)) +
            geom_bar(stat = "identity", fill = "skyblue") +
            geom_text(aes(label = freq), vjust = -0.5) +
            labs(title= "Top 10 palabras más frecuentes", x= "Palabras", subtitle= "Capitulo 3 de IT", y= "Frecuencia") +
            ylim(0,70)

Nube de palabras

# El procesamiento de datos antes de la nue de palabras es igual que en el análisis de frecuencias, desde importar el texto hasta convertir la frecuencia en un data frame "frecuencia_df"
set.seed(123)
wordcloud(word = frecuencia_df1$word, freq = frecuencia_df1$freq, min.freq = 1, random.order = FALSE, colors = brewer.pal(8, "Spectral"))

LS0tCnRpdGxlOiAiVGV4dCBtaW5pbmciCmF1dGhvcjogIkjDqWN0b3IgR3VhZGFsdXBlIGRlIGxhIEdhcnphIFRyZXZpw7FvIC0gQTAxMTc3OTYwIgpkYXRlOiAiMjAyNC0wMi0yNiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKICAgIHRoZW1lOiBjZXJ1bGVhbgogICAgaGlnaGxpZ2h0OiBrYXRlCi0tLQoKIVtdKC9Vc2Vycy9oZWN0b3JkZWxhZ2FyemF0cmV2aW5vL0xpYnJhcnkvQ2xvdWRTdG9yYWdlL0dvb2dsZURyaXZlLWEwMTE3Nzk2MEB0ZWMubXgvTWkgdW5pZGFkL0xJVC9TZXh0byBzZW1lc3RyZS9JbnRlbGlnZW5jaWEgQXJ0aWZpY2lhbCBjb24gSW1wYWN0byBFbXByZXNhcmlhbC9Nb2R1bG8gMi9TZXNpb24gNi85ODM1ODEwXzE2MTkxNzUxNDZfVGV4dCBNaW5pbmcuanBnKQoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5UZW9yw61hPC9zcGFuPgpMYSAqKm1pbmVyw61hIGRlIHRleHRvIChUZXh0IE1pbmluZykqKiBlcyBlbCBwcm9jZXNvIGRlIGV4dHJhZXIgaW5mb3JtYWNpw7NuIMO6dGlsLCBwYXRyb25lcyBvIGNvbm9jaW1pZW50byBkZSB0ZXh0b3Mgbm8gZXN0cnVjdHVyYWRvcy4gIAoKKipDb25zdGEgZGUgMyBldGFwYXM6KiogIAoxLiBPYnRlbmVyIGRhdG9zOiBFbCByZWNvbm9jaW1pZW50byDDs3B0aWNvIGRlIGNhcmFjdGVyZXMgKE9DUiBlbiBpbmdsZXMpIGVzIHVuYSB0ZWNub2xvZ8OtYSBxdWUgcGVybWl0ZSBjb252ZXJ0aXIgaW3DoWdlbmVzIGRlIHRleHRvIGVuIHRleHRvIGVkaXRhYmxlLiBUYW1iacOpbiBlcyBjb25vY2lkbyBjb21vICoqZXh0cmFjY2nDs24gZGUgdGV4dG8gZGUgaW1hZ2VuZXMqKi4gIAoyLiBFeHBsb3JhciBkYXRvczogUmVwcmVzZW50YWNpw7NuIGdyw6FmaWNhIG8gdmlzdWFsIGRlIGxvcyBkYXRvcyBwYXJhIHN1IGludGVycHJldGFjacOzbi4gTG9zIG3DqXRvZG9zIG3DoXMgY29tdW5lcyBzb24gZWwgYW7DoWxpc2lzIGRlIHNlbnRpbWllbnRvcywgbGEgbnViZSBkZSBwYWxhYnJhcyB5IGVsIHRvcGljIG1vZGVsaW5nLiAgCjMuIEFuw6FsaXNpcyBwcmVkaWN0aXZvOiBTb24gbGFzIHTDqWNuaWNhcyB5IG1vZGVsb3MgZXN0YWTDrXN0aWNvcyBwYXJhIHByZWRlY2lyIHJlc3VsdGFkb3MgZnV0dXJvcy4gTG9zIG1vZGVsb3MgbWFzIHVzYWRvcyBzb24gZWwgUmFuZG9tIEZvcmVzdCwgUmVkZXMgTmV1cm9uYWxlcyB5IFJlZ3Jlc2lvbmVzLiAgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5JbnN0YWxhciBwYXF1ZXRlcyB5IGRlc2NhcmdhciBsaWJyZXLDrWFzPC9zcGFuPgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKSAjIERhdGEgd3JhbmdsaW5nIChNYW5pcHVsYWNpw7NuIGRlIGRhdG9zKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKIyBpbnN0YWxsLnBhY2thZ2VzKCJ0ZXNzZXJhY3QiKSAjIE9DUiAoUmVjb25vY2ltaWVudG8gb3B0aWNvIGRlIGNhcmFjdGVyZXMpCmxpYnJhcnkodGVzc2VyYWN0KQojIGluc3RhbGwucGFja2FnZXMoIm1hZ2ljayIpICMgVHJhYmFqYXIgY29uIGltYWdlbmVzIGRlIGZvcm1hdG8gUE5HCmxpYnJhcnkobWFnaWNrKQojIGluc3RhbGwucGFja2FnZXMoIm9mZmljZXIiKSAjIE5vcyBwZXJtaXRlIGV4cG9ydGFyIGVuIGZvcm1hdG9zIGRlIG9mZmljZSAoV29yZCwgZXRjKQpsaWJyYXJ5KG9mZmljZXIpCiMgaW5zdGFsbC5wYWNrYWdlcygicGRmdG9vbHMiKSAjIE5vcyBwZXJtaXRlIGxlZXIgUERGcy4KbGlicmFyeShwZGZ0b29scykKIyBpbnN0YWxsLnBhY2thZ2VzKCJwdXJyciIpICMgUGFyYSBsYSBmdW5jacOzbiBtYXAgKEFwbGljYXIgZnVuY2lvbiBhIGNhZGEgdW5vIGRlIGxvcyB2ZWN0b3JlcykKbGlicmFyeShwdXJycikKIyBpbnN0YWxsLnBhY2thZ2VzKCJ0bSIpICMgVGV4dCBtaW5pbmcKbGlicmFyeSh0bSkKIyBpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKSAjIE1hbmVqYXIgY29sb3JlcwpsaWJyYXJ5KFJDb2xvckJyZXdlcikKIyBpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKSAjIE51YmUgZGUgcGFsYWJyYXMgKExhcyBncmFuZGVzIHNlIHJlcGl0ZW4gbWFzIHkgdmljZXZlcnNhKQpsaWJyYXJ5KHdvcmRjbG91ZCkKIyBpbnN0YWxsLnBhY2thZ2VzKCJ0b3BpY21vZGVscyIpICMgTW9kZWxvcyBkZSB0ZW1hcyAoRGUgcXVlIGhhYmxhIGVsIHRleHRvKQpsaWJyYXJ5KHRvcGljbW9kZWxzKQojIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKSAjIEdyYWZpY2FzIGNvbiBtw6FzIGRpc2XDsW8KbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+MS4gT2J0ZW5lciBEYXRvcyBtZWRpYW50ZSBPQ1I8L3NwYW4+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5EZSBpbWFnZW4gUE5HIGEgdGV4dG8gV09SRDwvc3Bhbj4KYGBge3J9CiMgaW1hZ2VuMSA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvaW1hZ2VuMS5QTkciKQojIHRleHRvMSA8LSBvY3IoaW1hZ2VuMSkKIyB0ZXh0bzEKIyBkb2MxIDwtIHJlYWRfZG9jeCgpICMgQ3JlYSBkb2N1bWVudG8gZGUgd29yZCBlbiBibGFuY28KIyBkb2MxIDwtIGRvYzEgJT4lIGJvZHlfYWRkX3Bhcih0ZXh0bzEsIHN0eWxlPSJOb3JtYWwiKSAjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZAojIHByaW50KGRvYzEsIHRhcmdldCA9ICJUZXh0bzEuZG9jeCIpICMgRGVzY2FyZ2EgZWwgYXJjaGl2byBlbiB3b3JkIGVuIGxhIGNvbXB1dGFkb3JhCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+SW1hZ2VuIFBORyBhIFRleHRvIFdPUkQgZW4gZXNwYcOxb2w8L3NwYW4+CgpbQ29uc3VsdGFyIGlkaW9tYXMgZGlzcG9uaWJsZXNdKGh0dHBzOi8vdGVzc2VyYWN0LW9jci5naXRodWIuaW8vdGVzc2RvYy9EYXRhLUZpbGVzLWluLWRpZmZlcmVudC12ZXJzaW9ucy5odG1sKQpgYGB7cn0KIyBpbWFnZW4yIDwtIGltYWdlX3JlYWQoIi9Vc2Vycy9oZWN0b3JkZWxhZ2FyemF0cmV2aW5vL0xpYnJhcnkvQ2xvdWRTdG9yYWdlL0dvb2dsZURyaXZlLWEwMTE3Nzk2MEB0ZWMubXgvTWkgdW5pZGFkL0xJVC9TZXh0byBzZW1lc3RyZS9JbnRlbGlnZW5jaWEgQXJ0aWZpY2lhbCBjb24gSW1wYWN0byBFbXByZXNhcmlhbC9Nb2R1bG8gMi9TZXNpb24gNi9pbWFnZW4yLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvMiA8LSBvY3IoaW1hZ2VuMiwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKIyB0ZXh0bzIKIyBkb2MyIDwtIHJlYWRfZG9jeCgpICMgQ3JlYSBkb2N1bWVudG8gZGUgd29yZCBlbiBibGFuY28KIyBkb2MyIDwtIGRvYzIgJT4lIGJvZHlfYWRkX3Bhcih0ZXh0bzIsIHN0eWxlPSJOb3JtYWwiKSAjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZAojIHByaW50KGRvYzIsIHRhcmdldCA9ICJUZXh0bzIuZG9jeCIpICMgRGVzY2FyZ2EgZWwgYXJjaGl2byBlbiB3b3JkIGVuIGxhIGNvbXB1dGFkb3JhCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+RGUgUERGIGEgdGV4dG8gZW4gV09SRDwvc3Bhbj4KYGBge3J9CiMgcGRmMSA8LSBwZGZfY29udmVydCgiL1VzZXJzL2hlY3RvcmRlbGFnYXJ6YXRyZXZpbm8vTGlicmFyeS9DbG91ZFN0b3JhZ2UvR29vZ2xlRHJpdmUtYTAxMTc3OTYwQHRlYy5teC9NaSB1bmlkYWQvTElUL1NleHRvIHNlbWVzdHJlL0ludGVsaWdlbmNpYSBBcnRpZmljaWFsIGNvbiBJbXBhY3RvIEVtcHJlc2FyaWFsL01vZHVsbyAyL1Nlc2lvbiA2L3BkZjEucGRmIiwgZHBpID0gNjAwKSAlPiUgbWFwKG9jcikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5BY3RpdmlkYWQgMS4gTm92ZWxhICJJVCI8L3NwYW4+CgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+RGUgUERGIGEgaW1hZ2VuIFBORzwvc3Bhbj4KYGBge3J9CiMgcGRmZXNvIDwtIHBkZl9jb252ZXJ0KCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvMy5wZGYiLCBkcGkgPSA2MDApICU+JSBtYXAob2NyKQpgYGAKCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5EZSBpbWFnZW4gUE5HIGEgdGV4dG8gV09SRDwvc3Bhbj4KYGBge3J9CiMgaW1hZ2VuMyA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvM18xLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvMyA8LSBvY3IoaW1hZ2VuMywgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKCiMgaW1hZ2VuNCA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvM18yLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvNCA8LSBvY3IoaW1hZ2VuNCwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKCiMgaW1hZ2VuNSA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvM18zLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvNSA8LSBvY3IoaW1hZ2VuNSwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKCiMgQWdyZWdhciBsb3MgdGV4dG9zIGFsIGRvY3VtZW50byBkZSBXb3JkCiMgQ29tcGxldG8gPC0gcmVhZF9kb2N4KCkKCiMgQ29tcGxldG8gPC0gQ29tcGxldG8gJT4lCiMgIGJvZHlfYWRkX3Bhcih0ZXh0bzMsIHN0eWxlID0gIk5vcm1hbCIpICU+JQojICBib2R5X2FkZF9wYXIodGV4dG80LCBzdHlsZSA9ICJOb3JtYWwiKSAlPiUKIyAgYm9keV9hZGRfcGFyKHRleHRvNSwgc3R5bGUgPSAiTm9ybWFsIikKCiMgcHJpbnQoQ29tcGxldG8sIHRhcmdldCA9ICJFc29DYXBpdHVsbzMuZG9jeCIpCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij4yLiBFeHBsb3JhY2nDs24gZGUgZGF0b3M8L3NwYW4+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5BbmFsaXNpcyBkZSBmcmVjdWVuY2lhczwvc3Bhbj4KYGBge3J9CnRleHQgPC0gcmVhZExpbmVzKCJodHRwOi8vd3d3LnN0aGRhLmNvbS9zdGhkYS9SRG9jL2V4YW1wbGUtZmlsZXMvbWFydGluLWx1dGhlci1raW5nLWktaGF2ZS1hLWRyZWFtLXNwZWVjaC50eHQiKQoKY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGV4dCkpICMgUG9uZSBjYWRhIHJlbmdsw7NuIGVuIHVuYSBjZWxkYSBkZWwgdmVjdG9yCiNpbnNwZWN0KGNvcnB1cykKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgUG9uZSB0b2RvIGVuIG1pbsO6c2N1bGFzCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZU51bWJlcnMpCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbiIpKSAjIEVsaW1pbmEgcGFsYWJyYXMgcXVlIG5vIGhhYmxhbiBkZWwgdGVtYQojIGNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3JkcywgYygiZHJlYW0iLCAid2lsbCIpKSBFbGltaW5hIGxhcyBwYWxhYnJhcyBwdW50dWFsZXMKCnRkbSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzKSAKbSA8LSBhcy5tYXRyaXgodGRtKSAjIEN1ZW50YSBsYXMgdmVjZXMgcXVlIGFwYXJlY2UgY2FkYSBwYWxhYnJhCgpmcmVjdWVuY2lhIDwtIHNvcnQocm93U3VtcyhtKSwgZGVjcmVhc2luZyA9IFRSVUUpICMgQ3VlbnRhIGxhIGZyZWN1ZW5jaWEgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIHRleHRvIGNvbXBsZXRvIAoKZnJlY3VlbmNpYV9kZiA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXMoZnJlY3VlbmNpYSksIGZyZXEgPSBmcmVjdWVuY2lhKSAjIENvbnZpZXJ0ZSBsYSBmcmVjdWVuY2lhIGVuIHVuIGRhdGEgZnJhbWUKCmdncGxvdChoZWFkKGZyZWN1ZW5jaWFfZGYsIDEwKSwgYWVzKHg9cmVvcmRlcih3b3JkLCAtZnJlcSksIHk9ZnJlcSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic2t5Ymx1ZSIpICsKICAgICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGZyZXEpLCB2anVzdCA9IC0wLjUpICsKICAgICAgICAgICAgbGFicyh0aXRsZT0gIlRvcCAxMCBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMiLCB4PSAiUGFsYWJyYXMiLCBzdWJ0aXRsZT0gIkRpc2N1cnNvIGRlIE1hcnRpbiBMdXRoZXIgS2luZyIsIHk9ICJGcmVjdWVuY2lhIikgKwogICAgICAgICAgICB5bGltKDAsMjApCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+TnViZSBkZSBwYWxhYnJhczwvc3Bhbj4KYGBge3J9CiMgRWwgcHJvY2VzYW1pZW50byBkZSBkYXRvcyBhbnRlcyBkZSBsYSBudWUgZGUgcGFsYWJyYXMgZXMgaWd1YWwgcXVlIGVuIGVsIGFuw6FsaXNpcyBkZSBmcmVjdWVuY2lhcywgZGVzZGUgaW1wb3J0YXIgZWwgdGV4dG8gaGFzdGEgY29udmVydGlyIGxhIGZyZWN1ZW5jaWEgZW4gdW4gZGF0YSBmcmFtZSAiZnJlY3VlbmNpYV9kZiIKc2V0LnNlZWQoMTIzKQp3b3JkY2xvdWQod29yZCA9IGZyZWN1ZW5jaWFfZGYkd29yZCwgZnJlcSA9IGZyZWN1ZW5jaWFfZGYkZnJlcSwgbWluLmZyZXEgPSAxLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiU3BlY3RyYWwiKSkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5FamVyY2ljaW8gMi4gTm92ZWxhIElUPC9zcGFuPgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPkFuYWxpc2lzIGRlIGZyZWN1ZW5jaWFzPC9zcGFuPgpgYGB7cn0KdGV4dDEgPC0gcmVhZF9kb2N4KCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvRXNvQ2FwaXR1bG8zLmRvY3giKQoKdGV4dG9fc2luX2FjZW50b3MgPC0gaWNvbnYodGV4dDEsIHRvID0gIkFTQ0lJLy9UUkFOU0xJVCIpCgpjb3JwdXMxIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGV4dG9fc2luX2FjZW50b3MpKSAjIFBvbmUgY2FkYSByZW5nbMOzbiBlbiB1bmEgY2VsZGEgZGVsIHZlY3RvcgojaW5zcGVjdChjb3JwdXMpCmNvcnB1czEgPC0gdG1fbWFwKGNvcnB1czEsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgUG9uZSB0b2RvIGVuIG1pbsO6c2N1bGFzCmNvcnB1czEgPC0gdG1fbWFwKGNvcnB1czEsIHJlbW92ZVB1bmN0dWF0aW9uKQpjb3JwdXMxIDwtIHRtX21hcChjb3JwdXMxLCByZW1vdmVOdW1iZXJzKQpjb3JwdXMxIDwtIHRtX21hcChjb3JwdXMxLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJzcGFuaXNoIikpICMgRWxpbWluYSBwYWxhYnJhcyBxdWUgbm8gaGFibGFuIGRlbCB0ZW1hCiMgY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBjKCJkcmVhbSIsICJ3aWxsIikpIEVsaW1pbmEgbGFzIHBhbGFicmFzIHB1bnR1YWxlcwoKdGRtMSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzMSkgCm0xIDwtIGFzLm1hdHJpeCh0ZG0xKSAjIEN1ZW50YSBsYXMgdmVjZXMgcXVlIGFwYXJlY2UgY2FkYSBwYWxhYnJhCgpmcmVjdWVuY2lhMSA8LSBzb3J0KHJvd1N1bXMobTEpLCBkZWNyZWFzaW5nID0gVFJVRSkgIyBDdWVudGEgbGEgZnJlY3VlbmNpYSBkZSBjYWRhIHBhbGFicmEgZW4gZWwgdGV4dG8gY29tcGxldG8gCgpmcmVjdWVuY2lhX2RmMSA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXMoZnJlY3VlbmNpYTEpLCBmcmVxID0gZnJlY3VlbmNpYTEpICMgQ29udmllcnRlIGxhIGZyZWN1ZW5jaWEgZW4gdW4gZGF0YSBmcmFtZQoKZ2dwbG90KGhlYWQoZnJlY3VlbmNpYV9kZjEsIDEwKSwgYWVzKHg9cmVvcmRlcih3b3JkLCAtZnJlcSksIHk9ZnJlcSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic2t5Ymx1ZSIpICsKICAgICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGZyZXEpLCB2anVzdCA9IC0wLjUpICsKICAgICAgICAgICAgbGFicyh0aXRsZT0gIlRvcCAxMCBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMiLCB4PSAiUGFsYWJyYXMiLCBzdWJ0aXRsZT0gIkNhcGl0dWxvIDMgZGUgSVQiLCB5PSAiRnJlY3VlbmNpYSIpICsKICAgICAgICAgICAgeWxpbSgwLDcwKQpgYGAKCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5OdWJlIGRlIHBhbGFicmFzPC9zcGFuPgpgYGB7cn0KIyBFbCBwcm9jZXNhbWllbnRvIGRlIGRhdG9zIGFudGVzIGRlIGxhIG51ZSBkZSBwYWxhYnJhcyBlcyBpZ3VhbCBxdWUgZW4gZWwgYW7DoWxpc2lzIGRlIGZyZWN1ZW5jaWFzLCBkZXNkZSBpbXBvcnRhciBlbCB0ZXh0byBoYXN0YSBjb252ZXJ0aXIgbGEgZnJlY3VlbmNpYSBlbiB1biBkYXRhIGZyYW1lICJmcmVjdWVuY2lhX2RmIgpzZXQuc2VlZCgxMjMpCndvcmRjbG91ZCh3b3JkID0gZnJlY3VlbmNpYV9kZjEkd29yZCwgZnJlcSA9IGZyZWN1ZW5jaWFfZGYxJGZyZXEsIG1pbi5mcmVxID0gMSwgcmFuZG9tLm9yZGVyID0gRkFMU0UsIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIlNwZWN0cmFsIikpCmBgYAoK