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ágines de texto en texto editable. También es conocido como extracción de texto de imágines
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.

Librerias

library(tidyverse) # Data Wrangling
library(tesseract) # OCR
library(magick) # Permite trabajar con imágenes de formato PNG
library(officer) # Permite exportar en formatos que son de OFFICE (ej. Word)
library(pdftools) # Permite leer PDFS
library(purrr) # Para la función "map"
library(tm) # Minería de texto
library(RColorBrewer) # Colores
library(wordcloud) # Crea nubes de palabras
library(topicmodels)
library(ggplot2) # Generador de gráficas con más diseño

Obtener datos (OCR)

De imagen PNG a Texto (Ingles)

# Texto en Ingles
imagen1 = image_read("C:\\Users\\Silva\\Downloads\\imagen1.PNG")
texto1 = ocr(imagen1)
#texto1
doc1 = read_docx() # Crea un documento 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

Consultar idiomas disponibles

De imagen PNG a Texto (Español)

imagen2 = image_read("C:\\Users\\Silva\\Downloads\\imagen2.PNG")
tesseract_download("spa")
## [1] "C:\\Users\\Silva\\AppData\\Local\\tesseract5\\tesseract5\\tessdata/spa.traineddata"
texto2 = ocr(imagen2, engine= tesseract("spa"))
#texto2
doc2 = read_docx() # Crea un documento 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 a Word

#pdf1 = pdf_convert("C:\\Users\\Silva\\Downloads\\pdf1.pdf", dpi = 600) %>% map(ocr)

Actividad1. Novela

novela = pdf_convert("C:\\Users\\Silva\\Downloads\\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!
# 2. Crear un documento de Word y agregar los textos
doc3 <- read_docx()

# Agregar texto del PDF
for (texto_pagina in novela) {
  doc3 <- doc3 %>% body_add_par(texto_pagina, style = "Normal")
}

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

Exploración de Datos

Análisis de Frecuencias

text = readLines("http://www.sthda.com/sthda/RDoc/example-files/martin-luther-king-i-have-a-dream-speech.txt") # Traer texto de Internet

Corpus =Corpus(VectorSource(text)) # Pone cada renglón en una celda de vector
Corpus = tm_map(Corpus, content_transformer(tolower)) # Pone todo en minúsculas
Corpus = tm_map(Corpus, removePunctuation) # Elimina puntuación
Corpus = tm_map(Corpus, removeNumbers) # Elimina números 
Corpus = tm_map(Corpus, removeWords, stopwords("en")) # Elimina las "stopwords" palabras inutiles para el análisis de texto

#Corpus = tm_map(Corpus, removeWords, c("","")) # Elimina palabras seleccionadas a mano
tdm = TermDocumentMatrix(Corpus) # Cuenta las veces que aparece cada palabra por renglón
m = as.matrix(tdm)

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 por Frecuencia",
         subtitle = "Discurso 'I have a Dream' de M. L. King ",
       x = "Palabra",
       y = "Frecuencia",)+
  theme_minimal()+
  ylim(0,20)

Nube de palabras

# El procesamiento de datos antes de la nube de palabras es igual que en el Anáisis de Frecuencias, desde importar el texto hsta frecuencia_df
set.seed(123)
wordcloud(words = frecuencia_df$word, freq = frecuencia_df$freq, min.freq = 1, random.order = FALSE,
          scale = c(3, 0.5),  # Ajustar el rango de tamaños de palabras
          colors = brewer.pal(8, "Dark2"),  # Utilizar una paleta de colores
          max.words = 100,  # Número máximo de palabras a mostrar
          random.color = TRUE,  # Colores aleatorios
          rot.per = 0.35,  # Porcentaje de palabras rotadas
          ordered.colors = FALSE  # Colores desordenados
          )

Actividad IT

Corpus2 =Corpus(VectorSource(novela)) # Pone cada renglón en una celda de vector
Corpus2 = tm_map(Corpus2, content_transformer(tolower)) # Pone todo en minúsculas
Corpus2 = tm_map(Corpus2, removePunctuation) # Elimina puntuación
Corpus2 = tm_map(Corpus2, removeNumbers) # Elimina números 
Corpus2 = tm_map(Corpus2, removeWords, stopwords("spa")) # Elimina las "stopwords" palabras inutiles para el análisis de texto
Corpus2 = tm_map(Corpus2, removeWords, c("—","--","__")) # Elimina palabras seleccionadas a mano
Corpus2 = tm_map(Corpus2, content_transformer(function(x) gsub("[[:punct:]]", " ", x)))


#inspect(Corpus2)
tdm2 = TermDocumentMatrix(Corpus2) # Cuenta las veces que aparece cada palabra por renglón
m2 = as.matrix(tdm2)

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 en un data frame
ggplot(head(frecuencia_df2, 10), aes(x = reorder(word, -freq), y = freq, fill = freq)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = freq), vjust = -0.3) +
  scale_fill_gradient(low = "skyblue", high = "navy", limits = c(0, 30)) +
  labs(title = "Top 10 Palabras por Frecuencia",
       subtitle = "Novela 'It' de Stephen King",
       x = "Palabra",
       y = "Frecuencia") +
  theme_minimal()

# El procesamiento de datos antes de la nube de palabras es igual que en el Anáisis de Frecuencias, desde importar el texto hsta frecuencia_df
set.seed(123)
wordcloud(words = frecuencia_df2$word, freq = frecuencia_df2$freq, min.freq = 2, random.order = FALSE,
          scale = c(3, 0.5),  # Ajustar el rango de tamaños de palabras
          colors = brewer.pal(8, "Dark2"),  # Utilizar una paleta de colores
          max.words = 100,  # Número máximo de palabras a mostrar
          random.color = TRUE,  # Colores aleatorios
          rot.per = 0.35,  # Porcentaje de palabras rotadas
          ordered.colors = FALSE  # Colores desordenados
          )

LS0tDQp0aXRsZTogIk0yX1RleHRNaW5pbmciDQphdXRob3I6ICJKb3PDqSBBcnR1cm8gU2lsdmEgRmxvcmVzIEEwMTE5ODA0OSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KICAgIHRoZW1lOiBjb3Ntbw0KLS0tDQoNCiFbXShDOlxcVXNlcnNcXFNpbHZhXFxEb2N1bWVudHNcXENvbmNlbnRyYWNpw7NuXFxJVC5naWYpDQoNCiMgVGVvcsOtYQ0KTGEgKiptaW5lcsOtYSBkZSB0ZXh0byoqIChUTSkgZXMgZWwgIHByb2Nlc28gZGUgZXh0cmFlciBpbmZvcm1hY2nDs24gw7p0aWwsIHBhdHJvbmVzIG8gY29ub2NpbWllbnRvIGRlIHRleHRvcyBubyBlc3RydWN0dXJhZG9zLg0KDQpDb25zdGEgZGUgMyBldGFwYXM6DQoxLiBPYnRlbmVyIGRhdG9zOiBFbCAqKnJlY29ub2NpbWllbnRvIMOzcHRpY28gZGUgY2FyYWN0ZXJlcyoqIChPQ1IpIGVzIHVuYSB0ZWNub2xvZ8OtYSBxdWUgcGVybWl0ZSBjb252ZXJ0aXIgaW3DoWdpbmVzIGRlIHRleHRvIGVuIHRleHRvIGVkaXRhYmxlLiBUYW1iacOpbiBlcyBjb25vY2lkbyBjb21vICoqZXh0cmFjY2nDs24gZGUgdGV4dG8gZGUgaW3DoWdpbmVzKiogIA0KMi4gRXhwbG9yYXIgZGF0b3M6IFJlcHJlc2VudGFjacOzbiBncsOhZmljYSBvIHZpc3VhbCBkZSBsb3MgZGF0b3MgcGFyYSBzdSBpbnRlcnByZXRhY2nDs24uIExvcyBtw6l0b2RvcyBtw6FzIGNvbXVuZXMgc29uIGVsICoqQW7DoWxpc2lzIGRlIFNlbnRpbWllbnRvcyoqLCBsYSAqKk51YmUgZGUgUGFsYWJyYXMqKiB5IGVsICoqVG9waWMgTW9kZWxpbmcqKi4gIA0KMy4gQW7DoWxpc2lzIHByZWRpY3Rpdm86IFNvbiBsYXMgdMOpY25pY2FzIHkgbW9kZWxvcyBlc3RhZMOtc3RpY29zIHBhcmEgcHJlZGVjaXIgcmVzdWx0YWRvcyBmdXR1cm9zLiBMb3MgbW9kZWxvcyBtw6FzIHVzYWRvcyBzb24gZWwgKipSYW5kb20gRm9yZXN0KiosIFJlZGVzIE5ldXJvbmFsZXMgeSBSZWdyZXNpb25lcy4NCg0KDQojIExpYnJlcmlhcw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKSAjIERhdGEgV3JhbmdsaW5nDQpsaWJyYXJ5KHRlc3NlcmFjdCkgIyBPQ1INCmxpYnJhcnkobWFnaWNrKSAjIFBlcm1pdGUgdHJhYmFqYXIgY29uIGltw6FnZW5lcyBkZSBmb3JtYXRvIFBORw0KbGlicmFyeShvZmZpY2VyKSAjIFBlcm1pdGUgZXhwb3J0YXIgZW4gZm9ybWF0b3MgcXVlIHNvbiBkZSBPRkZJQ0UgKGVqLiBXb3JkKQ0KbGlicmFyeShwZGZ0b29scykgIyBQZXJtaXRlIGxlZXIgUERGUw0KbGlicmFyeShwdXJycikgIyBQYXJhIGxhIGZ1bmNpw7NuICJtYXAiDQpsaWJyYXJ5KHRtKSAjIE1pbmVyw61hIGRlIHRleHRvDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikgIyBDb2xvcmVzDQpsaWJyYXJ5KHdvcmRjbG91ZCkgIyBDcmVhIG51YmVzIGRlIHBhbGFicmFzDQpsaWJyYXJ5KHRvcGljbW9kZWxzKQ0KbGlicmFyeShnZ3Bsb3QyKSAjIEdlbmVyYWRvciBkZSBncsOhZmljYXMgY29uIG3DoXMgZGlzZcOxbw0KYGBgDQoNCg0KIyBPYnRlbmVyIGRhdG9zIChPQ1IpDQoNCiMjIERlIGltYWdlbiBQTkcgYSBUZXh0byAoSW5nbGVzKQ0KYGBge3J9DQojIFRleHRvIGVuIEluZ2xlcw0KaW1hZ2VuMSA9IGltYWdlX3JlYWQoIkM6XFxVc2Vyc1xcU2lsdmFcXERvd25sb2Fkc1xcaW1hZ2VuMS5QTkciKQ0KdGV4dG8xID0gb2NyKGltYWdlbjEpDQojdGV4dG8xDQpkb2MxID0gcmVhZF9kb2N4KCkgIyBDcmVhIHVuIGRvY3VtZW50byBlbiBibGFuY28NCmRvYzEgPSBkb2MxICU+JSANCiAgYm9keV9hZGRfcGFyKHRleHRvMSwgc3R5bGUgPSJOb3JtYWwiKSAjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZA0KI3ByaW50KGRvYzEsIHRhcmdldCA9ICJ0ZXh0bzEuZG9jeCIpICMgR3VhcmRhIGVsIHdvcmQgZW4gbGEgY29tcHV0YWRvcmENCmBgYA0KDQoNCltDb25zdWx0YXIgaWRpb21hcyBkaXNwb25pYmxlc10oaHR0cHM6Ly90ZXNzZXJhY3Qtb2NyLmdpdGh1Yi5pby90ZXNzZG9jL0RhdGEtRmlsZXMtaW4tZGlmZmVyZW50LXZlcnNpb25zLmh0bWwpICANCg0KIyMgRGUgaW1hZ2VuIFBORyBhIFRleHRvIChFc3Bhw7FvbCkNCmBgYHtyfQ0KaW1hZ2VuMiA9IGltYWdlX3JlYWQoIkM6XFxVc2Vyc1xcU2lsdmFcXERvd25sb2Fkc1xcaW1hZ2VuMi5QTkciKQ0KdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQ0KdGV4dG8yID0gb2NyKGltYWdlbjIsIGVuZ2luZT0gdGVzc2VyYWN0KCJzcGEiKSkNCiN0ZXh0bzINCmRvYzIgPSByZWFkX2RvY3goKSAjIENyZWEgdW4gZG9jdW1lbnRvIGVuIGJsYW5jbw0KZG9jMiA9IGRvYzIgJT4lIA0KICBib2R5X2FkZF9wYXIodGV4dG8yLCBzdHlsZSA9Ik5vcm1hbCIpICMgUGVnYSBlbCB0ZXh0byBlbiBlbCB3b3JkDQojcHJpbnQoZG9jMiwgdGFyZ2V0ID0gInRleHRvMi5kb2N4IikgIyBHdWFyZGEgZWwgd29yZCBlbiBsYSBjb21wdXRhZG9yYQ0KYGBgDQoNCiMjIERlIFBERiBhIHRleHRvIGEgV29yZA0KYGBge3J9DQojcGRmMSA9IHBkZl9jb252ZXJ0KCJDOlxcVXNlcnNcXFNpbHZhXFxEb3dubG9hZHNcXHBkZjEucGRmIiwgZHBpID0gNjAwKSAlPiUgbWFwKG9jcikNCmBgYA0KDQojIyBBY3RpdmlkYWQxLiBOb3ZlbGENCmBgYHtyfQ0Kbm92ZWxhID0gcGRmX2NvbnZlcnQoIkM6XFxVc2Vyc1xcU2lsdmFcXERvd25sb2Fkc1xcZXNvMy5wZGYiLCBkcGkgPSA2MDApICU+JSBtYXAob2NyKQ0KDQojIDIuIENyZWFyIHVuIGRvY3VtZW50byBkZSBXb3JkIHkgYWdyZWdhciBsb3MgdGV4dG9zDQpkb2MzIDwtIHJlYWRfZG9jeCgpDQoNCiMgQWdyZWdhciB0ZXh0byBkZWwgUERGDQpmb3IgKHRleHRvX3BhZ2luYSBpbiBub3ZlbGEpIHsNCiAgZG9jMyA8LSBkb2MzICU+JSBib2R5X2FkZF9wYXIodGV4dG9fcGFnaW5hLCBzdHlsZSA9ICJOb3JtYWwiKQ0KfQ0KDQojIEd1YXJkYXIgZWwgZG9jdW1lbnRvIGRlIFdvcmQNCiNwcmludChkb2MzLCB0YXJnZXQgPSAiZG9jMy5kb2N4IikNCmBgYA0KDQojIEV4cGxvcmFjacOzbiBkZSBEYXRvcw0KDQojIyBBbsOhbGlzaXMgZGUgRnJlY3VlbmNpYXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0ZXh0ID0gcmVhZExpbmVzKCJodHRwOi8vd3d3LnN0aGRhLmNvbS9zdGhkYS9SRG9jL2V4YW1wbGUtZmlsZXMvbWFydGluLWx1dGhlci1raW5nLWktaGF2ZS1hLWRyZWFtLXNwZWVjaC50eHQiKSAjIFRyYWVyIHRleHRvIGRlIEludGVybmV0DQoNCkNvcnB1cyA9Q29ycHVzKFZlY3RvclNvdXJjZSh0ZXh0KSkgIyBQb25lIGNhZGEgcmVuZ2zDs24gZW4gdW5hIGNlbGRhIGRlIHZlY3Rvcg0KQ29ycHVzID0gdG1fbWFwKENvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkgIyBQb25lIHRvZG8gZW4gbWluw7pzY3VsYXMNCkNvcnB1cyA9IHRtX21hcChDb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKSAjIEVsaW1pbmEgcHVudHVhY2nDs24NCkNvcnB1cyA9IHRtX21hcChDb3JwdXMsIHJlbW92ZU51bWJlcnMpICMgRWxpbWluYSBuw7ptZXJvcyANCkNvcnB1cyA9IHRtX21hcChDb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuIikpICMgRWxpbWluYSBsYXMgInN0b3B3b3JkcyIgcGFsYWJyYXMgaW51dGlsZXMgcGFyYSBlbCBhbsOhbGlzaXMgZGUgdGV4dG8NCg0KI0NvcnB1cyA9IHRtX21hcChDb3JwdXMsIHJlbW92ZVdvcmRzLCBjKCIiLCIiKSkgIyBFbGltaW5hIHBhbGFicmFzIHNlbGVjY2lvbmFkYXMgYSBtYW5vDQpgYGANCg0KDQpgYGB7cn0NCnRkbSA9IFRlcm1Eb2N1bWVudE1hdHJpeChDb3JwdXMpICMgQ3VlbnRhIGxhcyB2ZWNlcyBxdWUgYXBhcmVjZSBjYWRhIHBhbGFicmEgcG9yIHJlbmdsw7NuDQptID0gYXMubWF0cml4KHRkbSkNCg0KZnJlY3VlbmNpYSA9IHNvcnQocm93U3VtcyhtKSwgZGVjcmVhc2luZyA9IFRSVUUpICMgQ3VlbnRhIGxhIGZyZWN1ZW5jaWEgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIHRleHRvIGNvbXBsZXRvDQpmcmVjdWVuY2lhX2RmID0gZGF0YS5mcmFtZSh3b3JkID0gbmFtZXMoZnJlY3VlbmNpYSksIGZyZXEgPSBmcmVjdWVuY2lhKSAjIENvbnZpZXJ0ZSBsYSBmcmVjdWVuY2lhIGVuIHVuIGRhdGEgZnJhbWUNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChoZWFkKGZyZWN1ZW5jaWFfZGYsMTApLCBhZXMoeCA9IHJlb3JkZXIod29yZCwtZnJlcSksIHkgPSBmcmVxKSkgKw0KICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSJza3libHVlIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID1mcmVxKSwgdmp1c3QgPSAtMC41KSsNCiAgICBsYWJzKHRpdGxlID0gIlRvcCAxMCBQYWxhYnJhcyBwb3IgRnJlY3VlbmNpYSIsDQogICAgICAgICBzdWJ0aXRsZSA9ICJEaXNjdXJzbyAnSSBoYXZlIGEgRHJlYW0nIGRlIE0uIEwuIEtpbmcgIiwNCiAgICAgICB4ID0gIlBhbGFicmEiLA0KICAgICAgIHkgPSAiRnJlY3VlbmNpYSIsKSsNCiAgdGhlbWVfbWluaW1hbCgpKw0KICB5bGltKDAsMjApDQpgYGANCg0KIyMgTnViZSBkZSBwYWxhYnJhcw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgRWwgcHJvY2VzYW1pZW50byBkZSBkYXRvcyBhbnRlcyBkZSBsYSBudWJlIGRlIHBhbGFicmFzIGVzIGlndWFsIHF1ZSBlbiBlbCBBbsOhaXNpcyBkZSBGcmVjdWVuY2lhcywgZGVzZGUgaW1wb3J0YXIgZWwgdGV4dG8gaHN0YSBmcmVjdWVuY2lhX2RmDQpzZXQuc2VlZCgxMjMpDQp3b3JkY2xvdWQod29yZHMgPSBmcmVjdWVuY2lhX2RmJHdvcmQsIGZyZXEgPSBmcmVjdWVuY2lhX2RmJGZyZXEsIG1pbi5mcmVxID0gMSwgcmFuZG9tLm9yZGVyID0gRkFMU0UsDQogICAgICAgICAgc2NhbGUgPSBjKDMsIDAuNSksICAjIEFqdXN0YXIgZWwgcmFuZ28gZGUgdGFtYcOxb3MgZGUgcGFsYWJyYXMNCiAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpLCAgIyBVdGlsaXphciB1bmEgcGFsZXRhIGRlIGNvbG9yZXMNCiAgICAgICAgICBtYXgud29yZHMgPSAxMDAsICAjIE7Dum1lcm8gbcOheGltbyBkZSBwYWxhYnJhcyBhIG1vc3RyYXINCiAgICAgICAgICByYW5kb20uY29sb3IgPSBUUlVFLCAgIyBDb2xvcmVzIGFsZWF0b3Jpb3MNCiAgICAgICAgICByb3QucGVyID0gMC4zNSwgICMgUG9yY2VudGFqZSBkZSBwYWxhYnJhcyByb3RhZGFzDQogICAgICAgICAgb3JkZXJlZC5jb2xvcnMgPSBGQUxTRSAgIyBDb2xvcmVzIGRlc29yZGVuYWRvcw0KICAgICAgICAgICkNCmBgYA0KDQoNCiMjIEFjdGl2aWRhZCBJVA0KYGBge3IgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0KQ29ycHVzMiA9Q29ycHVzKFZlY3RvclNvdXJjZShub3ZlbGEpKSAjIFBvbmUgY2FkYSByZW5nbMOzbiBlbiB1bmEgY2VsZGEgZGUgdmVjdG9yDQpDb3JwdXMyID0gdG1fbWFwKENvcnB1czIsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgUG9uZSB0b2RvIGVuIG1pbsO6c2N1bGFzDQpDb3JwdXMyID0gdG1fbWFwKENvcnB1czIsIHJlbW92ZVB1bmN0dWF0aW9uKSAjIEVsaW1pbmEgcHVudHVhY2nDs24NCkNvcnB1czIgPSB0bV9tYXAoQ29ycHVzMiwgcmVtb3ZlTnVtYmVycykgIyBFbGltaW5hIG7Dum1lcm9zIA0KQ29ycHVzMiA9IHRtX21hcChDb3JwdXMyLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJzcGEiKSkgIyBFbGltaW5hIGxhcyAic3RvcHdvcmRzIiBwYWxhYnJhcyBpbnV0aWxlcyBwYXJhIGVsIGFuw6FsaXNpcyBkZSB0ZXh0bw0KQ29ycHVzMiA9IHRtX21hcChDb3JwdXMyLCByZW1vdmVXb3JkcywgYygi4oCUIiwiLS0iLCJfXyIpKSAjIEVsaW1pbmEgcGFsYWJyYXMgc2VsZWNjaW9uYWRhcyBhIG1hbm8NCkNvcnB1czIgPSB0bV9tYXAoQ29ycHVzMiwgY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbih4KSBnc3ViKCJbWzpwdW5jdDpdXSIsICIgIiwgeCkpKQ0KDQoNCiNpbnNwZWN0KENvcnB1czIpDQpgYGANCg0KYGBge3J9DQp0ZG0yID0gVGVybURvY3VtZW50TWF0cml4KENvcnB1czIpICMgQ3VlbnRhIGxhcyB2ZWNlcyBxdWUgYXBhcmVjZSBjYWRhIHBhbGFicmEgcG9yIHJlbmdsw7NuDQptMiA9IGFzLm1hdHJpeCh0ZG0yKQ0KDQpmcmVjdWVuY2lhMiA9IHNvcnQocm93U3VtcyhtMiksIGRlY3JlYXNpbmcgPSBUUlVFKSAjIEN1ZW50YSBsYSBmcmVjdWVuY2lhIGRlIGNhZGEgcGFsYWJyYSBlbiBlbCB0ZXh0byBjb21wbGV0bw0KZnJlY3VlbmNpYV9kZjIgPSBkYXRhLmZyYW1lKHdvcmQgPSBuYW1lcyhmcmVjdWVuY2lhMiksIGZyZXEgPSBmcmVjdWVuY2lhMikgIyBDb252aWVydGUgbGEgZnJlY3VlbmNpYSBlbiB1biBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoaGVhZChmcmVjdWVuY2lhX2RmMiwgMTApLCBhZXMoeCA9IHJlb3JkZXIod29yZCwgLWZyZXEpLCB5ID0gZnJlcSwgZmlsbCA9IGZyZXEpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBmcmVxKSwgdmp1c3QgPSAtMC4zKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gInNreWJsdWUiLCBoaWdoID0gIm5hdnkiLCBsaW1pdHMgPSBjKDAsIDMwKSkgKw0KICBsYWJzKHRpdGxlID0gIlRvcCAxMCBQYWxhYnJhcyBwb3IgRnJlY3VlbmNpYSIsDQogICAgICAgc3VidGl0bGUgPSAiTm92ZWxhICdJdCcgZGUgU3RlcGhlbiBLaW5nIiwNCiAgICAgICB4ID0gIlBhbGFicmEiLA0KICAgICAgIHkgPSAiRnJlY3VlbmNpYSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQogIA0KIyBFbCBwcm9jZXNhbWllbnRvIGRlIGRhdG9zIGFudGVzIGRlIGxhIG51YmUgZGUgcGFsYWJyYXMgZXMgaWd1YWwgcXVlIGVuIGVsIEFuw6Fpc2lzIGRlIEZyZWN1ZW5jaWFzLCBkZXNkZSBpbXBvcnRhciBlbCB0ZXh0byBoc3RhIGZyZWN1ZW5jaWFfZGYNCnNldC5zZWVkKDEyMykNCndvcmRjbG91ZCh3b3JkcyA9IGZyZWN1ZW5jaWFfZGYyJHdvcmQsIGZyZXEgPSBmcmVjdWVuY2lhX2RmMiRmcmVxLCBtaW4uZnJlcSA9IDIsIHJhbmRvbS5vcmRlciA9IEZBTFNFLA0KICAgICAgICAgIHNjYWxlID0gYygzLCAwLjUpLCAgIyBBanVzdGFyIGVsIHJhbmdvIGRlIHRhbWHDsW9zIGRlIHBhbGFicmFzDQogICAgICAgICAgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSwgICMgVXRpbGl6YXIgdW5hIHBhbGV0YSBkZSBjb2xvcmVzDQogICAgICAgICAgbWF4LndvcmRzID0gMTAwLCAgIyBOw7ptZXJvIG3DoXhpbW8gZGUgcGFsYWJyYXMgYSBtb3N0cmFyDQogICAgICAgICAgcmFuZG9tLmNvbG9yID0gVFJVRSwgICMgQ29sb3JlcyBhbGVhdG9yaW9zDQogICAgICAgICAgcm90LnBlciA9IDAuMzUsICAjIFBvcmNlbnRhamUgZGUgcGFsYWJyYXMgcm90YWRhcw0KICAgICAgICAgIG9yZGVyZWQuY29sb3JzID0gRkFMU0UgICMgQ29sb3JlcyBkZXNvcmRlbmFkb3MNCiAgICAgICAgICApDQpgYGANCg0KDQo=