Teoría

La minería de texto (TM) 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) es una tecnología que permite convertir imágenes de texto en texto editable. También es conocido como extracción de texto en imágenes.
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 más usados son el Random Forest, redes neuronales y regresiones.

Instalar paquetes y llamar librerías

#install.packages("tidyverse") # Data wrangling
library(tidyverse)
#install.packages("tesseract") # OCR
library(tesseract)
#install.packages("magick") # PNG
library(magick)
#install.packages("officer") # Exportar a formatos Office (Especialmente Word)
library(officer)
#install.packages("pdftools") # PDF
library(pdftools)
#install.packages("purrr") # Para la función map para aplicar una función a cada elemento de un vector
library(purrr)
#install.packages("tm") # Text Mining
library(tm)
#install.packages("RColorBrewer") # Para manejar colores
library(RColorBrewer)
#install.packages("wordcloud") # Para hacer Nubes de Palabras
library(wordcloud)
#install.packages("topicmodels") # Modelos de Temas
library(topicmodels)
#install.packages("ggplot2") # Gráficas con más diseño
library(ggplot2)

1. Obtener datos mediante OCR

imagen1 <- image_read("C:\\Users\\kathi\\OneDrive\\Escritorio\\M2_IA con Impacto Empresarial\\imagen1.PNG")
texto1 <- ocr(imagen1)
texto1
## [1] "Linear regression with one variable x is also known as univariate linear regression\nor simple linear regression. Simple linear regression is used to predict a single\noutput from a single input. This is an example of supervised learning, which means\nthat the data is labeled, i.e., the output values are known in the training data. Let us\nfit a line through the data using simple linear regression as shown in Fig. 4.1.\n"
doc1 <- read_docx() # Crea un documento de word en blanco
doc1 <- doc1 %>% body_add_par(texto1, style ="Normal") # Pega el texto en el word 
# print(doc1, target = "texto1.docx") # Guarda el word en la computadora

Imagen en español PNG a texto en WORD

Consultar idiomas disponibles

imagen2 <- image_read("C:\\Users\\kathi\\OneDrive\\Escritorio\\M2_IA con Impacto Empresarial\\imagen2.PNG")
# tesseract_download("spa") # Cuando se use en español
# texto2 <- ocr(imagen2, engine = tesseract("spa"))
texto2 <- ocr(imagen2)
texto2
## [1] "Un importante, y quiza controversial, asunto politico es el que se refiere al efecto del salario minimo sobre\nlas tasas de desempleo en diversos grupos de trabajadores. Aunque este problema puede ser estudiado con\ndiversos tipos de datos (corte transversal, series de tiempo o datos de panel), suelen usarse las series de\ntiempo para observar los efectos agregados. En la tabla 1.3 se presenta un ejemplo de una base de datos\nde series de tiempo sobre tasas de desempleo y salarios minimos.\n"
doc2 <- read_docx() # Crea un documento de word en blanco
doc2 <- doc2 %>% body_add_par(texto2, style ="Normal")# Pega el texto en el word
# print(doc2, target = "texto2.docx") # Guarda el word en la computadora

De PDF a texto en WORD

pdf1 <- pdf_convert("C:\\Users\\kathi\\OneDrive\\Escritorio\\M2_IA con Impacto Empresarial\\pdf1.pdf", dpi=600) %>% map(ocr)
## Converting page 1 to pdf1_1.png... done!
## Converting page 2 to pdf1_2.png... done!
## Converting page 3 to pdf1_3.png... done!
## Converting page 4 to pdf1_4.png... done!
## Converting page 5 to pdf1_5.png... done!
## Converting page 6 to pdf1_6.png... done!
## Converting page 7 to pdf1_7.png... done!
## Converting page 8 to pdf1_8.png... done!

Actividad 1. Novela “IT”

Convertir de PDF a texto en WORD

pdf2<- pdf_convert( "C:\\Users\\kathi\\OneDrive\\Escritorio\\M2_IA con Impacto Empresarial\\eso3.pdf" , dpi=600)  %>% map(ocr)
## Converting page 1 to eso3_1.png... done!
## Converting page 2 to eso3_2.png... done!
## Converting page 3 to eso3_3.png... done!

Convertir imágenes en español PNG a texto en WORD

imagen3 <-image_read("eso3_1.png")
tesseract_download("spa")
## [1] "C:\\Users\\kathi\\AppData\\Local\\tesseract5\\tesseract5\\tessdata/spa.traineddata"
text3 <- ocr(imagen3, engine = tesseract("spa"))

imagen4 <-image_read("eso3_2.png")
tesseract_download("spa")
## [1] "C:\\Users\\kathi\\AppData\\Local\\tesseract5\\tesseract5\\tessdata/spa.traineddata"
text4 <- ocr(imagen4, engine = tesseract("spa"))

imagen5 <-image_read("eso3_3.png")
tesseract_download("spa")
## [1] "C:\\Users\\kathi\\AppData\\Local\\tesseract5\\tesseract5\\tessdata/spa.traineddata"
text5 <- ocr(imagen5, engine = tesseract("spa"))

doc3 <- read_docx() #Crea un documento de word en blanco


doc3 <- doc3 %>%
  body_add_par(text3, style = "Normal") %>%
  body_add_par(text4, style = "Normal") %>%
  body_add_par(text5, style = "Normal")

# Guardar el documento
# print(doc3, target = "it.docx")

2. Exploración de Datos

Análisis de Frecuencias

text <- readLines("C:\\Users\\kathi\\OneDrive\\Escritorio\\M2_IA con Impacto Empresarial\\martin-luther-king-i-have-a-dream-speech.txt")

corpus <- Corpus(VectorSource(text)) # Pone cada renglón en una celda de 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) # Elimina puntuación
## Warning in tm_map.SimpleCorpus(corpus, removePunctuation): transformation drops
## documents
corpus <- tm_map(corpus, removeNumbers) # Elimina números
## Warning in tm_map.SimpleCorpus(corpus, removeNumbers): transformation drops
## documents
corpus <- tm_map(corpus, removeWords, stopwords("en")) # Elimina palabras que no hablen del tema
## Warning in tm_map.SimpleCorpus(corpus, removeWords, stopwords("en")):
## transformation drops documents
# corpus <- tm_map(corpus, removeWords, c("dream", "will")) #Elimina palabras puntuales

tdm <- TermDocumentMatrix(corpus)
m <- as.matrix(tdm) # Cuenta las veces que aparece cada palabra por renglón
               
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 a data frame

ggplot(head(frecuencia_df,10), aes(x=word, y=freq)) + 
       geom_bar(stat="identity", fill="pink") + 
       geom_text(aes(label = freq), vjust = -0.5) +
       labs(title="TOP 10 palabras más frecuentes", subtitle="Discurso 'I have a Dream' de M.L: King", x= "Palabra", y="Frecuencia") + 
       ylim(0,20)

Nube de Palabras

# El procesamiento de datos antes de la nube de palabras es igual que en el Análisis de Frecuencias, desde importar el texto hasta frecuencia_df
set.seed(123)
wordcloud(words=frecuencia_df$word,freq=frecuencia_df$freq, min.freq=1,
random.order=FALSE, colors = brewer.pal(8, "RdPu"))

Ejercicio 2. Novela IT

texto <- readLines("C:\\Users\\kathi\\OneDrive\\Escritorio\\M2_IA con Impacto Empresarial\\it.txt")

text2 <- iconv(texto, to = "UTF-8", sub = "byte") # Convertir texto codificado a caracteres legibles

corpus2 <- Corpus(VectorSource(text2)) 

corpus2 <- tm_map(corpus2, content_transformer(tolower)) # Pone todo en minúsculas
## Warning in tm_map.SimpleCorpus(corpus2, content_transformer(tolower)):
## transformation drops documents
corpus2 <- tm_map(corpus2, removePunctuation) # Elimina puntuación
## Warning in tm_map.SimpleCorpus(corpus2, removePunctuation): transformation
## drops documents
corpus2 <- tm_map(corpus2, removeNumbers) # Elimina números
## Warning in tm_map.SimpleCorpus(corpus2, removeNumbers): transformation drops
## documents
corpus2 <- tm_map(corpus2, removeWords, stopwords("spanish")) # Elimina palabras que no hablen del tema
## Warning in tm_map.SimpleCorpus(corpus2, removeWords, stopwords("spanish")):
## transformation drops documents
# corpus <- tm_map(corpus, removeWords, c("dream", "will")) #Elimina palabras puntuales

tdm2 <- TermDocumentMatrix(corpus2)
m2 <- as.matrix(tdm2) # Cuenta las veces que aparece cada palabra por renglón

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

frecuencia_df2 <- data.frame(word = names(frecuencia2), freq = frecuencia2) # Convierte la frecuencia a data frame

ggplot(head(frecuencia_df2, 10), aes(x = reorder(word, -freq), y = freq)) +
  geom_bar(stat = "identity", fill = "red") +
  geom_text(aes(label = freq), vjust = -0.5) +
  labs(title = "TOP 10 palabras más frecuentes", 
       subtitle = "IT", 
       x = "Palabra", 
       y = "Frecuencia") +
  ylim(0, 20)
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

Nube de Palabras

# El procesamiento de datos antes de la nube de palabras es igual que en el Análisis de Frecuencias, desde importar el texto hasta frecuencia_df2
set.seed(123)
wordcloud(words=frecuencia_df2$word,freq=frecuencia_df2$freq, min.freq=1,
random.order=FALSE, colors = brewer.pal(8, "RdPu"))

LS0tDQp0aXRsZTogIlRleHQgTWluaW5nIg0KYXV0aG9yOiAiS2F0aGlhIEdlcmFsZGluZSBSdWl6IENhc3RlbMOhbiINCmRhdGU6ICIyMDI0LTAyLTI2Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgdGhlbWU6IGNvc21vDQotLS0NCg0KIVtdKEM6XFxVc2Vyc1xca2F0aGlcXE9uZURyaXZlXFxFc2NyaXRvcmlvXFxNMl9JQSBjb24gSW1wYWN0byBFbXByZXNhcmlhbFxcaXRfaW1nLmdpZikNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPlRlb3LDrWE8L3NwYW4+DQpMYSAqKm1pbmVyw61hIGRlIHRleHRvKiogKFRNKSBlcyBlbCBwcm9jZXNvIGRlIGV4dHJhZXIgDQppbmZvcm1hY2nDs24gw7p0aWwsIHBhdHJvbmVzIG8gY29ub2NpbWllbnRvIGRlIHRleHRvcyBubw0KZXN0cnVjdHVyYWRvcy4NCg0KQ29uc3RhIGRlIDMgZXRhcGFzOiAgDQoxLiBPYnRlbmVyIGRhdG9zOiBFbCAqKnJlY29ub2NpbWllbnRvIMOzcHRpY28gZGUgY2FyYWN0ZXJlcyAoT0NSKSoqIGVzIHVuYSB0ZWNub2xvZ8OtYSBxdWUgcGVybWl0ZSBjb252ZXJ0aXIgaW3DoWdlbmVzIGRlIHRleHRvIGVuIHRleHRvIGVkaXRhYmxlLiBUYW1iacOpbiBlcyBjb25vY2lkbyBjb21vICoqZXh0cmFjY2nDs24gZGUgdGV4dG8gZW4gaW3DoWdlbmVzKiouICANCjIuIEV4cGxvcmFyIGRhdG9zOiBSZXByZXNlbnRhY2nDs24gZ3LDoWZpY2EgbyB2aXN1YWwgZGUgbG9zIGRhdG9zIHBhcmEgc3UgaW50ZXJwcmV0YWNpw7NuLiBMb3MgbcOpdG9kb3MgbcOhcyBjb211bmVzIHNvbiBlbCBBbsOhbGlzaXMgZGUgU2VudGltaWVudG9zLCBsYSBOdWJlIGRlIFBhbGFicmFzIHkgZWwgVG9waWMgTW9kZWxpbmcuICANCjMuIEFuw6FsaXNpcyBwcmVkaWN0aXZvOiBTb24gbGFzIHTDqWNuaWNhcyB5IG1vZGVsb3MgZXN0YWTDrXN0aWNvcyAgcGFyYSBwcmVkZWNpciByZXN1bHRhZG9zIGZ1dHVyb3MuIExvcyBtb2RlbG9zIG3DoXMgdXNhZG9zIHNvbiBlbCBSYW5kb20gRm9yZXN0LCByZWRlcyBuZXVyb25hbGVzIHkgcmVncmVzaW9uZXMuDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij5JbnN0YWxhciBwYXF1ZXRlcyB5IGxsYW1hciBsaWJyZXLDrWFzPC9zcGFuPg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKSAjIERhdGEgd3JhbmdsaW5nDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJ0ZXNzZXJhY3QiKSAjIE9DUg0KbGlicmFyeSh0ZXNzZXJhY3QpDQojaW5zdGFsbC5wYWNrYWdlcygibWFnaWNrIikgIyBQTkcNCmxpYnJhcnkobWFnaWNrKQ0KI2luc3RhbGwucGFja2FnZXMoIm9mZmljZXIiKSAjIEV4cG9ydGFyIGEgZm9ybWF0b3MgT2ZmaWNlIChFc3BlY2lhbG1lbnRlIFdvcmQpDQpsaWJyYXJ5KG9mZmljZXIpDQojaW5zdGFsbC5wYWNrYWdlcygicGRmdG9vbHMiKSAjIFBERg0KbGlicmFyeShwZGZ0b29scykNCiNpbnN0YWxsLnBhY2thZ2VzKCJwdXJyciIpICMgUGFyYSBsYSBmdW5jacOzbiBtYXAgcGFyYSBhcGxpY2FyIHVuYSBmdW5jacOzbiBhIGNhZGEgZWxlbWVudG8gZGUgdW4gdmVjdG9yDQpsaWJyYXJ5KHB1cnJyKQ0KI2luc3RhbGwucGFja2FnZXMoInRtIikgIyBUZXh0IE1pbmluZw0KbGlicmFyeSh0bSkNCiNpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKSAjIFBhcmEgbWFuZWphciBjb2xvcmVzDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCiNpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKSAjIFBhcmEgaGFjZXIgTnViZXMgZGUgUGFsYWJyYXMNCmxpYnJhcnkod29yZGNsb3VkKQ0KI2luc3RhbGwucGFja2FnZXMoInRvcGljbW9kZWxzIikgIyBNb2RlbG9zIGRlIFRlbWFzDQpsaWJyYXJ5KHRvcGljbW9kZWxzKQ0KI2luc3RhbGwucGFja2FnZXMoImdncGxvdDIiKSAjIEdyw6FmaWNhcyBjb24gbcOhcyBkaXNlw7FvDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPjEuIE9idGVuZXIgZGF0b3MgbWVkaWFudGUgT0NSPC9zcGFuPg0KYGBge3J9DQppbWFnZW4xIDwtIGltYWdlX3JlYWQoIkM6XFxVc2Vyc1xca2F0aGlcXE9uZURyaXZlXFxFc2NyaXRvcmlvXFxNMl9JQSBjb24gSW1wYWN0byBFbXByZXNhcmlhbFxcaW1hZ2VuMS5QTkciKQ0KdGV4dG8xIDwtIG9jcihpbWFnZW4xKQ0KdGV4dG8xDQpkb2MxIDwtIHJlYWRfZG9jeCgpICMgQ3JlYSB1biBkb2N1bWVudG8gZGUgd29yZCBlbiBibGFuY28NCmRvYzEgPC0gZG9jMSAlPiUgYm9keV9hZGRfcGFyKHRleHRvMSwgc3R5bGUgPSJOb3JtYWwiKSAjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZCANCiMgcHJpbnQoZG9jMSwgdGFyZ2V0ID0gInRleHRvMS5kb2N4IikgIyBHdWFyZGEgZWwgd29yZCBlbiBsYSBjb21wdXRhZG9yYQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+SW1hZ2VuIGVuIGVzcGHDsW9sIFBORyBhIHRleHRvIGVuIFdPUkQ8L3NwYW4+DQoNCltDb25zdWx0YXIgaWRpb21hcyBkaXNwb25pYmxlc10oaHR0cHM6Ly9naXRodWIuY29tL3Rlc3NlcmFjdC1vY3IvdGVzc2RvYy9ibG9iL21haW4vRGF0YS1GaWxlcy1pbi1kaWZmZXJlbnQtdmVyc2lvbnMubWQpDQpgYGB7cn0NCmltYWdlbjIgPC0gaW1hZ2VfcmVhZCgiQzpcXFVzZXJzXFxrYXRoaVxcT25lRHJpdmVcXEVzY3JpdG9yaW9cXE0yX0lBIGNvbiBJbXBhY3RvIEVtcHJlc2FyaWFsXFxpbWFnZW4yLlBORyIpDQojIHRlc3NlcmFjdF9kb3dubG9hZCgic3BhIikgIyBDdWFuZG8gc2UgdXNlIGVuIGVzcGHDsW9sDQojIHRleHRvMiA8LSBvY3IoaW1hZ2VuMiwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkNCnRleHRvMiA8LSBvY3IoaW1hZ2VuMikNCnRleHRvMg0KZG9jMiA8LSByZWFkX2RvY3goKSAjIENyZWEgdW4gZG9jdW1lbnRvIGRlIHdvcmQgZW4gYmxhbmNvDQpkb2MyIDwtIGRvYzIgJT4lIGJvZHlfYWRkX3Bhcih0ZXh0bzIsIHN0eWxlID0iTm9ybWFsIikjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZA0KIyBwcmludChkb2MyLCB0YXJnZXQgPSAidGV4dG8yLmRvY3giKSAjIEd1YXJkYSBlbCB3b3JkIGVuIGxhIGNvbXB1dGFkb3JhDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij5EZSBQREYgYSB0ZXh0byBlbiBXT1JEPC9zcGFuPg0KYGBge3J9DQpwZGYxIDwtIHBkZl9jb252ZXJ0KCJDOlxcVXNlcnNcXGthdGhpXFxPbmVEcml2ZVxcRXNjcml0b3Jpb1xcTTJfSUEgY29uIEltcGFjdG8gRW1wcmVzYXJpYWxcXHBkZjEucGRmIiwgZHBpPTYwMCkgJT4lIG1hcChvY3IpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij5BY3RpdmlkYWQgMS4gTm92ZWxhICJJVCI8L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+Q29udmVydGlyIGRlIFBERiBhIHRleHRvIGVuIFdPUkQ8L3NwYW4+DQpgYGB7cn0NCnBkZjI8LSBwZGZfY29udmVydCggIkM6XFxVc2Vyc1xca2F0aGlcXE9uZURyaXZlXFxFc2NyaXRvcmlvXFxNMl9JQSBjb24gSW1wYWN0byBFbXByZXNhcmlhbFxcZXNvMy5wZGYiICwgZHBpPTYwMCkgICU+JSBtYXAob2NyKQ0KYGBgDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPkNvbnZlcnRpciBpbcOhZ2VuZXMgZW4gZXNwYcOxb2wgUE5HIGEgdGV4dG8gZW4gV09SRDwvc3Bhbj4NCmBgYHtyfQ0KaW1hZ2VuMyA8LWltYWdlX3JlYWQoImVzbzNfMS5wbmciKQ0KdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQ0KdGV4dDMgPC0gb2NyKGltYWdlbjMsIGVuZ2luZSA9IHRlc3NlcmFjdCgic3BhIikpDQoNCmltYWdlbjQgPC1pbWFnZV9yZWFkKCJlc28zXzIucG5nIikNCnRlc3NlcmFjdF9kb3dubG9hZCgic3BhIikNCnRleHQ0IDwtIG9jcihpbWFnZW40LCBlbmdpbmUgPSB0ZXNzZXJhY3QoInNwYSIpKQ0KDQppbWFnZW41IDwtaW1hZ2VfcmVhZCgiZXNvM18zLnBuZyIpDQp0ZXNzZXJhY3RfZG93bmxvYWQoInNwYSIpDQp0ZXh0NSA8LSBvY3IoaW1hZ2VuNSwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkNCg0KZG9jMyA8LSByZWFkX2RvY3goKSAjQ3JlYSB1biBkb2N1bWVudG8gZGUgd29yZCBlbiBibGFuY28NCg0KDQpkb2MzIDwtIGRvYzMgJT4lDQogIGJvZHlfYWRkX3Bhcih0ZXh0Mywgc3R5bGUgPSAiTm9ybWFsIikgJT4lDQogIGJvZHlfYWRkX3Bhcih0ZXh0NCwgc3R5bGUgPSAiTm9ybWFsIikgJT4lDQogIGJvZHlfYWRkX3Bhcih0ZXh0NSwgc3R5bGUgPSAiTm9ybWFsIikNCg0KIyBHdWFyZGFyIGVsIGRvY3VtZW50bw0KIyBwcmludChkb2MzLCB0YXJnZXQgPSAiaXQuZG9jeCIpDQpgYGANCg0KDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij4yLiBFeHBsb3JhY2nDs24gZGUgRGF0b3M8L3NwYW4+DQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+QW7DoWxpc2lzIGRlIEZyZWN1ZW5jaWFzPC9zcGFuPg0KYGBge3J9DQp0ZXh0IDwtIHJlYWRMaW5lcygiQzpcXFVzZXJzXFxrYXRoaVxcT25lRHJpdmVcXEVzY3JpdG9yaW9cXE0yX0lBIGNvbiBJbXBhY3RvIEVtcHJlc2FyaWFsXFxtYXJ0aW4tbHV0aGVyLWtpbmctaS1oYXZlLWEtZHJlYW0tc3BlZWNoLnR4dCIpDQoNCmNvcnB1cyA8LSBDb3JwdXMoVmVjdG9yU291cmNlKHRleHQpKSAjIFBvbmUgY2FkYSByZW5nbMOzbiBlbiB1bmEgY2VsZGEgZGUgdmVjdG9yDQojIGluc3BlY3QoY29ycHVzKQ0KDQpjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkgIyBQb25lIHRvZG8gZW4gbWluw7pzY3VsYXMNCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikgIyBFbGltaW5hIHB1bnR1YWNpw7NuDQpjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlTnVtYmVycykgIyBFbGltaW5hIG7Dum1lcm9zDQpjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW4iKSkgIyBFbGltaW5hIHBhbGFicmFzIHF1ZSBubyBoYWJsZW4gZGVsIHRlbWENCiMgY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBjKCJkcmVhbSIsICJ3aWxsIikpICNFbGltaW5hIHBhbGFicmFzIHB1bnR1YWxlcw0KDQp0ZG0gPC0gVGVybURvY3VtZW50TWF0cml4KGNvcnB1cykNCm0gPC0gYXMubWF0cml4KHRkbSkgIyBDdWVudGEgbGFzIHZlY2VzIHF1ZSBhcGFyZWNlIGNhZGEgcGFsYWJyYSBwb3IgcmVuZ2zDs24NCiAgICAgICAgICAgICAgIA0KZnJlY3VlbmNpYSA8LSBzb3J0KHJvd1N1bXMobSksIGRlY3JlYXNpbmcgPSBUUlVFKSAjIEN1ZW50YSBsYSBmcmVjdWVuY2lhIGRlIGNhZGEgcGFsYWJyYSBlbiBlbCB0ZXh0byBjb21wbGV0bw0KDQpmcmVjdWVuY2lhX2RmIDwtIGRhdGEuZnJhbWUod29yZD1uYW1lcyhmcmVjdWVuY2lhKSxmcmVxPWZyZWN1ZW5jaWEpICMgQ29udmllcnRlIGxhIGZyZWN1ZW5jaWEgYSBkYXRhIGZyYW1lDQoNCmdncGxvdChoZWFkKGZyZWN1ZW5jaWFfZGYsMTApLCBhZXMoeD13b3JkLCB5PWZyZXEpKSArIA0KICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgZmlsbD0icGluayIpICsgDQogICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGZyZXEpLCB2anVzdCA9IC0wLjUpICsNCiAgICAgICBsYWJzKHRpdGxlPSJUT1AgMTAgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIiwgc3VidGl0bGU9IkRpc2N1cnNvICdJIGhhdmUgYSBEcmVhbScgZGUgTS5MOiBLaW5nIiwgeD0gIlBhbGFicmEiLCB5PSJGcmVjdWVuY2lhIikgKyANCiAgICAgICB5bGltKDAsMjApDQoNCmBgYA0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPk51YmUgZGUgUGFsYWJyYXM8L3NwYW4+DQpgYGB7cn0NCiMgRWwgcHJvY2VzYW1pZW50byBkZSBkYXRvcyBhbnRlcyBkZSBsYSBudWJlIGRlIHBhbGFicmFzIGVzIGlndWFsIHF1ZSBlbiBlbCBBbsOhbGlzaXMgZGUgRnJlY3VlbmNpYXMsIGRlc2RlIGltcG9ydGFyIGVsIHRleHRvIGhhc3RhIGZyZWN1ZW5jaWFfZGYNCnNldC5zZWVkKDEyMykNCndvcmRjbG91ZCh3b3Jkcz1mcmVjdWVuY2lhX2RmJHdvcmQsZnJlcT1mcmVjdWVuY2lhX2RmJGZyZXEsIG1pbi5mcmVxPTEsDQpyYW5kb20ub3JkZXI9RkFMU0UsIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIlJkUHUiKSkNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+RWplcmNpY2lvIDIuIE5vdmVsYSBJVDwvc3Bhbj4NCmBgYHtyfQ0KdGV4dG8gPC0gcmVhZExpbmVzKCJDOlxcVXNlcnNcXGthdGhpXFxPbmVEcml2ZVxcRXNjcml0b3Jpb1xcTTJfSUEgY29uIEltcGFjdG8gRW1wcmVzYXJpYWxcXGl0LnR4dCIpDQoNCnRleHQyIDwtIGljb252KHRleHRvLCB0byA9ICJVVEYtOCIsIHN1YiA9ICJieXRlIikgIyBDb252ZXJ0aXIgdGV4dG8gY29kaWZpY2FkbyBhIGNhcmFjdGVyZXMgbGVnaWJsZXMNCg0KY29ycHVzMiA8LSBDb3JwdXMoVmVjdG9yU291cmNlKHRleHQyKSkgDQoNCmNvcnB1czIgPC0gdG1fbWFwKGNvcnB1czIsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgUG9uZSB0b2RvIGVuIG1pbsO6c2N1bGFzDQpjb3JwdXMyIDwtIHRtX21hcChjb3JwdXMyLCByZW1vdmVQdW5jdHVhdGlvbikgIyBFbGltaW5hIHB1bnR1YWNpw7NuDQpjb3JwdXMyIDwtIHRtX21hcChjb3JwdXMyLCByZW1vdmVOdW1iZXJzKSAjIEVsaW1pbmEgbsO6bWVyb3MNCmNvcnB1czIgPC0gdG1fbWFwKGNvcnB1czIsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoInNwYW5pc2giKSkgIyBFbGltaW5hIHBhbGFicmFzIHF1ZSBubyBoYWJsZW4gZGVsIHRlbWENCg0KIyBjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlV29yZHMsIGMoImRyZWFtIiwgIndpbGwiKSkgI0VsaW1pbmEgcGFsYWJyYXMgcHVudHVhbGVzDQoNCnRkbTIgPC0gVGVybURvY3VtZW50TWF0cml4KGNvcnB1czIpDQptMiA8LSBhcy5tYXRyaXgodGRtMikgIyBDdWVudGEgbGFzIHZlY2VzIHF1ZSBhcGFyZWNlIGNhZGEgcGFsYWJyYSBwb3IgcmVuZ2zDs24NCg0KZnJlY3VlbmNpYTIgPC0gc29ydChyb3dTdW1zKG0yKSwgZGVjcmVhc2luZyA9IFRSVUUpICMgQ3VlbnRhIGxhIGZyZWN1ZW5jaWEgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIHRleHRvIGNvbXBsZXRvDQoNCmZyZWN1ZW5jaWFfZGYyIDwtIGRhdGEuZnJhbWUod29yZCA9IG5hbWVzKGZyZWN1ZW5jaWEyKSwgZnJlcSA9IGZyZWN1ZW5jaWEyKSAjIENvbnZpZXJ0ZSBsYSBmcmVjdWVuY2lhIGEgZGF0YSBmcmFtZQ0KDQpnZ3Bsb3QoaGVhZChmcmVjdWVuY2lhX2RmMiwgMTApLCBhZXMoeCA9IHJlb3JkZXIod29yZCwgLWZyZXEpLCB5ID0gZnJlcSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAicmVkIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gZnJlcSksIHZqdXN0ID0gLTAuNSkgKw0KICBsYWJzKHRpdGxlID0gIlRPUCAxMCBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMiLCANCiAgICAgICBzdWJ0aXRsZSA9ICJJVCIsIA0KICAgICAgIHggPSAiUGFsYWJyYSIsIA0KICAgICAgIHkgPSAiRnJlY3VlbmNpYSIpICsNCiAgeWxpbSgwLCAyMCkNCmBgYA0KDQoNCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+TnViZSBkZSBQYWxhYnJhczwvc3Bhbj4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIEVsIHByb2Nlc2FtaWVudG8gZGUgZGF0b3MgYW50ZXMgZGUgbGEgbnViZSBkZSBwYWxhYnJhcyBlcyBpZ3VhbCBxdWUgZW4gZWwgQW7DoWxpc2lzIGRlIEZyZWN1ZW5jaWFzLCBkZXNkZSBpbXBvcnRhciBlbCB0ZXh0byBoYXN0YSBmcmVjdWVuY2lhX2RmMg0Kc2V0LnNlZWQoMTIzKQ0Kd29yZGNsb3VkKHdvcmRzPWZyZWN1ZW5jaWFfZGYyJHdvcmQsZnJlcT1mcmVjdWVuY2lhX2RmMiRmcmVxLCBtaW4uZnJlcT0xLA0KcmFuZG9tLm9yZGVyPUZBTFNFLCBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJSZFB1IikpDQpgYGANCg==