Text Mining

Teoría

La minería de texto es el proceso de extraer información util, patrones o conocimientos de texto 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 de imágenes. 2. Explorar datos: Representación gráfica o visual de los datos para su interpretación. Los metodos más comunes son el Analisis de Sentimientos, la Nube de Palabras y el Topic Modeling. 3. Análisis predictivo: Son las tecnicas y modelos estadísticos para predecir resultados futuros. Los modelos más usados son Random Forest, Redes Neuronales y Regressiones.

Instalar paquetes y llamar Librerías

library(tidyverse)
library(tesseract)
library(magick)
library(officer)
library(pdftools)
library(purrr)
library(tm)
library(wordcloud)
library(RColorBrewer)
library(topicmodels)
library(ggplot2)

Obtener Datos mediante OCR

imagen1 <- image_read("/Users/daviddrums180/Tec/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()
doc1 <- doc1 %>%
  body_add_par(texto1, style = "Normal")

print(doc1, target = "/Users/daviddrums180/Tec/texto1.docx")

Imagen en español PNG a texto en WORD

Consultar idiomas disponibles

imagen2 <- image_read("/Users/daviddrums180/Tec/imagen2.PNG")
tesseract_download("spa")
## [1] "/Users/daviddrums180/Library/Application Support/tesseract5/tessdata/spa.traineddata"
texto2 <- ocr(imagen2, engine = tesseract("spa"))
texto2
## [1] "Un importante, y quizá controversial, asunto político es el que se refiere al efecto del salario mínimo 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 mínimos.\n"
doc2 <- read_docx()
doc2 <- doc2 %>%
  body_add_par(texto2, style = "Normal")

# print(doc2, target = "/Users/daviddrums180/Tec/texto2.docx")

De PDF a Texto en Word

# pdf1 <- pdf_convert("/Users/daviddrums180/Tec/pdf1.pdf", dpi = 600) %>%
  # map(ocr)

Actividad 1. Novela “IT”

pdf_path <- "/Users/daviddrums180/Tec/eso3.pdf"
it <- pdf_convert(pdf_path, dpi = 600)
## Converting page 1 to eso3_1.png... done!
## Converting page 2 to eso3_2.png... done!
## Converting page 3 to eso3_3.png... done!
# Configuración del engine de Tesseract para OCR
engine <- tesseract("spa") # Ajusta al idioma de tu documento, "spa" para español por ejemplo

# Convertir el PDF a imágenes y aplicar OCR en cada una
pdf_path <- "/Users/daviddrums180/Tec/eso3.pdf"
imagenes <- pdf_convert(pdf_path, dpi = 600)
## Converting page 1 to eso3_1.png... done!
## Converting page 2 to eso3_2.png... done!
## Converting page 3 to eso3_3.png... done!
# Crear un nuevo documento de Word
doc <- read_docx()

# Aplicar OCR a cada imagen y añadir el texto al documento de Word
for(imagen_path in imagenes) {
  texto <- ocr(image_read(imagen_path), engine = engine)
  doc <- doc %>% 
    body_add_par(texto, style = "Normal") %>%
    body_add_par("", style = "Normal") # Añade un párrafo en blanco para separar las páginas
}

# Guardar el documento de Word
# print(doc, target = "/Users/daviddrums180/Tec/it.docx")

Leer e inspeccionar en línea

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 de vector

corpus <- tm_map(corpus, content_transformer(tolower))
corpus <- tm_map(corpus, removePunctuation)
corpus <- tm_map(corpus, removeNumbers)
corpus <- tm_map(corpus, removeWords, stopwords("en"))

# inspect(corpus)

# corpus <- tm_map(corpus, removeWords, c("dream","will"))

tdm <- TermDocumentMatrix(corpus)
m <- as.matrix(tdm)

frecuencia <- sort(rowSums(m), decreasing = TRUE)
frecuencia_df <- data.frame(word=names(frecuencia), freq = frecuencia)

ggplot(head(frecuencia_df, 10), aes(x = reorder(word, -freq), y = freq, fill = freq)) + 
  geom_col() +  # Dibuja las barras permitiendo gradiente de color
  geom_text(aes(label = freq), vjust = -0.3) +  # Añade números encima de las barras
  scale_fill_gradient(low = "lightblue", high = "blue", limits = c(0, 20)) +  # Gradiente de azul con escala de 0 a 20
  labs(title = "TOP 10 Palabras Más Frecuentes", 
       subtitle = "Discurso Martin Luther King", 
       x = "Palabra", 
       y = "Frecuencia") +
  theme_minimal() 

# El procesamiento de datos antes de la nube de palabras es igual que en el analisis de frecuencias, desde importar el texto hasta la frecuencia en el df
set.seed(123)
wordcloud(words = frecuencia_df$word,
          freq = frecuencia_df$freq,
          min.freq = 2,
          random.order = FALSE,
          rot.per = 0.35, # Permite una rotación del 35% de las palabras
          colors = brewer.pal(8, "Dark2"), # Usa una paleta de colores 'Dark2' con 8 colores
          scale = c(4,0.5), # Ajusta el tamaño de las palabras más y menos frecuentes
          max.words = 100) # Limita el número de palabras a 100

Actividad 2

# Cargar las librerías necesarias
library(tm)
library(wordcloud)
library(RColorBrewer)
library(officer)

# Leer el documento .docx
doc <- read_docx("/Users/daviddrums180/Tec/it.docx")

# Extraer el texto del documento
texto <- docx_summary(doc)
texto_completo <- paste(texto$text, collapse=" ")

# Crear un corpus del texto
corpus <- Corpus(VectorSource(texto_completo))

# Preprocesar el texto
corpus <- tm_map(corpus, content_transformer(tolower)) # Convertir a minúsculas
corpus <- tm_map(corpus, removePunctuation) # Quitar puntuación
corpus <- tm_map(corpus, removeNumbers) # Quitar números
corpus <- tm_map(corpus, removeWords, stopwords("spanish")) # Quitar stopwords en español
corpus <- tm_map(corpus, stripWhitespace) # Quitar espacios en blanco adicionales

# Crear una tabla de frecuencias de las palabras
dtm <- TermDocumentMatrix(corpus)
matriz <- as.matrix(dtm)
frecuencia <- sort(rowSums(matriz), decreasing=TRUE)
frecuencia_df <- data.frame(word = names(frecuencia), freq = frecuencia)

ggplot(head(frecuencia_df, 10), aes(x = reorder(word, -freq), y = freq, fill = freq)) + 
  geom_col() +  # Dibuja las barras permitiendo gradiente de color
  geom_text(aes(label = freq), vjust = -0.3) +  # Añade números encima de las barras
  scale_fill_gradient(low = "lightblue", high = "blue", limits = c(0, 30)) +  # Gradiente de azul con escala de 0 a 20
  labs(title = "TOP 10 Palabras Más Frecuentes", 
       subtitle = "IT: 3 Capítulos", 
       x = "Palabra", 
       y = "Frecuencia") +
  theme_minimal() 

# Generar la nube de palabras
set.seed(123)
wordcloud(words = frecuencia_df$word, freq = frecuencia_df$freq, min.freq = 2,
          random.order=FALSE, rot.per=0.35, colors=brewer.pal(8, "Dark2"), scale=c(4,0.5))

LS0tCnRpdGxlOiAiVGV4dCBNaW5pbmciCmF1dGhvcjogIkRhdmlkIERvbWluZ3VleiAtIEEwMTU3MDk3NSIKZGF0ZTogIjIwMjQtMDItMjYiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCiAgICB0aGVtZTogY29zbW8KLS0tCgojIFRleHQgTWluaW5nCgojIyBUZW9yw61hCkxhICoqbWluZXLDrWEgZGUgdGV4dG8qKiBlcyBlbCBwcm9jZXNvIGRlIGV4dHJhZXIgaW5mb3JtYWNpw7NuIHV0aWwsIHBhdHJvbmVzIG8gY29ub2NpbWllbnRvcyBkZSB0ZXh0byBubyBlc3RydWN0dXJhZG9zLgoKQ29uc3RhIGRlIDMgZXRhcGFzOgoxLiBPYnRlbmVyIGRhdG9zOiBFbCByZWNvbm9jaW1pZW50byDDs3B0aWNvIGRlIGNhcmFjdGVyZXMgKE9DUikgZXMgdW5hIHRlY25vbG9nw61hIHF1ZSBwZXJtaXRlIGNvbnZlcnRpciBpbcOhZ2VuZXMgZGUgdGV4dG8gZW4gdGV4dG8gZWRpdGFibGUuIFRhbWJpw6luIGVzIGNvbm9jaWRvIGNvbW8gKipleHRyYWNjacOzbiBkZSB0ZXh0byBkZSBpbcOhZ2VuZXMqKi4KMi4gRXhwbG9yYXIgZGF0b3M6IFJlcHJlc2VudGFjacOzbiBncsOhZmljYSBvIHZpc3VhbCBkZSBsb3MgZGF0b3MgcGFyYSBzdSBpbnRlcnByZXRhY2nDs24uIExvcyBtZXRvZG9zIG3DoXMgY29tdW5lcyBzb24gZWwgQW5hbGlzaXMgZGUgU2VudGltaWVudG9zLCBsYSBOdWJlIGRlIFBhbGFicmFzIHkgZWwgVG9waWMgTW9kZWxpbmcuCjMuIEFuw6FsaXNpcyBwcmVkaWN0aXZvOiBTb24gbGFzIHRlY25pY2FzIHkgbW9kZWxvcyBlc3RhZMOtc3RpY29zIHBhcmEgcHJlZGVjaXIgcmVzdWx0YWRvcyBmdXR1cm9zLiBMb3MgbW9kZWxvcyBtw6FzIHVzYWRvcyBzb24gUmFuZG9tIEZvcmVzdCwgUmVkZXMgTmV1cm9uYWxlcyB5IFJlZ3Jlc3Npb25lcy4gCgojIyBJbnN0YWxhciBwYXF1ZXRlcyB5IGxsYW1hciBMaWJyZXLDrWFzCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRlc3NlcmFjdCkKbGlicmFyeShtYWdpY2spCmxpYnJhcnkob2ZmaWNlcikKbGlicmFyeShwZGZ0b29scykKbGlicmFyeShwdXJycikKbGlicmFyeSh0bSkKbGlicmFyeSh3b3JkY2xvdWQpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KHRvcGljbW9kZWxzKQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKIyMgT2J0ZW5lciBEYXRvcyBtZWRpYW50ZSBPQ1IKCmBgYHtyfQppbWFnZW4xIDwtIGltYWdlX3JlYWQoIi9Vc2Vycy9kYXZpZGRydW1zMTgwL1RlYy9pbWFnZW4xLlBORyIpCnRleHRvMSA8LSBvY3IoaW1hZ2VuMSkKdGV4dG8xCmBgYAoKYGBge3J9CmRvYzEgPC0gcmVhZF9kb2N4KCkKZG9jMSA8LSBkb2MxICU+JQogIGJvZHlfYWRkX3Bhcih0ZXh0bzEsIHN0eWxlID0gIk5vcm1hbCIpCgpwcmludChkb2MxLCB0YXJnZXQgPSAiL1VzZXJzL2RhdmlkZHJ1bXMxODAvVGVjL3RleHRvMS5kb2N4IikKYGBgCgoKIyMjIEltYWdlbiBlbiBlc3Bhw7FvbCBQTkcgYSB0ZXh0byBlbiBXT1JECltDb25zdWx0YXIgaWRpb21hcyBkaXNwb25pYmxlc10oaHR0cHM6Ly90ZXNzZXJhY3Qtb2NyLmdpdGh1Yi5pby90ZXNzZG9jL0RhdGEtRmlsZXMtaW4tZGlmZmVyZW50LXZlcnNpb25zLmh0bWwpCmBgYHtyfQppbWFnZW4yIDwtIGltYWdlX3JlYWQoIi9Vc2Vycy9kYXZpZGRydW1zMTgwL1RlYy9pbWFnZW4yLlBORyIpCnRlc3NlcmFjdF9kb3dubG9hZCgic3BhIikKdGV4dG8yIDwtIG9jcihpbWFnZW4yLCBlbmdpbmUgPSB0ZXNzZXJhY3QoInNwYSIpKQp0ZXh0bzIKYGBgCgpgYGB7cn0KZG9jMiA8LSByZWFkX2RvY3goKQpkb2MyIDwtIGRvYzIgJT4lCiAgYm9keV9hZGRfcGFyKHRleHRvMiwgc3R5bGUgPSAiTm9ybWFsIikKCiMgcHJpbnQoZG9jMiwgdGFyZ2V0ID0gIi9Vc2Vycy9kYXZpZGRydW1zMTgwL1RlYy90ZXh0bzIuZG9jeCIpCmBgYAoKCiMjIERlIFBERiBhIFRleHRvIGVuIFdvcmQKYGBge3J9CiMgcGRmMSA8LSBwZGZfY29udmVydCgiL1VzZXJzL2RhdmlkZHJ1bXMxODAvVGVjL3BkZjEucGRmIiwgZHBpID0gNjAwKSAlPiUKICAjIG1hcChvY3IpCmBgYAoKIyMgQWN0aXZpZGFkIDEuIE5vdmVsYSAiSVQiCmBgYHtyfQpwZGZfcGF0aCA8LSAiL1VzZXJzL2RhdmlkZHJ1bXMxODAvVGVjL2VzbzMucGRmIgppdCA8LSBwZGZfY29udmVydChwZGZfcGF0aCwgZHBpID0gNjAwKQpgYGAKCmBgYHtyfQojIENvbmZpZ3VyYWNpw7NuIGRlbCBlbmdpbmUgZGUgVGVzc2VyYWN0IHBhcmEgT0NSCmVuZ2luZSA8LSB0ZXNzZXJhY3QoInNwYSIpICMgQWp1c3RhIGFsIGlkaW9tYSBkZSB0dSBkb2N1bWVudG8sICJzcGEiIHBhcmEgZXNwYcOxb2wgcG9yIGVqZW1wbG8KCiMgQ29udmVydGlyIGVsIFBERiBhIGltw6FnZW5lcyB5IGFwbGljYXIgT0NSIGVuIGNhZGEgdW5hCnBkZl9wYXRoIDwtICIvVXNlcnMvZGF2aWRkcnVtczE4MC9UZWMvZXNvMy5wZGYiCmltYWdlbmVzIDwtIHBkZl9jb252ZXJ0KHBkZl9wYXRoLCBkcGkgPSA2MDApCgojIENyZWFyIHVuIG51ZXZvIGRvY3VtZW50byBkZSBXb3JkCmRvYyA8LSByZWFkX2RvY3goKQoKIyBBcGxpY2FyIE9DUiBhIGNhZGEgaW1hZ2VuIHkgYcOxYWRpciBlbCB0ZXh0byBhbCBkb2N1bWVudG8gZGUgV29yZApmb3IoaW1hZ2VuX3BhdGggaW4gaW1hZ2VuZXMpIHsKICB0ZXh0byA8LSBvY3IoaW1hZ2VfcmVhZChpbWFnZW5fcGF0aCksIGVuZ2luZSA9IGVuZ2luZSkKICBkb2MgPC0gZG9jICU+JSAKICAgIGJvZHlfYWRkX3Bhcih0ZXh0bywgc3R5bGUgPSAiTm9ybWFsIikgJT4lCiAgICBib2R5X2FkZF9wYXIoIiIsIHN0eWxlID0gIk5vcm1hbCIpICMgQcOxYWRlIHVuIHDDoXJyYWZvIGVuIGJsYW5jbyBwYXJhIHNlcGFyYXIgbGFzIHDDoWdpbmFzCn0KCiMgR3VhcmRhciBlbCBkb2N1bWVudG8gZGUgV29yZAojIHByaW50KGRvYywgdGFyZ2V0ID0gIi9Vc2Vycy9kYXZpZGRydW1zMTgwL1RlYy9pdC5kb2N4IikKYGBgCgoKIyMgTGVlciBlIGluc3BlY2Npb25hciBlbiBsw61uZWEKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KdGV4dCA8LSByZWFkTGluZXMoImh0dHA6Ly93d3cuc3RoZGEuY29tL3N0aGRhL1JEb2MvZXhhbXBsZS1maWxlcy9tYXJ0aW4tbHV0aGVyLWtpbmctaS1oYXZlLWEtZHJlYW0tc3BlZWNoLnR4dCIpCgpjb3JwdXMgPC0gQ29ycHVzKFZlY3RvclNvdXJjZSh0ZXh0KSkgIyBQb25lIGNhZGEgcmVuZ2zDs24gZW4gdW5hIGNlbGRhIGRlIHZlY3RvcgoKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZU51bWJlcnMpCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbiIpKQoKIyBpbnNwZWN0KGNvcnB1cykKCiMgY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBjKCJkcmVhbSIsIndpbGwiKSkKCnRkbSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzKQptIDwtIGFzLm1hdHJpeCh0ZG0pCgpmcmVjdWVuY2lhIDwtIHNvcnQocm93U3VtcyhtKSwgZGVjcmVhc2luZyA9IFRSVUUpCmZyZWN1ZW5jaWFfZGYgPC0gZGF0YS5mcmFtZSh3b3JkPW5hbWVzKGZyZWN1ZW5jaWEpLCBmcmVxID0gZnJlY3VlbmNpYSkKCmdncGxvdChoZWFkKGZyZWN1ZW5jaWFfZGYsIDEwKSwgYWVzKHggPSByZW9yZGVyKHdvcmQsIC1mcmVxKSwgeSA9IGZyZXEsIGZpbGwgPSBmcmVxKSkgKyAKICBnZW9tX2NvbCgpICsgICMgRGlidWphIGxhcyBiYXJyYXMgcGVybWl0aWVuZG8gZ3JhZGllbnRlIGRlIGNvbG9yCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGZyZXEpLCB2anVzdCA9IC0wLjMpICsgICMgQcOxYWRlIG7Dum1lcm9zIGVuY2ltYSBkZSBsYXMgYmFycmFzCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAibGlnaHRibHVlIiwgaGlnaCA9ICJibHVlIiwgbGltaXRzID0gYygwLCAyMCkpICsgICMgR3JhZGllbnRlIGRlIGF6dWwgY29uIGVzY2FsYSBkZSAwIGEgMjAKICBsYWJzKHRpdGxlID0gIlRPUCAxMCBQYWxhYnJhcyBNw6FzIEZyZWN1ZW50ZXMiLCAKICAgICAgIHN1YnRpdGxlID0gIkRpc2N1cnNvIE1hcnRpbiBMdXRoZXIgS2luZyIsIAogICAgICAgeCA9ICJQYWxhYnJhIiwgCiAgICAgICB5ID0gIkZyZWN1ZW5jaWEiKSArCiAgdGhlbWVfbWluaW1hbCgpIApgYGAKCmBgYHtyfQojIEVsIHByb2Nlc2FtaWVudG8gZGUgZGF0b3MgYW50ZXMgZGUgbGEgbnViZSBkZSBwYWxhYnJhcyBlcyBpZ3VhbCBxdWUgZW4gZWwgYW5hbGlzaXMgZGUgZnJlY3VlbmNpYXMsIGRlc2RlIGltcG9ydGFyIGVsIHRleHRvIGhhc3RhIGxhIGZyZWN1ZW5jaWEgZW4gZWwgZGYKc2V0LnNlZWQoMTIzKQp3b3JkY2xvdWQod29yZHMgPSBmcmVjdWVuY2lhX2RmJHdvcmQsCiAgICAgICAgICBmcmVxID0gZnJlY3VlbmNpYV9kZiRmcmVxLAogICAgICAgICAgbWluLmZyZXEgPSAyLAogICAgICAgICAgcmFuZG9tLm9yZGVyID0gRkFMU0UsCiAgICAgICAgICByb3QucGVyID0gMC4zNSwgIyBQZXJtaXRlIHVuYSByb3RhY2nDs24gZGVsIDM1JSBkZSBsYXMgcGFsYWJyYXMKICAgICAgICAgIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIkRhcmsyIiksICMgVXNhIHVuYSBwYWxldGEgZGUgY29sb3JlcyAnRGFyazInIGNvbiA4IGNvbG9yZXMKICAgICAgICAgIHNjYWxlID0gYyg0LDAuNSksICMgQWp1c3RhIGVsIHRhbWHDsW8gZGUgbGFzIHBhbGFicmFzIG3DoXMgeSBtZW5vcyBmcmVjdWVudGVzCiAgICAgICAgICBtYXgud29yZHMgPSAxMDApICMgTGltaXRhIGVsIG7Dum1lcm8gZGUgcGFsYWJyYXMgYSAxMDAKYGBgCgojIyBBY3RpdmlkYWQgMgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIENhcmdhciBsYXMgbGlicmVyw61hcyBuZWNlc2FyaWFzCmxpYnJhcnkodG0pCmxpYnJhcnkod29yZGNsb3VkKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeShvZmZpY2VyKQoKIyBMZWVyIGVsIGRvY3VtZW50byAuZG9jeApkb2MgPC0gcmVhZF9kb2N4KCIvVXNlcnMvZGF2aWRkcnVtczE4MC9UZWMvaXQuZG9jeCIpCgojIEV4dHJhZXIgZWwgdGV4dG8gZGVsIGRvY3VtZW50bwp0ZXh0byA8LSBkb2N4X3N1bW1hcnkoZG9jKQp0ZXh0b19jb21wbGV0byA8LSBwYXN0ZSh0ZXh0byR0ZXh0LCBjb2xsYXBzZT0iICIpCgojIENyZWFyIHVuIGNvcnB1cyBkZWwgdGV4dG8KY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGV4dG9fY29tcGxldG8pKQoKIyBQcmVwcm9jZXNhciBlbCB0ZXh0bwpjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkgIyBDb252ZXJ0aXIgYSBtaW7DunNjdWxhcwpjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pICMgUXVpdGFyIHB1bnR1YWNpw7NuCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVOdW1iZXJzKSAjIFF1aXRhciBuw7ptZXJvcwpjb3JwdXMgPC0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlV29yZHMsIHN0b3B3b3Jkcygic3BhbmlzaCIpKSAjIFF1aXRhciBzdG9wd29yZHMgZW4gZXNwYcOxb2wKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHN0cmlwV2hpdGVzcGFjZSkgIyBRdWl0YXIgZXNwYWNpb3MgZW4gYmxhbmNvIGFkaWNpb25hbGVzCgojIENyZWFyIHVuYSB0YWJsYSBkZSBmcmVjdWVuY2lhcyBkZSBsYXMgcGFsYWJyYXMKZHRtIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChjb3JwdXMpCm1hdHJpeiA8LSBhcy5tYXRyaXgoZHRtKQpmcmVjdWVuY2lhIDwtIHNvcnQocm93U3VtcyhtYXRyaXopLCBkZWNyZWFzaW5nPVRSVUUpCmZyZWN1ZW5jaWFfZGYgPC0gZGF0YS5mcmFtZSh3b3JkID0gbmFtZXMoZnJlY3VlbmNpYSksIGZyZXEgPSBmcmVjdWVuY2lhKQoKZ2dwbG90KGhlYWQoZnJlY3VlbmNpYV9kZiwgMTApLCBhZXMoeCA9IHJlb3JkZXIod29yZCwgLWZyZXEpLCB5ID0gZnJlcSwgZmlsbCA9IGZyZXEpKSArIAogIGdlb21fY29sKCkgKyAgIyBEaWJ1amEgbGFzIGJhcnJhcyBwZXJtaXRpZW5kbyBncmFkaWVudGUgZGUgY29sb3IKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gZnJlcSksIHZqdXN0ID0gLTAuMykgKyAgIyBBw7FhZGUgbsO6bWVyb3MgZW5jaW1hIGRlIGxhcyBiYXJyYXMKICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJsaWdodGJsdWUiLCBoaWdoID0gImJsdWUiLCBsaW1pdHMgPSBjKDAsIDMwKSkgKyAgIyBHcmFkaWVudGUgZGUgYXp1bCBjb24gZXNjYWxhIGRlIDAgYSAyMAogIGxhYnModGl0bGUgPSAiVE9QIDEwIFBhbGFicmFzIE3DoXMgRnJlY3VlbnRlcyIsIAogICAgICAgc3VidGl0bGUgPSAiSVQ6IDMgQ2Fww610dWxvcyIsIAogICAgICAgeCA9ICJQYWxhYnJhIiwgCiAgICAgICB5ID0gIkZyZWN1ZW5jaWEiKSArCiAgdGhlbWVfbWluaW1hbCgpIAoKIyBHZW5lcmFyIGxhIG51YmUgZGUgcGFsYWJyYXMKc2V0LnNlZWQoMTIzKQp3b3JkY2xvdWQod29yZHMgPSBmcmVjdWVuY2lhX2RmJHdvcmQsIGZyZXEgPSBmcmVjdWVuY2lhX2RmJGZyZXEsIG1pbi5mcmVxID0gMiwKICAgICAgICAgIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjM1LCBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSwgc2NhbGU9Yyg0LDAuNSkpCmBgYAoK