Datos No Estructurados

Pensado para ir siguiendo estos videos

No todo viene siempre en un CSV

En esta parte de la materia vamos a explorar otras fuentes de datos que que no son el tradicional archivo CSV. En rigor, se usa el término no estructurado para referirse a cualquier fuente de datos que no se encuentre en formato de tabla. Como suele pasar con categorias que se define por la negativa contiene cosas de las más variadas:

  • Una colección de archivos de texto.
  • Código HTML o XML
  • Objetos anidados (formato JSON)
  • Mediciones de sensores
  • Datos geoespaciales
  • Imágenes, video, audio…

Por supesto que esta lista es enorme y escapa a los contenidos de la materia verlos todos. Es más, muchos de esos tipos de datos definen campos en sí mismos con técnicas específicas y propias de cada tipo. Por ejemplo las imágenes y videos son objeto de estudio del campo de la visión computacional. Vamos a empezar nuestro recorrido alejandonos un poco del formato super estructurado del CSV, pero no nos vamos a alejar tanto. Si permitimos formas de vinculo un poco más flexibles que la estructura tabular nos encontramos con formatos como HTML, XML y JSON. A veces a este tipo de datos se los llama semi-estructurados.

Datos semi-estructurados: XML, HTML, JSON

Este tipo de formatos suelen ser el resultado de peticiones http hechas a través de internet. No es la idea de este curso estudiar el protocolo http y todas sus posibilidades, basta con saber que a partir de una URL (Uniform Resource Locator) podemos acceder a ciertos recursos específicos. Por ejemplo, si hacemos un pedido a la URL https://lcd.exactas.uba.ar/materias/ recibimos como respuesta una serie de caracteres que conforman un archivo HTML. Por supuesto que para mostrar una página web vamos a necesitar un montón de archivos adicionales que iran llegando mediante otros pedidos http dependiendo de cual sea el código. Luego de ese proceso iterativo términamos con algo como esto:

Screenshot de la página de exactas

Nosotros no vamos a hacer uso de ese proceso tan complejo lo único que nos importa es que dada una URL podemos recibir una serie de caracteres como respuesta.

Resumido asi no más: URL —> caracteres (texto)

Para poder hacer estas peticiones http vamos a necesitar algunas funciones que no son parte de rbase asi que vamos a ir introduciendo algunos paquetes a medida que lo necesitemos. En primera instancia vamos a usar el paquete httr. Si no lo tienen, pueden instalarlo usando install.packages('httr').

require(httr) # Activamos el paquete
Loading required package: httr

No se preocupen mucho por entender este paquete en sí porque lo vamos a usar solo para entender la mecánica básica de los pedidos HTTP pero después vamos a usar paquetes más específicos que nos van a simplificar la vida. Veamos que pasa si hacemos un pedido al sitio de exactas usando la función GET:

res = GET("https://lcd.exactas.uba.ar/materias/") # hacemos el pedido http 
summary(res) # Vemos un resumen de la salida del llamado anterior
            Length Class       Mode       
url              1 -none-      character  
status_code      1 -none-      numeric    
headers          9 insensitive list       
all_headers      1 -none-      list       
cookies          7 data.frame  list       
content     111776 -none-      raw        
date             1 POSIXct     numeric    
times            6 -none-      numeric    
request          7 request     list       
handle           1 curl_handle externalptr

Vemos que nos devuelve un objeto con muchas propiedades pero lo que nos va a interesar es esencialmente el contenido de la respuesta que esta en res$content

res$content[1:1000]
   [1] 3c 21 44 4f 43 54 59 50 45 20 68 74 6d 6c 3e 0a 3c 68 74 6d 6c 20 63 6c 61 73 73 3d 22 6e 6f 2d 6f 76 65 72 66 6c 6f 77 2d 79 20 61 76 61 64 61 2d 68 74
  [52] 6d 6c 2d 6c 61 79 6f 75 74 2d 77 69 64 65 20 61 76 61 64 61 2d 68 74 6d 6c 2d 68 65 61 64 65 72 2d 70 6f 73 69 74 69 6f 6e 2d 74 6f 70 20 61 76 61 64 61
 [103] 2d 69 73 2d 31 30 30 2d 70 65 72 63 65 6e 74 2d 74 65 6d 70 6c 61 74 65 20 61 76 61 64 61 2d 68 65 61 64 65 72 2d 63 6f 6c 6f 72 2d 6e 6f 74 2d 6f 70 61
 [154] 71 75 65 22 20 6c 61 6e 67 3d 22 65 73 2d 41 52 22 20 70 72 65 66 69 78 3d 22 6f 67 3a 20 68 74 74 70 3a 2f 2f 6f 67 70 2e 6d 65 2f 6e 73 23 20 66 62 3a
 [205] 20 68 74 74 70 3a 2f 2f 6f 67 70 2e 6d 65 2f 6e 73 2f 66 62 23 22 3e 0a 3c 68 65 61 64 3e 0a 09 3c 6d 65 74 61 20 68 74 74 70 2d 65 71 75 69 76 3d 22 58
 [256] 2d 55 41 2d 43 6f 6d 70 61 74 69 62 6c 65 22 20 63 6f 6e 74 65 6e 74 3d 22 49 45 3d 65 64 67 65 22 20 2f 3e 0a 09 3c 6d 65 74 61 20 68 74 74 70 2d 65 71
 [307] 75 69 76 3d 22 43 6f 6e 74 65 6e 74 2d 54 79 70 65 22 20 63 6f 6e 74 65 6e 74 3d 22 74 65 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 3d 75 74 66 2d
 [358] 38 22 2f 3e 0a 09 3c 6d 65 74 61 20 6e 61 6d 65 3d 22 76 69 65 77 70 6f 72 74 22 20 63 6f 6e 74 65 6e 74 3d 22 77 69 64 74 68 3d 64 65 76 69 63 65 2d 77
 [409] 69 64 74 68 2c 20 69 6e 69 74 69 61 6c 2d 73 63 61 6c 65 3d 31 22 20 2f 3e 0a 09 3c 74 69 74 6c 65 3e 50 6c 61 6e 20 64 65 20 65 73 74 75 64 69 6f 73 20
 [460] 26 23 38 32 31 31 3b 20 4c 69 63 65 6e 63 69 61 74 75 72 61 20 65 6e 20 44 61 74 6f 73 20 26 23 38 32 31 31 3b 20 45 78 61 63 74 61 73 20 26 23 38 32 31
 [511] 31 3b 20 55 42 41 3c 2f 74 69 74 6c 65 3e 0a 09 09 3c 73 74 79 6c 65 3e 0d 0a 09 09 23 77 70 61 64 6d 69 6e 62 61 72 20 23 77 70 2d 61 64 6d 69 6e 2d 62
 [562] 61 72 2d 63 70 5f 70 6c 75 67 69 6e 73 5f 74 6f 70 5f 62 75 74 74 6f 6e 20 2e 61 62 2d 69 63 6f 6e 3a 62 65 66 6f 72 65 20 7b 0d 0a 09 09 09 63 6f 6e 74
 [613] 65 6e 74 3a 20 22 5c 66 35 33 33 22 3b 0d 0a 09 09 09 74 6f 70 3a 20 33 70 78 3b 0d 0a 09 09 7d 0d 0a 09 09 23 77 70 61 64 6d 69 6e 62 61 72 20 23 77 70
 [664] 2d 61 64 6d 69 6e 2d 62 61 72 2d 63 70 5f 70 6c 75 67 69 6e 73 5f 74 6f 70 5f 62 75 74 74 6f 6e 20 2e 61 62 2d 69 63 6f 6e 20 7b 0d 0a 09 09 09 74 72 61
 [715] 6e 73 66 6f 72 6d 3a 20 72 6f 74 61 74 65 28 34 35 64 65 67 29 3b 0d 0a 09 09 7d 0d 0a 09 09 3c 2f 73 74 79 6c 65 3e 0d 0a 09 3c 73 74 79 6c 65 3e 0a 23
 [766] 77 70 61 64 6d 69 6e 62 61 72 20 23 77 70 2d 61 64 6d 69 6e 2d 62 61 72 2d 76 74 72 74 73 5f 66 72 65 65 5f 74 6f 70 5f 62 75 74 74 6f 6e 20 2e 61 62 2d
 [817] 69 63 6f 6e 3a 62 65 66 6f 72 65 20 7b 0a 09 63 6f 6e 74 65 6e 74 3a 20 22 5c 66 31 38 35 22 3b 0a 09 63 6f 6c 6f 72 3a 20 23 31 44 41 45 32 32 3b 0a 09
 [868] 74 6f 70 3a 20 33 70 78 3b 0a 7d 0a 3c 2f 73 74 79 6c 65 3e 3c 6c 69 6e 6b 20 72 65 6c 3d 27 64 6e 73 2d 70 72 65 66 65 74 63 68 27 20 68 72 65 66 3d 27
 [919] 2f 2f 73 2e 77 2e 6f 72 67 27 20 2f 3e 0a 3c 6c 69 6e 6b 20 72 65 6c 3d 22 61 6c 74 65 72 6e 61 74 65 22 20 74 79 70 65 3d 22 61 70 70 6c 69 63 61 74 69
 [970] 6f 6e 2f 72 73 73 2b 78 6d 6c 22 20 74 69 74 6c 65 3d 22 4c 69 63 65 6e 63 69 61 74 75 72 61

Oops! Son los bytes pelados! Para convertir esos bytes pelados en caracteres podemos usar rawToChar

rawToChar(res$content[1:1000])
[1] "<!DOCTYPE html>\n<html class=\"no-overflow-y avada-html-layout-wide avada-html-header-position-top avada-is-100-percent-template avada-header-color-not-opaque\" lang=\"es-AR\" prefix=\"og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#\">\n<head>\n\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n\t<title>Plan de estudios &#8211; Licenciatura en Datos &#8211; Exactas &#8211; UBA</title>\n\t\t<style>\r\n\t\t#wpadminbar #wp-admin-bar-cp_plugins_top_button .ab-icon:before {\r\n\t\t\tcontent: \"\\f533\";\r\n\t\t\ttop: 3px;\r\n\t\t}\r\n\t\t#wpadminbar #wp-admin-bar-cp_plugins_top_button .ab-icon {\r\n\t\t\ttransform: rotate(45deg);\r\n\t\t}\r\n\t\t</style>\r\n\t<style>\n#wpadminbar #wp-admin-bar-vtrts_free_top_button .ab-icon:before {\n\tcontent: \"\\f185\";\n\tcolor: #1DAE22;\n\ttop: 3px;\n}\n</style><link rel='dns-prefetch' href='//s.w.org' />\n<link rel=\"alternate\" type=\"application/rss+xml\" title=\"Licenciatura"

Un poco más claro pero no mucho. Para pode extraer información de un archivo HTML vamos a tener que “parsearlo” (anglicismo del verbo to parse), es decir identificar sus componentes y separarlo en partes. Por suerte las tareas de parseo mas comunes ya están preprogramadas y hay paquetes que nos van a simplificar un poco la vida. Acá es donde entra rvest (juego de palabras entre R y harvest, que quiere decir recolectar o cosechar en inglés). Recuerden instalarlo si no lo tienen con install.packages("rvest") y acceder a la documentación con: ?rvest

require(rvest) # Cargamos el paquete
Loading required package: rvest

y vamos a usarlo para extaer información de la pagina de materias de la carrera que vimos recien. Para eso usamos la función read_html que nos permite leer directamente una URL

pag_materias = read_html("https://lcd.exactas.uba.ar/materias/")

y ahora vamos a “parsear” los datos que estan en la tabla de correlatividades de esa pagina Correlatividades de la carrera Para eso, tenemos que seleccionar el elemento HTML que contiene la tabla. Acá es donde el “parseo” se convierte un poco en un arte, podemos usar la consola de nuestro navegador para estudiar el código html y ver que el elemento que contiene lo que nos interesa pertenece a una clase que se llama table. (Aclaración sobre cómo se hace esto en el video)

pag_materias
{html_document}
<html class="no-overflow-y avada-html-layout-wide avada-html-header-position-top avada-is-100-percent-template avada-header-color-not-opaque" lang="es-AR" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
[1] <head>\n<meta http-equiv="X-UA-Compatible" content="IE=edge">\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n<meta name="viewport" ...
[2] <body class="page-template page-template-100-width page-template-100-width-php page page-id-43 fusion-image-hovers fusion-pagination-sizing fusion-button ...

Podemos elegir los elementos que pertenecen a la clase table de la siguiente manera:

elemento_tabla  = html_element(pag_materias, '.table')
toString(elemento_tabla) # Podemos chusmear el pedacito de codigo HTML que seleccionamos
[1] "<table class=\"table\"><tbody>\n<tr>\n<td width=\"73\"><strong>Cuatrimestre</strong></td>\n<td width=\"194\"><strong>Asignatura</strong></td>\n<td width=\"131\"><strong>Correlatividad de Asignaturas</strong></td>\n</tr>\n<tr>\n<td width=\"73\">3</td>\n<td width=\"194\">Análisis I</td>\n<td width=\"131\">CBC</td>\n</tr>\n<tr>\n<td width=\"73\">3</td>\n<td width=\"194\">Álgebra I</td>\n<td width=\"131\">CBC</td>\n</tr>\n<tr>\n<td width=\"73\">4</td>\n<td width=\"194\">Algoritmos y Estructuras de Datos I</td>\n<td width=\"131\">Álgebra I</td>\n</tr>\n<tr>\n<td width=\"73\">4</td>\n<td width=\"194\">Electiva de Introducción a las Ciencias Naturales</td>\n<td width=\"131\">CBC</td>\n</tr>\n<tr>\n<td width=\"73\">5</td>\n<td width=\"194\">Análisis II</td>\n<td width=\"131\">Análisis I</td>\n</tr>\n<tr>\n<td width=\"73\">5</td>\n<td width=\"194\">Algoritmos y Estructuras de Datos II</td>\n<td width=\"131\">Algoritmos y Estructuras de Datos I</td>\n</tr>\n<tr>\n<td width=\"73\">6</td>\n<td width=\"194\">Laboratorio de Datos</td>\n<td width=\"131\">Algoritmos y Estructuras de Datos I</td>\n</tr>\n<tr>\n<td width=\"73\">6</td>\n<td width=\"194\">Análisis Avanzado</td>\n<td width=\"131\">Análisis II, Álgebra I</td>\n</tr>\n<tr>\n<td width=\"73\">6</td>\n<td width=\"194\">Álgebra Lineal Computacional</td>\n<td width=\"131\">Álgebra I</td>\n</tr>\n<tr>\n<td width=\"73\">7</td>\n<td width=\"194\">Probabilidad</td>\n<td width=\"131\">Análisis Avanzado</td>\n</tr>\n<tr>\n<td width=\"73\">7</td>\n<td width=\"194\">Algoritmos y Estructura de Datos III</td>\n<td width=\"131\">Algoritmos y Estructuras de Datos II</td>\n</tr>\n<tr>\n<td width=\"73\">8</td>\n<td width=\"194\">Intr. a la Estadística y Ciencia de Datos</td>\n<td width=\"131\">Alg y Estruct de Datos II, Probabilidad, Álgebra Lineal Computacional</td>\n</tr>\n<tr>\n<td width=\"73\">8</td>\n<td width=\"194\">Intr. a la Investigación Operativa y Optimización</td>\n<td width=\"131\">Alg y Estruc de Datos III, Análisis II, Álgebra Lineal Computacional</td>\n</tr>\n<tr>\n<td width=\"73\">8</td>\n<td width=\"194\">Intr. al Modelado Continuo.</td>\n<td width=\"131\">Análisis Avanzado, Álgebra Lineal Computacional, Alg y Estructura de Datos II</td>\n</tr>\n</tbody></table>"

y luego convertir el código html que corresponde a ese elemento en una tabla de R

correlativas = html_table(elemento_tabla)
correlativas

Fijense que en este caso el parseo automatico de tables que hizo la funcion html_table no capturo correctamente la primera fila como titulos o headers. Para corregir esto podemos hacer:

nombres = correlativas[1,]
colnames(correlativas) = nombres
correlativas = correlativas[-1,] # Esto quiere decir todas las filas EXCEPTO la primera
correlativas

Esto es algo que, en general, va a haber que hacer: limpiar, emprolijar, uniformizar campos, trabajar con datos incompletos. En este caso fue bastante fácil porque el código html escondía un formato tabular pero podría ser mucho más complejo. Imagínense, por ejemplo, obtener todas las características de los productos listados como ‘guitarra’ en un comercio virtual, cuyos resultados pueden estar separados en varias paginas y contener información muy distinta entre guitarra y guitarra, puede haber errores de etiquetado, etc.

Buenas practias de scrapeo. No todo lo que se puede se debe.

Al scrapear estamos haciendo uso de recursos que generan o almacenan terceras personas y que pueden no querer compartirlo. Es por eso que es importante leer bien los términos y condiciones de uso del sitio del cual queremos extraer información. Por ejemplo Amazon prohibe este tipo de prácticas expresamente:

En los sitios suele haber un archivo en la URL base /robots.txt (por ejemplo https://exactas.uba.ar/robots.txt) donde se explicita que familias de URLs se pueden scrapear y cuáles no. Hay un paquete de R para chequear esto automáticamente.

Espacio para practicar - Ejercicio

Extraigan la tabla de velocidad de distintos animales de https://es.wikipedia.org/wiki/Velocidad_de_los_animales.

  pag_wiki = read_html("https://es.wikipedia.org/wiki/Velocidad_de_los_animales")
  elem_tabla = html_element(pag_wiki, ".wikitable")
  velocidades = html_table(elem_tabla)
  velocidades

Van a notar que la columna interesante es muy sucia porque tiene rangos de velocidades (hay que tomar una decision y reemplazar por promedio, maxima o minima) y ademas tiene llamadas al pie de citas que dificultan la conversion a numeros. Para poder eliminar esta información vamos a ver en que tags html se encuentran almacenadas y los vamos a borrar del codigo de la página antes de extraer la tabla.

Vemos que las etiquetas que nos molestan son <span> y <sup>. Vamos a eliminarlas, para eso vamos a usar de la libreria xml2.

require(xml2) # REcuerden instalarla con install.packages('xml2') si no la tienen
Loading required package: xml2
sups = html_elements(pag_wiki, xpath='//table//sup') # elegimos todos los elementos sup que esten dentro de un elemento table
spans = html_elements(pag_wiki, xpath='//table//span') # elegimos todos los elementos span que esten dentro de un elemento table
xml_remove(spans) # Los eliminamos
xml_remove(sups)

Ahora si vuelvan a intentar parsear la tabla

  velocidades = html_table(elem_tabla)
  velocidades
  
  #
  # (completar)
  #

Aún así necesitamos procesar un poco más la columna de velocidad. Tenemos que poder converirlas a un número. Para eso pueden resultarles útiles las siguientes lineas:

  • vel_numero = strsplit(vel_str, ‘-|−’) # Divide al string donde haya - o −
  • vel_numero = gsub(‘[^0-9,]’,’’, vel_numero) # Reemplaza por nada todo lo que NO sea numero o ‘,’
  • vel_numero = gsub(‘,’,‘.’, vel_numero) # Reemplaza coma por punto para poder convertir con as.double()
for (vel_str in ________){  ## Completar
 ## Completar
}

Extra desafío

Esto es opcional no lo vamos a resolver en clase. En la tabla de velocidades de animales cada animal tiene un link a su página de wikipedia. El desafio es recorrer esos links y extaer de cada animal su clase Ej:

Y explorar si existe alguna relación entre las clases de animal y sus respectivas velocidades.

Ejercicio

Carguen la tabla de superficies de paises de https://www.worldometers.info/geography/largest-countries-in-the-world/

pag_paises = # Completar
superficies['area_km'] = # Completar
  1. Conviertan las columnas que consideren apropiadas en datos numericos
  2. ¿Cuál es el pais de mayor superficie?
  3. ¿Cual es el pais con mayor superficie maritima?
  4. ¿Cuál tiene la mayor relación sup. agua / sup. tierra?
  5. Cuenten cuántos paises tiene áreas que empiezan con el número 1, cuántas con 2,3,4,5,6,7,8,9. Pista: usen la columna original en formato char y la funcion substr
  6. Hagan un histograma ¿Hay algún patrón? Pista: https://es.wikipedia.org/wiki/Ley_de_Benford (para una versión más amena vean éste capítulo de serie https://www.imdb.com/title/tt12816822/ )

Acceso programatico a datos (APIs)

Muchos sitios proveen acceso a su información o sus servicios de manera programática. Es decir que tienen URLs que en vez de estar pensadas para ser visitadas por un navegador están preparadas para integrarse como parte de otros procesos. Esto nos va simplificar la vida porque no vamos a tener que estar parseando código HTML que en realidad está pensado para ser renderizado y mostrado en un navegador.

¿Qué es una api?

La sigla API corresponde a Application Programming Interface (interfaz de programación de una apliacación). Es decir que una API son las reglas para interactuar con un dado software. Podemos pensarlo como las especificaciones de una enchufe, con todos los requisitos que tenemos que cumplir para “engancharnos” a un sistema y usarlo. Es una especificación de los métodos, el tipo de datos que ingresan y el tipo de datos que regresan de un dado sistema. Si bien la idea de API es aplicable a cualquier software, se suele usar especialmente en el contexto de software que se expone através de internet. Algunas APIs estan pensadas para ser de uso público, otras son internas; algunos se pueden acceder sin ninguna forma de autorización, otras requieren metodos de autenticación; algunas son gratis otras se ofrecen como servicios pagos.

¿Cómo se accede a datos mediante una api?

Empecemos con una API muy sencilla, como puede ser la que expone el ministerio de cultura en esta URL: https://www.cultura.gob.ar/api/v2.0/ Si ingresan en esta URL podemos ver que la API tiene los siguientes endpoints (un endpoint es una suburl dentro de la api para algún fin específico):

Veamos que encontramos en el endpoint museos:

res_museos = GET("https://www.cultura.gob.ar/api/v2.0/museos/")
rawToChar(res_museos$content[1:2000])
[1] "{\"count\":27,\"next\":\"https://www.cultura.gob.ar/api/v2.0/museos/?limit=20&offset=20\",\"previous\":null,\"results\":[{\"id\":27,\"url\":\"https://www.cultura.gob.ar/api/v2.0/organismos/27/\",\"link\":\"https://www.cultura.gob.ar/institucional/organismos/museos/comision-nacional-de-la-manzana-de-las-luces/\",\"nombre\":\"Complejo Histórico Cultural Manzana de las Luces\",\"direccion\":\"Perú 294, Ciudad de Buenos Aires\",\"telefono\":\"+54 (011) 4342-9930 / 6973\",\"descripcion\":\"<p>El Complejo Hist&oacute;rico Cultural Manzana de las Luces depende del Ministerio de Cultura de la Naci&oacute;n.</p>\\r\\n<p>Fue creada en 1971 por el Decreto n&ordm; 4657/71 y ampliadas sus funciones por los decretos: 1185/73; 1454/74 y 1479/81. El decreto 108/2013 cambia su nombre de Comisi&oacute;n Nacional a Complejo Hist&oacute;rico Cultural Manzana de las Luces.</p>\\r\\n<p>Son sus objetivos la restauraci&oacute;n y conservaci&oacute;n de los edificios hist&oacute;ricos; la investigaci&oacute;n con relaci&oacute;n a instituciones, acontecimientos y personajes que desfilaron por la Manzana de las Luces; y la refuncionalizaci&oacute;n de los edificios a trav&eacute;s de la actividad cultural.</p>\",\"email\":\"cnml@manzanadelasluces.gov.ar\",\"provincia\":\"\",\"depende_de\":\"Ministerio de Cultura de la Nación\",\"autoridad\":null},{\"id\":28,\"url\":\"https://www.cultura.gob.ar/api/v2.0/organismos/28/\",\"link\":\"https://www.cultura.gob.ar/institucional/organismos/museos/estancia-de-jesus-maria-museo-jesuitico-nacional/\",\"nombre\":\"Estancia de Jesús María - Museo Jesuítico Nacional\",\"direccion\":\"Pedro de Oñate s/n, Jesús María, Córdoba\",\"telefono\":\"+54 (03525) 420126\",\"descripcion\":\"<p><strong>La estancia</strong></p>\\r\\n<p>La Compa&ntilde;&iacute;a de Jes&uacute;s llega a la provincia de C&oacute;rdoba, en la actual Argentina en 1599. En 1608 crean el Noviciado y dos a&ntilde;os despu&eacute;s se declara al Colegio de C&oacute;rdoba como Colegio M&aacute;ximo. Debido a algunos problemas econ&oacute;micos que se presentan, comi"

Si lo formateamos un poco mas prolijo vemos una parte de la respuesta que llegó:

Vemos que no es un HTML. Es otro formato de datos y es EL formato mas estandar en uso en la interacción con APIs. Es un formato que se llama JSON.

El formato JSON

El formato JSON (JavaScript Object Notation) es el caballito de batalla de los datos semi-estructurados asi como el CSV es el caballito de batalla de los datos estructurados. El formato CSV es una especificación sobre como almacenar datos tabulares en un archivo de texto (y define qué caracteres se pueden usar y en qué orden - aca la definicion formal técnica del standard en ingles) y el formato JSON también define cómo codificar en texto cierto tipo de objetos anidados (aca la definicion formal técnica del standard en ingles ). NO ES IMPORTANTE que lean los estándares, quedan solo como referencia por si a algune le interesan.

Al igual que pasa con el HTML para poder usar la información de un JSON vamos a tener que parsearlo. Por suerte como es un formato super estándar ya hay paquetes preparados para hacerlo. En este caso, el paquete que vamos a usar se llama jsonlite. Recuerden instalarlo si no lo tienen con install.packages("jsonlite")

require(jsonlite)  # lo cargamos
Loading required package: jsonlite
museos = fromJSON("https://www.cultura.gob.ar/api/v2.0/museos/") # Llamamos al endpoint ahora ya "parseando" el JSON
summary(museos)
         Length Class      Mode     
count     1     -none-     numeric  
next      1     -none-     character
previous  0     -none-     NULL     
results  11     data.frame list     
museos$results

Vemos que el texto se convirtio en un objeto de R con los mismos campos que podiamos leer en el formato de llaves y comillas

Ejemplos

Vamos a ver algunos ejemplos de otras APIs para poner estas ideas en práctica. En cada ejemplo se plantean una serie de ejercicios

API de datos de recetas de tragos

Como primer ejemplo de una api sencilla vamos a ver esta https://www.thecocktaildb.com/api.php. Ahi podemos ver sus endpoints. Y vamos a ver algo interesante que no habiamos visto en la API de cultura que es que los endpoints son métodos que pueden recibir parámetros. Por ejemplo vemos que el endpoint www.thecocktaildb.com/api/json/v1/1/filter.php?i=Gin nos permite hacer una búsqueda por ingrediente (el ingrediente Gin es el valor asignado al parámetro i). Veamos:

tragos = fromJSON("http://www.thecocktaildb.com/api/json/v1/1/filter.php?i=Gin")
tragos
$drinks
NA

Y si ahora queremos tragos que lleven naranja simplemente reemplazamos el ingrediente:

tragos = fromJSON("http://www.thecocktaildb.com/api/json/v1/1/filter.php?i=Orange")
tragos
$drinks
NA

Si queremos saber que ingredientes hay disponibles podemos usar el endpoint http://www.thecocktaildb.com/api/json/v1/1/list.php?i=list

ingredientes = fromJSON("http://www.thecocktaildb.com/api/json/v1/1/list.php?i=list")
ingredientes
$drinks
NA

Ejercicios

  1. Elijan 10 tragos al azar (busquen en la documentacion hay un endpoint para traer de 1 trago al azar -el de 10 es pago-) y calculen la cantidad de ingredientes promedio por trago. Recuerden ese número, lo vamos a compartir al final de la clase.
  2. Elijan un ingrediente del dataframe de ingredientes y busquen tragos con ese ingredientes: ¿Cuántos son?
  3. ¿Cuál es la distribución de tipos de vaso en los tragos que usan Gin?
  4. ¿Se parece a la de tragos que usan Champagne?
  5. Tomen las instrucciones para hacer el trago de 10 tragos distintos en los distintos idiomas y cuenten la frecuencia de cada letra (Pista: table(strsplit(texto_instrucciones, ’’)))
  6. Hagan uno o más graficos de barras ¿Se parecen las distribuciones?
  7. ¿Cuál es la letra que mejor distingue el alemán del italiano?

API de datos del Banco mundial

En esta parte de la clase vamos a trabajar con otra api que tiene datos económicos y sociales de distintos paises. Es la api del banco mundial. Es una API bastante compleja, acá pueden encontrar la documentación para usarla: https://datahelpdesk.worldbank.org/knowledgebase/topics/125589-developer-information

Dada la cantidad ENORME de indicadores que tiene el banco mundial, vamos a usar este sitio que nos permite explorarlos visualmente y luego elegir con cuales vamos a querer trabajar https://datos.bancomundial.org/ Ahi pueden buscar por ejemplo PBI (hay varias variantes puede ser por ejemplo -PIB per cápita, PPA ($ a precios internacionales actuales)- ). Si entran van a ver que aparece un grafico para el mundo y hay un boton de detalles. Si lo clickean van a poder encontrar un id para el indicador. En este caso NY.GDP.PCAP.PP.CD. Ese es el id que vamos necesitar para hacer la consulta:

data = fromJSON("https://api.worldbank.org/v2/es/country/all/indicator/NY.GDP.PCAP.PP.CD?format=json")
data
[[1]]
[[1]]$page
[1] 1

[[1]]$pages
[1] 325

[[1]]$per_page
[1] 50

[[1]]$total
[1] 16226

[[1]]$sourceid
[1] "2"

[[1]]$sourcename
[1] "World Development Indicators"

[[1]]$lastupdated
[1] "2021-07-30"


[[2]]
NA

Vemos que la respuesta incluye dos partes, unos metadatos que nos indican información general sobre el recurso que acabamos de pedir. Por ejemplo que la cantidad total de registros del dataset es 16226 y que se actualizaron por última vez el 30 de Julio de 2021. También que solo recibimos en este pedido 50 datos. En este caso los datos están paginados en 325 páginas de 50 elementos cada una. Hay un parámetro de la api que nos permite traer todo de una:

si agregamos &per_page=20000 cambiamos el maximo por página y traemos todos los datos en una sola consulta

data = fromJSON("https://api.worldbank.org/v2/es/country/all/indicator/NY.GDP.PCAP.PP.CD?format=json&per_page=20000")
pbi = data[[2]]

y podemos graficar el pbi para Argentina y Brasil en función del tiempo

valor_ar = pbi[pbi$countryiso3code == 'ARG', 'value']
anio_ar = pbi[pbi$countryiso3code == 'ARG', 'date']
plot(anio_ar, valor_ar, type='l', col='red', xlab = 'Año', ylab='PBI Per. Cap. PPP')

valor_br = pbi[pbi$countryiso3code == 'BRA', 'value']
anio_br = pbi[pbi$countryiso3code == 'BRA', 'date']
lines(anio_br, valor_br, col='blue')
legend(1960, 15000, legend=c("Argentina", "Brasil"),
       col=c("red", "blue"), lty=c(1, 1), cex=0.8)

Ejercicios

Tengan presente la documentacion para hacer los ejercicios https://datahelpdesk.worldbank.org/knowledgebase/articles/898581-api-basic-call-structures

  1. Elijan otro indicador usando el explorador del banco mundial y traigan los datos via API
  2. Prueben traer datos solo para un par de paises y dentro de un rango particular de años (consulten la documentacion para ver que parte de la URL modificar)
  3. Hagan un grafico de barras con los 10 paises con mayor esperanza de vida (considerando solo paises de al menos 10M de habitantes) en los 80’ y hoy. ¿Cambio el top 10?
  4. Dentro de los paises de mas de 10M de habitantes ¿Cuáles aumentaron más su esperanza de vida en los últimos 40 años?

APIs grandes suelen tener paquetes

Las APIs grandes o muy usadas suelen tener paquetes instalables para los lenguajes más populares de manera tal que no hace falta estar generando a mano las URLs y consultas HTTP. Muchas veces esos paquetes son construidos por la propia comunidad de cada lenguaje y no necesariamente por quién creo la API. Ese es el caso de la api del banco mundial que tiene disponible el paquete WDI para R. Para instalarlo install.packages('WDI') . Pueden ver la documentación en https://github.com/vincentarelbundock/WDI

  1. Prueben usar directamente el paquete en lugar de usar la URL para repetir alguno de los puntos del ejercicio anterior

API de datos georreferenciados de argentina.gob.ar

Finalmente vamos a tabajar con una API un poco distinta para entender que no solo sirven para consultar datos sino que podemos usarlas como servicios e incluirlas como parte de nuestras rutinas de análisis. Como ejemplo vamos a usar esta API de georreferenciación nacional https://datosgobar.github.io/georef-ar-api/

Esta api tiene distintos endpoints para cada caso de uso, por ejemplo podemos saber a que muncipio, departamento y provincia pertenecen ciertas coordenadas:

fromJSON("https://apis.datos.gob.ar/georef/api/ubicacion?lat=-27.2741&lon=-66.7529")
$parametros
$parametros$lat
[1] -27.2741

$parametros$lon
[1] -66.7529


$ubicacion
$ubicacion$departamento
$ubicacion$departamento$id
[1] "10035"

$ubicacion$departamento$nombre
[1] "Belén"


$ubicacion$lat
[1] -27.2741

$ubicacion$lon
[1] -66.7529

$ubicacion$municipio
$ubicacion$municipio$id
[1] "100077"

$ubicacion$municipio$nombre
[1] "Hualfín"


$ubicacion$provincia
$ubicacion$provincia$id
[1] "10"

$ubicacion$provincia$nombre
[1] "Catamarca"

O cual es la ubicación de una dada dirección. Por ejemplo, ¿Dónde queda Avenida Rivadavia 3000?

res = fromJSON("https://apis.datos.gob.ar/georef/api/direcciones?direccion=av.%20rivadavia%203000")
res$direcciones$nomenclatura
[1] "AV RIVADAVIA 3000, Comuna 3, Ciudad Autónoma de Buenos Aires" "AV RIVADAVIA 3000, Capitán Sarmiento, Buenos Aires"          
[3] "AV RIVADAVIA 3000, Ituzaingó, Buenos Aires"                   "AV RIVADAVIA 3000, Saladillo, Buenos Aires"                  
[5] "AV RIVADAVIA 3000, Saladillo, Buenos Aires"                   "AV RIVADAVIA 3000, Saladillo, Buenos Aires"                  
[7] "AV RIVADAVIA 3000, Saladillo, Buenos Aires"                   "AV RIVADAVIA 3000, Saladillo, Buenos Aires"                  

Vemos que hay varias opciones compatibles con esa dirección y recibimos mucha información adicional sobre esas direcciones, por ejemplo sus coordenadas:

res$direcciones$ubicacion

Si agregamos algo de informacion extra, como la provincia o distrito sobre la que estamos haciendo la busqueda (por ejemplo CABA) podemos conseguir una respuesta única

x = fromJSON("https://apis.datos.gob.ar/georef/api/direcciones?direccion=av.%20rivadavia%203000&provincia=caba")
x$direcciones$nomenclatura
[1] "AV RIVADAVIA 3000, Comuna 3, Ciudad Autónoma de Buenos Aires"

Habrán notado que estamos introduciendo las direcciones de una manera un poco extraña en la url, esto es porque las URLs no pueden contener cieros caracteres (como espacios) y entonces hay que convertirlos a otras secuencias de caracteres permitidos. Por suerte hay una función que ya lo hace que podemos usar:

URLencode("Av. de mayo 1658")
[1] "Av.%20de%20mayo%201658"
x = fromJSON(URLencode("https://apis.datos.gob.ar/georef/api/direcciones?direccion=av. del libertador 1400&provincia=caba"))
print(x$direcciones$nomenclatura)
[1] "AV DEL LIBERTADOR 1400, Comuna 2, Ciudad Autónoma de Buenos Aires"
print(x$direcciones$ubicacion)

Ejercicios

Escriban un pequeño script que para cada museo de la API del ministerio de cultura obtenga sus coordenadas usando la API de georreferenciación. Partan el problema en pasos:

  1. Consigan la dirección de un museo
  2. Extraigan las coordenadas con la api de georref para ese museo
  3. Pongan esos dos códigos juntos y fíjense que ande
  4. Construyan un for loop que recorra todos los museos y ejecute el código que armaron en el punto anterior
  5. Elijan un trago con o sin alcohol de la API de tragos y brinden por su ejercicio

Session Info

Referencia de la sesion con la que fue corrido este notebook

sessionInfo()
R version 4.1.1 (2021-08-10)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 20.04.3 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/atlas/libblas.so.3.10.3
LAPACK: /usr/lib/x86_64-linux-gnu/atlas/liblapack.so.3.10.3

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8   
 [6] LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] jsonlite_1.7.2 xml2_1.3.2     rvest_1.0.1    httr_1.4.2    

loaded via a namespace (and not attached):
 [1] rstudioapi_0.13   knitr_1.33        magrittr_2.0.1    R6_2.5.0          rlang_0.4.11      fansi_0.5.0       stringr_1.4.0     tools_4.1.1      
 [9] xfun_0.24         utf8_1.2.2        cli_3.0.1         selectr_0.4-2     htmltools_0.5.1.1 ellipsis_0.3.2    yaml_2.2.1        digest_0.6.27    
[17] tibble_3.1.3      lifecycle_1.0.0   crayon_1.4.1      vctrs_0.3.8       rsconnect_0.8.24  curl_4.3.2        glue_1.4.2        evaluate_0.14    
[25] rmarkdown_2.9     stringi_1.7.3     compiler_4.1.1    pillar_1.6.2      pkgconfig_2.0.3  
LS0tCnRpdGxlOiAiT3RyYXMgbWFuZXJhcyBkZSBjb25zdW1pciBkYXRvcyIKYXV0aG9yOiAiTWFydGluIEVsaWFzIENvc3RhIgpkYXRlOiAiMjAgZGUgSnVsaW8gZGUgMjAyMSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBsdW1lbgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCnN1YnRpdGxlOiAiTGFib3JhdG9yaW8gZGUgRGF0b3MiCi0tLQoKIyBEYXRvcyBObyBFc3RydWN0dXJhZG9zCgpQZW5zYWRvIHBhcmEgaXIgc2lndWllbmRvIFtlc3RvcyB2aWRlb3NdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3BsYXlsaXN0P2xpc3Q9UExtS2FmejhsSl9BWE03dXhoYXNMUjY2NVpxV2xwQzZSRCkKCiMjIE5vIHRvZG8gdmllbmUgc2llbXByZSBlbiB1biBDU1YKCkVuIGVzdGEgcGFydGUgZGUgbGEgbWF0ZXJpYSB2YW1vcyBhIGV4cGxvcmFyIG90cmFzIGZ1ZW50ZXMgZGUgZGF0b3MgcXVlIHF1ZSBubyBzb24gZWwgdHJhZGljaW9uYWwgYXJjaGl2byBDU1YuIEVuIHJpZ29yLCBzZSB1c2EgZWwgdMOpcm1pbm8gbm8gZXN0cnVjdHVyYWRvIHBhcmEgcmVmZXJpcnNlIGEgY3VhbHF1aWVyIGZ1ZW50ZSBkZSBkYXRvcyBxdWUgbm8gc2UgZW5jdWVudHJlIGVuIGZvcm1hdG8gZGUgdGFibGEuIENvbW8gc3VlbGUgcGFzYXIgY29uIGNhdGVnb3JpYXMgcXVlIHNlIGRlZmluZSBwb3IgbGEgbmVnYXRpdmEgY29udGllbmUgY29zYXMgZGUgbGFzIG3DoXMgdmFyaWFkYXM6CgotICAgVW5hIGNvbGVjY2nDs24gZGUgYXJjaGl2b3MgZGUgdGV4dG8uCi0gICBDw7NkaWdvIEhUTUwgbyBYTUwKLSAgIE9iamV0b3MgYW5pZGFkb3MgKGZvcm1hdG8gSlNPTikKLSAgIE1lZGljaW9uZXMgZGUgc2Vuc29yZXMKLSAgIERhdG9zIGdlb2VzcGFjaWFsZXMKLSAgIEltw6FnZW5lcywgdmlkZW8sIGF1ZGlvLi4uCgpQb3Igc3VwZXN0byBxdWUgZXN0YSBsaXN0YSBlcyBlbm9ybWUgeSBlc2NhcGEgYSBsb3MgY29udGVuaWRvcyBkZSBsYSBtYXRlcmlhIHZlcmxvcyB0b2Rvcy4gRXMgbcOhcywgbXVjaG9zIGRlIGVzb3MgdGlwb3MgZGUgZGF0b3MgZGVmaW5lbiBjYW1wb3MgZW4gc8OtIG1pc21vcyBjb24gdMOpY25pY2FzIGVzcGVjw61maWNhcyB5IHByb3BpYXMgZGUgY2FkYSB0aXBvLiBQb3IgZWplbXBsbyBsYXMgaW3DoWdlbmVzIHkgdmlkZW9zIHNvbiBvYmpldG8gZGUgZXN0dWRpbyBkZWwgY2FtcG8gZGUgbGEgKnZpc2nDs24gY29tcHV0YWNpb25hbCouIFZhbW9zIGEgZW1wZXphciBudWVzdHJvIHJlY29ycmlkbyBhbGVqYW5kb25vcyB1biBwb2NvIGRlbCBmb3JtYXRvIHN1cGVyIGVzdHJ1Y3R1cmFkbyBkZWwgQ1NWLCBwZXJvIG5vIG5vcyB2YW1vcyBhIGFsZWphciB0YW50by4gU2kgcGVybWl0aW1vcyBmb3JtYXMgZGUgdmluY3VsbyB1biBwb2NvIG3DoXMgZmxleGlibGVzIHF1ZSBsYSBlc3RydWN0dXJhIHRhYnVsYXIgbm9zIGVuY29udHJhbW9zIGNvbiBmb3JtYXRvcyBjb21vIEhUTUwsIFhNTCB5IEpTT04uIEEgdmVjZXMgYSBlc3RlIHRpcG8gZGUgZGF0b3Mgc2UgbG9zIGxsYW1hIHNlbWktZXN0cnVjdHVyYWRvcy4KCiMjIERhdG9zIHNlbWktZXN0cnVjdHVyYWRvczogWE1MLCBIVE1MLCBKU09OCgpFc3RlIHRpcG8gZGUgZm9ybWF0b3Mgc3VlbGVuIHNlciBlbCByZXN1bHRhZG8gZGUgcGV0aWNpb25lcyAqaHR0cCogaGVjaGFzIGEgdHJhdsOpcyBkZSBpbnRlcm5ldC4gTm8gZXMgbGEgaWRlYSBkZSBlc3RlIGN1cnNvIGVzdHVkaWFyIGVsIHByb3RvY29sbyBodHRwIHkgdG9kYXMgc3VzIHBvc2liaWxpZGFkZXMsIGJhc3RhIGNvbiBzYWJlciBxdWUgYSBwYXJ0aXIgZGUgdW5hIFVSTCAoVW5pZm9ybSBSZXNvdXJjZSBMb2NhdG9yKSBwb2RlbW9zIGFjY2VkZXIgYSBjaWVydG9zIHJlY3Vyc29zIGVzcGVjw61maWNvcy4gUG9yIGVqZW1wbG8sIHNpIGhhY2Vtb3MgdW4gcGVkaWRvIGEgbGEgVVJMIGBodHRwczovL2xjZC5leGFjdGFzLnViYS5hci9tYXRlcmlhcy9gIHJlY2liaW1vcyBjb21vIHJlc3B1ZXN0YSB1bmEgc2VyaWUgZGUgY2FyYWN0ZXJlcyBxdWUgY29uZm9ybWFuIHVuIGFyY2hpdm8gSFRNTC4gUG9yIHN1cHVlc3RvIHF1ZSBwYXJhIG1vc3RyYXIgdW5hIHDDoWdpbmEgd2ViIHZhbW9zIGEgbmVjZXNpdGFyIHVuIG1vbnTDs24gZGUgYXJjaGl2b3MgYWRpY2lvbmFsZXMgcXVlIGlyYW4gbGxlZ2FuZG8gbWVkaWFudGUgKipvdHJvcyoqIHBlZGlkb3MgaHR0cCBkZXBlbmRpZW5kbyBkZSBjdWFsIHNlYSBlbCBjw7NkaWdvLiBMdWVnbyBkZSBlc2UgcHJvY2VzbyBpdGVyYXRpdm8gdMOpcm1pbmFtb3MgY29uIGFsZ28gY29tbyBlc3RvOgoKIVtTY3JlZW5zaG90IGRlIGxhIHDDoWdpbmEgZGUgZXhhY3Rhc10oLi9sY2QucG5nKQoKTm9zb3Ryb3Mgbm8gdmFtb3MgYSBoYWNlciB1c28gZGUgZXNlIHByb2Nlc28gdGFuIGNvbXBsZWpvIGxvIMO6bmljbyBxdWUgbm9zIGltcG9ydGEgZXMgcXVlIGRhZGEgdW5hICpVUkwqIHBvZGVtb3MgcmVjaWJpciB1bmEgc2VyaWUgZGUgY2FyYWN0ZXJlcyBjb21vIHJlc3B1ZXN0YS4KClJlc3VtaWRvIGFzaSBubyBtw6FzOiAqVVJMKiAtLS1cPiAqY2FyYWN0ZXJlcyAodGV4dG8pKgoKUGFyYSBwb2RlciBoYWNlciBlc3RhcyBwZXRpY2lvbmVzIGh0dHAgdmFtb3MgYSBuZWNlc2l0YXIgYWxndW5hcyBmdW5jaW9uZXMgcXVlIG5vIHNvbiBwYXJ0ZSBkZSByYmFzZSBhc2kgcXVlIHZhbW9zIGEgaXIgaW50cm9kdWNpZW5kbyBhbGd1bm9zIHBhcXVldGVzIGEgbWVkaWRhIHF1ZSBsbyBuZWNlc2l0ZW1vcy4gRW4gcHJpbWVyYSBpbnN0YW5jaWEgdmFtb3MgYSB1c2FyIGVsIHBhcXVldGUgKmh0dHIqLiBTaSBubyBsbyB0aWVuZW4sIHB1ZWRlbiBpbnN0YWxhcmxvIHVzYW5kbyBgaW5zdGFsbC5wYWNrYWdlcygnaHR0cicpYC4KCmBgYHtyfQpyZXF1aXJlKGh0dHIpICMgQWN0aXZhbW9zIGVsIHBhcXVldGUKYGBgCgpObyBzZSBwcmVvY3VwZW4gbXVjaG8gcG9yIGVudGVuZGVyIGVzdGUgcGFxdWV0ZSBlbiBzw60gcG9ycXVlIGxvIHZhbW9zIGEgdXNhciBzb2xvIHBhcmEgZW50ZW5kZXIgbGEgbWVjw6FuaWNhIGLDoXNpY2EgZGUgbG9zIHBlZGlkb3MgSFRUUCBwZXJvIGRlc3B1w6lzIHZhbW9zIGEgdXNhciBwYXF1ZXRlcyBtw6FzIGVzcGVjw61maWNvcyBxdWUgbm9zIHZhbiBhIHNpbXBsaWZpY2FyIGxhIHZpZGEuIFZlYW1vcyBxdWUgcGFzYSBzaSBoYWNlbW9zIHVuIHBlZGlkbyBhbCBzaXRpbyBkZSBleGFjdGFzIHVzYW5kbyBsYSBmdW5jacOzbiBgR0VUYDoKCmBgYHtyfQpyZXMgPSBHRVQoImh0dHBzOi8vbGNkLmV4YWN0YXMudWJhLmFyL21hdGVyaWFzLyIpICMgaGFjZW1vcyBlbCBwZWRpZG8gaHR0cCAKc3VtbWFyeShyZXMpICMgVmVtb3MgdW4gcmVzdW1lbiBkZSBsYSBzYWxpZGEgZGVsIGxsYW1hZG8gYW50ZXJpb3IKYGBgCgpWZW1vcyBxdWUgbm9zIGRldnVlbHZlIHVuIG9iamV0byBjb24gbXVjaGFzIHByb3BpZWRhZGVzIHBlcm8gbG8gcXVlIG5vcyB2YSBhIGludGVyZXNhciBlcyBlc2VuY2lhbG1lbnRlIGVsIGNvbnRlbmlkbyBkZSBsYSByZXNwdWVzdGEgcXVlIGVzdGEgZW4gYHJlcyRjb250ZW50YAoKYGBge3J9CnJlcyRjb250ZW50WzE6MTAwMF0KYGBgCgpPb3BzISBTb24gbG9zIGJ5dGVzIHBlbGFkb3MhIFBhcmEgY29udmVydGlyIGVzb3MgYnl0ZXMgcGVsYWRvcyBlbiBjYXJhY3RlcmVzIHBvZGVtb3MgdXNhciBgcmF3VG9DaGFyYAoKYGBge3J9CnJhd1RvQ2hhcihyZXMkY29udGVudFsxOjEwMDBdKQpgYGAKClVuIHBvY28gbcOhcyBjbGFybyBwZXJvIG5vIG11Y2hvLiBQYXJhIHBvZGUgZXh0cmFlciBpbmZvcm1hY2nDs24gZGUgdW4gYXJjaGl2byBIVE1MIHZhbW9zIGEgdGVuZXIgcXVlICJwYXJzZWFybG8iIChhbmdsaWNpc21vIGRlbCB2ZXJibyAqdG8gcGFyc2UqKSwgZXMgZGVjaXIgaWRlbnRpZmljYXIgc3VzIGNvbXBvbmVudGVzIHkgc2VwYXJhcmxvIGVuIHBhcnRlcy4gUG9yIHN1ZXJ0ZSBsYXMgdGFyZWFzIGRlIHBhcnNlbyBtYXMgY29tdW5lcyB5YSBlc3TDoW4gcHJlcHJvZ3JhbWFkYXMgeSBoYXkgcGFxdWV0ZXMgcXVlIG5vcyB2YW4gYSBzaW1wbGlmaWNhciB1biBwb2NvIGxhIHZpZGEuIEFjw6EgZXMgZG9uZGUgZW50cmEgcnZlc3QgKGp1ZWdvIGRlIHBhbGFicmFzIGVudHJlIFIgeSAqaGFydmVzdCosIHF1ZSBxdWllcmUgZGVjaXIgcmVjb2xlY3RhciBvIGNvc2VjaGFyIGVuIGluZ2zDqXMpLiBSZWN1ZXJkZW4gaW5zdGFsYXJsbyBzaSBubyBsbyB0aWVuZW4gY29uIGBpbnN0YWxsLnBhY2thZ2VzKCJydmVzdCIpYCB5IGFjY2VkZXIgYSBsYSBkb2N1bWVudGFjacOzbiBjb246IGA/cnZlc3RgCgpgYGB7cn0KcmVxdWlyZShydmVzdCkgIyBDYXJnYW1vcyBlbCBwYXF1ZXRlCmBgYAoKeSB2YW1vcyBhIHVzYXJsbyBwYXJhIGV4dGFlciBpbmZvcm1hY2nDs24gZGUgbGEgcGFnaW5hIGRlIG1hdGVyaWFzIGRlIGxhIGNhcnJlcmEgcXVlIHZpbW9zIHJlY2llbi4gUGFyYSBlc28gdXNhbW9zIGxhIGZ1bmNpw7NuIGByZWFkX2h0bWxgIHF1ZSBub3MgcGVybWl0ZSBsZWVyIGRpcmVjdGFtZW50ZSB1bmEgVVJMCgpgYGB7cn0KcGFnX21hdGVyaWFzID0gcmVhZF9odG1sKCJodHRwczovL2xjZC5leGFjdGFzLnViYS5hci9tYXRlcmlhcy8iKQpgYGAKCnkgYWhvcmEgdmFtb3MgYSAicGFyc2VhciIgbG9zIGRhdG9zIHF1ZSBlc3RhbiBlbiBsYSB0YWJsYSBkZSBjb3JyZWxhdGl2aWRhZGVzIGRlIGVzYSBwYWdpbmEgIVtDb3JyZWxhdGl2aWRhZGVzIGRlIGxhIGNhcnJlcmFdKC4vY3Jvbm9ncmFtYS5wbmcpIFBhcmEgZXNvLCB0ZW5lbW9zIHF1ZSBzZWxlY2Npb25hciBlbCBlbGVtZW50byBIVE1MIHF1ZSBjb250aWVuZSBsYSB0YWJsYS4gQWPDoSBlcyBkb25kZSBlbCAicGFyc2VvIiBzZSBjb252aWVydGUgdW4gcG9jbyBlbiB1biBhcnRlLCBwb2RlbW9zIHVzYXIgbGEgY29uc29sYSBkZSBudWVzdHJvIG5hdmVnYWRvciBwYXJhIGVzdHVkaWFyIGVsIGPDs2RpZ28gaHRtbCB5IHZlciBxdWUgZWwgZWxlbWVudG8gcXVlIGNvbnRpZW5lIGxvIHF1ZSBub3MgaW50ZXJlc2EgcGVydGVuZWNlIGEgdW5hIGNsYXNlIHF1ZSBzZSBsbGFtYSAqdGFibGUqLiAoQWNsYXJhY2nDs24gc29icmUgY8OzbW8gc2UgaGFjZSBlc3RvIGVuIGVsIHZpZGVvKQoKIVtdKC4vY29uc29sYS5wbmcpCmBgYHtyfQpwYWdfbWF0ZXJpYXMKYGBgCgpQb2RlbW9zIGVsZWdpciBsb3MgZWxlbWVudG9zIHF1ZSBwZXJ0ZW5lY2VuIGEgbGEgY2xhc2UgKnRhYmxlKiBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOgoKYGBge3J9CmVsZW1lbnRvX3RhYmxhICA9IGh0bWxfZWxlbWVudChwYWdfbWF0ZXJpYXMsICcudGFibGUnKQp0b1N0cmluZyhlbGVtZW50b190YWJsYSkgIyBQb2RlbW9zIGNodXNtZWFyIGVsIHBlZGFjaXRvIGRlIGNvZGlnbyBIVE1MIHF1ZSBzZWxlY2Npb25hbW9zCmBgYAoKeSBsdWVnbyBjb252ZXJ0aXIgZWwgY8OzZGlnbyBodG1sIHF1ZSBjb3JyZXNwb25kZSBhIGVzZSBlbGVtZW50byBlbiB1bmEgdGFibGEgZGUgUgoKYGBge3J9CmNvcnJlbGF0aXZhcyA9IGh0bWxfdGFibGUoZWxlbWVudG9fdGFibGEpCmNvcnJlbGF0aXZhcwpgYGAKCkZpamVuc2UgcXVlIGVuIGVzdGUgY2FzbyBlbCBwYXJzZW8gYXV0b21hdGljbyBkZSB0YWJsZXMgcXVlIGhpem8gbGEgZnVuY2lvbiBgaHRtbF90YWJsZWAgbm8gY2FwdHVybyBjb3JyZWN0YW1lbnRlIGxhIHByaW1lcmEgZmlsYSBjb21vIHRpdHVsb3MgbyAqaGVhZGVycyouIFBhcmEgY29ycmVnaXIgZXN0byBwb2RlbW9zIGhhY2VyOgoKYGBge3J9Cm5vbWJyZXMgPSBjb3JyZWxhdGl2YXNbMSxdCmNvbG5hbWVzKGNvcnJlbGF0aXZhcykgPSBub21icmVzCmNvcnJlbGF0aXZhcyA9IGNvcnJlbGF0aXZhc1stMSxdICMgRXN0byBxdWllcmUgZGVjaXIgdG9kYXMgbGFzIGZpbGFzIEVYQ0VQVE8gbGEgcHJpbWVyYQpjb3JyZWxhdGl2YXMKYGBgCgpFc3RvIGVzIGFsZ28gcXVlLCBlbiBnZW5lcmFsLCB2YSBhIGhhYmVyIHF1ZSBoYWNlcjogbGltcGlhciwgZW1wcm9saWphciwgdW5pZm9ybWl6YXIgY2FtcG9zLCB0cmFiYWphciBjb24gZGF0b3MgaW5jb21wbGV0b3MuIEVuIGVzdGUgY2FzbyBmdWUgYmFzdGFudGUgZsOhY2lsIHBvcnF1ZSBlbCBjw7NkaWdvIGh0bWwgZXNjb25kw61hIHVuIGZvcm1hdG8gdGFidWxhciBwZXJvIHBvZHLDrWEgc2VyIG11Y2hvIG3DoXMgY29tcGxlam8uIEltYWfDrW5lbnNlLCBwb3IgZWplbXBsbywgb2J0ZW5lciB0b2RhcyBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsb3MgcHJvZHVjdG9zIGxpc3RhZG9zIGNvbW8gJ2d1aXRhcnJhJyBlbiB1biBjb21lcmNpbyB2aXJ0dWFsLCBjdXlvcyByZXN1bHRhZG9zIHB1ZWRlbiBlc3RhciBzZXBhcmFkb3MgZW4gdmFyaWFzIHBhZ2luYXMgeSBjb250ZW5lciBpbmZvcm1hY2nDs24gbXV5IGRpc3RpbnRhIGVudHJlIGd1aXRhcnJhIHkgZ3VpdGFycmEsIHB1ZWRlIGhhYmVyIGVycm9yZXMgZGUgZXRpcXVldGFkbywgZXRjLgoKIyMgQnVlbmFzIHByYWN0aWFzIGRlIHNjcmFwZW8uIE5vIHRvZG8gbG8gcXVlIHNlIHB1ZWRlIHNlIGRlYmUuCgpBbCAqc2NyYXBlYXIqIGVzdGFtb3MgaGFjaWVuZG8gdXNvIGRlIHJlY3Vyc29zIHF1ZSBnZW5lcmFuIG8gYWxtYWNlbmFuIHRlcmNlcmFzIHBlcnNvbmFzIHkgcXVlIHB1ZWRlbiBubyBxdWVyZXIgY29tcGFydGlybG8uIEVzIHBvciBlc28gcXVlIGVzIGltcG9ydGFudGUgbGVlciBiaWVuIGxvcyB0w6lybWlub3MgeSBjb25kaWNpb25lcyBkZSB1c28gZGVsIHNpdGlvIGRlbCBjdWFsIHF1ZXJlbW9zIGV4dHJhZXIgaW5mb3JtYWNpw7NuLiBQb3IgZWplbXBsbyBBbWF6b24gcHJvaGliZSBlc3RlIHRpcG8gZGUgcHLDoWN0aWNhcyBleHByZXNhbWVudGU6ICFbXSguL2FtYXpvbi5wbmcpCgpFbiBsb3Mgc2l0aW9zIHN1ZWxlIGhhYmVyIHVuIGFyY2hpdm8gZW4gbGEgVVJMIGJhc2UgL3JvYm90cy50eHQgKHBvciBlamVtcGxvIDxodHRwczovL2V4YWN0YXMudWJhLmFyL3JvYm90cy50eHQ+KSBkb25kZSBzZSBleHBsaWNpdGEgcXVlIGZhbWlsaWFzIGRlIFVSTHMgc2UgcHVlZGVuICpzY3JhcGVhciogeSBjdcOhbGVzIG5vLiBIYXkgdW4gW3BhcXVldGUgZGUgUiBwYXJhIGNoZXF1ZWFyIGVzdG8gYXV0b23DoXRpY2FtZW50ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JvYm90c3R4dC9pbmRleC5odG1sKS4KCiMjIEVzcGFjaW8gcGFyYSBwcmFjdGljYXIgLSBFamVyY2ljaW8KCkV4dHJhaWdhbiBsYSB0YWJsYSBkZSB2ZWxvY2lkYWQgZGUgZGlzdGludG9zIGFuaW1hbGVzIGRlIDxodHRwczovL2VzLndpa2lwZWRpYS5vcmcvd2lraS9WZWxvY2lkYWRfZGVfbG9zX2FuaW1hbGVzPi4KCmBgYHtyIGV2YWwgPSBGQUxTRX0KICBwYWdfd2lraSA9IHJlYWRfaHRtbCgiaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvVmVsb2NpZGFkX2RlX2xvc19hbmltYWxlcyIpCiAgZWxlbV90YWJsYSA9IGh0bWxfZWxlbWVudChwYWdfd2lraSwgIi53aWtpdGFibGUiKQogIHZlbG9jaWRhZGVzID0gaHRtbF90YWJsZShlbGVtX3RhYmxhKQogIHZlbG9jaWRhZGVzCgpgYGAKClZhbiBhIG5vdGFyIHF1ZSBsYSBjb2x1bW5hIGludGVyZXNhbnRlIGVzIG11eSBzdWNpYSBwb3JxdWUgdGllbmUgcmFuZ29zIGRlIHZlbG9jaWRhZGVzIChoYXkgcXVlIHRvbWFyIHVuYSBkZWNpc2lvbiB5IHJlZW1wbGF6YXIgcG9yIHByb21lZGlvLCBtYXhpbWEgbyBtaW5pbWEpIHkgYWRlbWFzIHRpZW5lIGxsYW1hZGFzIGFsIHBpZSBkZSBjaXRhcyBxdWUgZGlmaWN1bHRhbiBsYSBjb252ZXJzaW9uIGEgbnVtZXJvcy4gUGFyYSBwb2RlciBlbGltaW5hciBlc3RhIGluZm9ybWFjacOzbiB2YW1vcyBhIHZlciBlbiBxdWUgdGFncyBodG1sIHNlIGVuY3VlbnRyYW4gYWxtYWNlbmFkYXMgeSBsb3MgdmFtb3MgYSBib3JyYXIgZGVsIGNvZGlnbyBkZSBsYSBww6FnaW5hIGFudGVzIGRlIGV4dHJhZXIgbGEgdGFibGEuCgohW10oLi9ub2Rvc19zcGFuX3N1cC5wbmcpCgpWZW1vcyBxdWUgbGFzIGV0aXF1ZXRhcyBxdWUgbm9zIG1vbGVzdGFuIHNvbiBgPHNwYW4+YCB5IGA8c3VwPmAuIFZhbW9zIGEgZWxpbWluYXJsYXMsIHBhcmEgZXNvIHZhbW9zIGEgdXNhciBkZSBsYSBsaWJyZXJpYSAqeG1sMiouCgpgYGB7cn0KcmVxdWlyZSh4bWwyKSAjIFJFY3VlcmRlbiBpbnN0YWxhcmxhIGNvbiBpbnN0YWxsLnBhY2thZ2VzKCd4bWwyJykgc2kgbm8gbGEgdGllbmVuCmBgYAoKYGBge3IgZXZhbCA9IEZBTFNFfQoKc3VwcyA9IGh0bWxfZWxlbWVudHMocGFnX3dpa2ksIHhwYXRoPScvL3RhYmxlLy9zdXAnKSAjIGVsZWdpbW9zIHRvZG9zIGxvcyBlbGVtZW50b3Mgc3VwIHF1ZSBlc3RlbiBkZW50cm8gZGUgdW4gZWxlbWVudG8gdGFibGUKc3BhbnMgPSBodG1sX2VsZW1lbnRzKHBhZ193aWtpLCB4cGF0aD0nLy90YWJsZS8vc3BhbicpICMgZWxlZ2ltb3MgdG9kb3MgbG9zIGVsZW1lbnRvcyBzcGFuIHF1ZSBlc3RlbiBkZW50cm8gZGUgdW4gZWxlbWVudG8gdGFibGUKeG1sX3JlbW92ZShzcGFucykgIyBMb3MgZWxpbWluYW1vcwp4bWxfcmVtb3ZlKHN1cHMpCmBgYAoKQWhvcmEgc2kgdnVlbHZhbiBhIGludGVudGFyIHBhcnNlYXIgbGEgdGFibGEKCmBgYHtyIGV2YWwgPSBGQUxTRX0KICB2ZWxvY2lkYWRlcyA9IGh0bWxfdGFibGUoZWxlbV90YWJsYSkKICB2ZWxvY2lkYWRlcwogIAogICMKICAjIChjb21wbGV0YXIpCiAgIwogIApgYGAKCkHDum4gYXPDrSBuZWNlc2l0YW1vcyBwcm9jZXNhciB1biBwb2NvIG3DoXMgbGEgY29sdW1uYSBkZSB2ZWxvY2lkYWQuIFRlbmVtb3MgcXVlIHBvZGVyIGNvbnZlcmlybGFzIGEgdW4gbsO6bWVyby4gUGFyYSBlc28gcHVlZGVuIHJlc3VsdGFybGVzIMO6dGlsZXMgbGFzIHNpZ3VpZW50ZXMgbGluZWFzOgoKLSB2ZWxfbnVtZXJvID0gc3Ryc3BsaXQodmVsX3N0ciwgJy184oiSJykgKiMgRGl2aWRlIGFsIHN0cmluZyBkb25kZSBoYXlhIC0gbyDiiJIgKgotIHZlbF9udW1lcm8gPSBnc3ViKCdbXjAtOSxdJywnJywgdmVsX251bWVybykgKiMgUmVlbXBsYXphIHBvciBuYWRhIHRvZG8gbG8gcXVlICoqKk5PICoqKnNlYSBudW1lcm8gbyAnLCcqCi0gdmVsX251bWVybyA9IGdzdWIoJywnLCcuJywgdmVsX251bWVybykgKiMgUmVlbXBsYXphIGNvbWEgcG9yIHB1bnRvIHBhcmEgcG9kZXIgY29udmVydGlyIGNvbiBhcy5kb3VibGUoKSoKCmBgYHtyIGV2YWwgPSBGQUxTRX0KCmZvciAodmVsX3N0ciBpbiBfX19fX19fXyl7ICAjIyBDb21wbGV0YXIKICMjIENvbXBsZXRhcgp9CgpgYGAKCiMjIyBFeHRyYSBkZXNhZsOtbwoKRXN0byBlcyBvcGNpb25hbCAqKm5vIGxvIHZhbW9zIGEgcmVzb2x2ZXIgZW4gY2xhc2UqKi4gRW4gbGEgdGFibGEgZGUgdmVsb2NpZGFkZXMgZGUgYW5pbWFsZXMgY2FkYSBhbmltYWwgdGllbmUgdW4gbGluayBhIHN1IHDDoWdpbmEgZGUgd2lraXBlZGlhLiBFbCBkZXNhZmlvIGVzIHJlY29ycmVyIGVzb3MgbGlua3MgeSBleHRhZXIgZGUgY2FkYSBhbmltYWwgc3UgY2xhc2UgRWo6CgohW10oLi9jbGFzZV9hbmltYWwucG5nKQoKClkgZXhwbG9yYXIgc2kgZXhpc3RlIGFsZ3VuYSByZWxhY2nDs24gZW50cmUgbGFzIGNsYXNlcyBkZSBhbmltYWwgeSBzdXMgcmVzcGVjdGl2YXMgdmVsb2NpZGFkZXMuCgojIyMgRWplcmNpY2lvCgpDYXJndWVuIGxhIHRhYmxhIGRlIHN1cGVyZmljaWVzIGRlIHBhaXNlcyBkZSBodHRwczovL3d3dy53b3JsZG9tZXRlcnMuaW5mby9nZW9ncmFwaHkvbGFyZ2VzdC1jb3VudHJpZXMtaW4tdGhlLXdvcmxkLyAKCmBgYHtyLCBldmFsPUZBTFNFfQpwYWdfcGFpc2VzID0gIyBDb21wbGV0YXIKYGBgCgoKYGBge3IsIGV2YWw9RkFMU0V9CnN1cGVyZmljaWVzWydhcmVhX2ttJ10gPSAjIENvbXBsZXRhcgpgYGAKCjEuIENvbnZpZXJ0YW4gbGFzIGNvbHVtbmFzIHF1ZSBjb25zaWRlcmVuIGFwcm9waWFkYXMgZW4gZGF0b3MgbnVtZXJpY29zCjEuIMK/Q3XDoWwgZXMgZWwgcGFpcyBkZSBtYXlvciBzdXBlcmZpY2llPwoxLiDCv0N1YWwgZXMgZWwgcGFpcyBjb24gbWF5b3Igc3VwZXJmaWNpZSBtYXJpdGltYT8KMS4gwr9DdcOhbCB0aWVuZSBsYSBtYXlvciByZWxhY2nDs24gc3VwLiBhZ3VhIC8gc3VwLiB0aWVycmE/CjEuIEN1ZW50ZW4gY3XDoW50b3MgcGFpc2VzIHRpZW5lIMOhcmVhcyBxdWUgZW1waWV6YW4gY29uIGVsIG7Dum1lcm8gMSwgY3XDoW50YXMgY29uIDIsMyw0LDUsNiw3LDgsOS4gUGlzdGE6IHVzZW4gbGEgY29sdW1uYSBvcmlnaW5hbCBlbiBmb3JtYXRvICpjaGFyKiB5IGxhIGZ1bmNpb24gKnN1YnN0cioKMS4gSGFnYW4gdW4gaGlzdG9ncmFtYSDCv0hheSBhbGfDum4gcGF0csOzbj8gUGlzdGE6IGh0dHBzOi8vZXMud2lraXBlZGlhLm9yZy93aWtpL0xleV9kZV9CZW5mb3JkIChwYXJhIHVuYSB2ZXJzacOzbiBtw6FzIGFtZW5hIHZlYW4gw6lzdGUgY2Fww610dWxvIGRlIHNlcmllIGh0dHBzOi8vd3d3LmltZGIuY29tL3RpdGxlL3R0MTI4MTY4MjIvICkKCiMjIEFjY2VzbyBwcm9ncmFtYXRpY28gYSBkYXRvcyAoQVBJcykKCk11Y2hvcyBzaXRpb3MgcHJvdmVlbiBhY2Nlc28gYSBzdSBpbmZvcm1hY2nDs24gbyBzdXMgc2VydmljaW9zIGRlIG1hbmVyYSBwcm9ncmFtw6F0aWNhLiBFcyBkZWNpciBxdWUgdGllbmVuIFVSTHMgcXVlIGVuIHZleiBkZSBlc3RhciBwZW5zYWRhcyBwYXJhIHNlciB2aXNpdGFkYXMgcG9yIHVuIG5hdmVnYWRvciBlc3TDoW4gcHJlcGFyYWRhcyBwYXJhIGludGVncmFyc2UgY29tbyBwYXJ0ZSBkZSBvdHJvcyBwcm9jZXNvcy4gRXN0byBub3MgdmEgc2ltcGxpZmljYXIgbGEgdmlkYSBwb3JxdWUgbm8gdmFtb3MgYSB0ZW5lciBxdWUgZXN0YXIgKnBhcnNlYW5kbyogY8OzZGlnbyBIVE1MIHF1ZSBlbiByZWFsaWRhZCBlc3TDoSBwZW5zYWRvIHBhcmEgc2VyIHJlbmRlcml6YWRvIHkgbW9zdHJhZG8gZW4gdW4gbmF2ZWdhZG9yLiAKCiMjIyDCv1F1w6kgZXMgdW5hIGFwaT8KCkxhIHNpZ2xhIEFQSSBjb3JyZXNwb25kZSBhIEFwcGxpY2F0aW9uIFByb2dyYW1taW5nIEludGVyZmFjZSAoaW50ZXJmYXogZGUgcHJvZ3JhbWFjacOzbiBkZSB1bmEgYXBsaWFjYWNpw7NuKS4gRXMgZGVjaXIgcXVlIHVuYSBBUEkgc29uIGxhcyByZWdsYXMgcGFyYSBpbnRlcmFjdHVhciBjb24gdW4gZGFkbyBzb2Z0d2FyZS4gUG9kZW1vcyBwZW5zYXJsbyBjb21vIGxhcyBlc3BlY2lmaWNhY2lvbmVzIGRlIHVuYSBlbmNodWZlLCBjb24gdG9kb3MgbG9zIHJlcXVpc2l0b3MgcXVlIHRlbmVtb3MgcXVlIGN1bXBsaXIgcGFyYSAiZW5nYW5jaGFybm9zIiBhIHVuIHNpc3RlbWEgeSB1c2FybG8uIEVzIHVuYSBlc3BlY2lmaWNhY2nDs24gZGUgbG9zIG3DqXRvZG9zLCBlbCB0aXBvIGRlIGRhdG9zIHF1ZSBpbmdyZXNhbiB5IGVsIHRpcG8gZGUgZGF0b3MgcXVlIHJlZ3Jlc2FuIGRlIHVuIGRhZG8gc2lzdGVtYS4gIVtdKC4vcGx1Z3MuanBnKSBTaSBiaWVuIGxhIGlkZWEgZGUgQVBJIGVzIGFwbGljYWJsZSBhIGN1YWxxdWllciBzb2Z0d2FyZSwgc2Ugc3VlbGUgdXNhciBlc3BlY2lhbG1lbnRlIGVuIGVsIGNvbnRleHRvIGRlIHNvZnR3YXJlIHF1ZSBzZSBleHBvbmUgYXRyYXbDqXMgZGUgaW50ZXJuZXQuIEFsZ3VuYXMgQVBJcyBlc3RhbiBwZW5zYWRhcyBwYXJhIHNlciBkZSB1c28gcMO6YmxpY28sIG90cmFzIHNvbiBpbnRlcm5hczsgYWxndW5vcyBzZSBwdWVkZW4gYWNjZWRlciBzaW4gbmluZ3VuYSBmb3JtYSBkZSBhdXRvcml6YWNpw7NuLCBvdHJhcyByZXF1aWVyZW4gbWV0b2RvcyBkZSBhdXRlbnRpY2FjacOzbjsgYWxndW5hcyBzb24gZ3JhdGlzIG90cmFzIHNlIG9mcmVjZW4gY29tbyBzZXJ2aWNpb3MgcGFnb3MuCgojIyMgwr9Dw7NtbyBzZSBhY2NlZGUgYSBkYXRvcyBtZWRpYW50ZSB1bmEgYXBpPwoKRW1wZWNlbW9zIGNvbiB1bmEgQVBJIG11eSBzZW5jaWxsYSwgY29tbyBwdWVkZSBzZXIgbGEgcXVlIGV4cG9uZSBlbCBtaW5pc3RlcmlvIGRlIGN1bHR1cmEgZW4gZXN0YSBVUkw6IDxodHRwczovL3d3dy5jdWx0dXJhLmdvYi5hci9hcGkvdjIuMC8+IFNpIGluZ3Jlc2FuIGVuIGVzdGEgVVJMIHBvZGVtb3MgdmVyIHF1ZSBsYSBBUEkgdGllbmUgbG9zIHNpZ3VpZW50ZXMgKmVuZHBvaW50cyogKHVuIGVuZHBvaW50IGVzIHVuYSBzdWJ1cmwgZGVudHJvIGRlIGxhIGFwaSBwYXJhIGFsZ8O6biBmaW4gZXNwZWPDrWZpY28pOgoKLSAgICJ1c3VhcmlvcyI6ICI8aHR0cHM6Ly93d3cuY3VsdHVyYS5nb2IuYXIvYXBpL3YyLjAvdXN1YXJpb3MvPiIKLSAgICJvcmdhbmlzbW9zIjogIjxodHRwczovL3d3dy5jdWx0dXJhLmdvYi5hci9hcGkvdjIuMC9vcmdhbmlzbW9zLz4iCi0gICAicHJvZ3JhbWFzIjogIjxodHRwczovL3d3dy5jdWx0dXJhLmdvYi5hci9hcGkvdjIuMC9wcm9ncmFtYXMvPiIKLSAgICJtdXNlb3MiOiAiPGh0dHBzOi8vd3d3LmN1bHR1cmEuZ29iLmFyL2FwaS92Mi4wL211c2Vvcy8+IgotICAgImluc3RpdHV0b3MiOiAiPGh0dHBzOi8vd3d3LmN1bHR1cmEuZ29iLmFyL2FwaS92Mi4wL2luc3RpdHV0b3MvPiIKLSAgICJ0cmFtaXRlcyI6ICI8aHR0cHM6Ly93d3cuY3VsdHVyYS5nb2IuYXIvYXBpL3YyLjAvdHJhbWl0ZXMvPiIKLSAgICJjb252b2NhdG9yaWFzIjogIjxodHRwczovL3d3dy5jdWx0dXJhLmdvYi5hci9hcGkvdjIuMC9jb252b2NhdG9yaWFzLz4iCgpWZWFtb3MgcXVlIGVuY29udHJhbW9zIGVuIGVsICplbmRwb2ludCogbXVzZW9zOgoKYGBge3J9CnJlc19tdXNlb3MgPSBHRVQoImh0dHBzOi8vd3d3LmN1bHR1cmEuZ29iLmFyL2FwaS92Mi4wL211c2Vvcy8iKQpyYXdUb0NoYXIocmVzX211c2VvcyRjb250ZW50WzE6MjAwMF0pCmBgYAoKU2kgbG8gZm9ybWF0ZWFtb3MgdW4gcG9jbyBtYXMgcHJvbGlqbyB2ZW1vcyB1bmEgcGFydGUgZGUgbGEgcmVzcHVlc3RhIHF1ZSBsbGVnw7M6ICFbXSguL2pzb24ucG5nKQoKVmVtb3MgcXVlICoqbm8gZXMgdW4gSFRNTCoqLiBFcyBvdHJvIGZvcm1hdG8gZGUgZGF0b3MgeSBlcyBFTCBmb3JtYXRvIG1hcyBlc3RhbmRhciBlbiB1c28gZW4gbGEgaW50ZXJhY2Npw7NuIGNvbiBBUElzLiBFcyB1biBmb3JtYXRvIHF1ZSBzZSBsbGFtYSBbSlNPTl0oaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvSlNPTikuCgojIyMgRWwgZm9ybWF0byBKU09OCgpFbCBmb3JtYXRvIEpTT04gKEphdmFTY3JpcHQgT2JqZWN0IE5vdGF0aW9uKSBlcyBlbCBjYWJhbGxpdG8gZGUgYmF0YWxsYSBkZSBsb3MgZGF0b3Mgc2VtaS1lc3RydWN0dXJhZG9zIGFzaSBjb21vIGVsIENTViBlcyBlbCBjYWJhbGxpdG8gZGUgYmF0YWxsYSBkZSBsb3MgZGF0b3MgZXN0cnVjdHVyYWRvcy4gRWwgZm9ybWF0byBDU1YgZXMgdW5hIGVzcGVjaWZpY2FjacOzbiBzb2JyZSBjb21vIGFsbWFjZW5hciBkYXRvcyB0YWJ1bGFyZXMgZW4gdW4gYXJjaGl2byBkZSB0ZXh0byAoeSBkZWZpbmUgcXXDqSBjYXJhY3RlcmVzIHNlIHB1ZWRlbiB1c2FyIHkgZW4gcXXDqSBvcmRlbiAtIFthY2EgbGEgZGVmaW5pY2lvbiBmb3JtYWwgdMOpY25pY2EgZGVsIHN0YW5kYXJkIGVuIGluZ2xlc10oaHR0cHM6Ly9kYXRhdHJhY2tlci5pZXRmLm9yZy9kb2MvaHRtbC9yZmM0MTgwKSkgeSBlbCBmb3JtYXRvIEpTT04gdGFtYmnDqW4gZGVmaW5lIGPDs21vIGNvZGlmaWNhciBlbiB0ZXh0byBjaWVydG8gdGlwbyBkZSBvYmpldG9zIGFuaWRhZG9zIChbYWNhIGxhIGRlZmluaWNpb24gZm9ybWFsIHTDqWNuaWNhIGRlbCBzdGFuZGFyZCBlbiBpbmdsZXNdKGh0dHBzOi8vZGF0YXRyYWNrZXIuaWV0Zi5vcmcvZG9jL2h0bWwvcmZjNzE1OSkgKS4gTk8gRVMgSU1QT1JUQU5URSBxdWUgbGVhbiBsb3MgZXN0w6FuZGFyZXMsIHF1ZWRhbiBzb2xvIGNvbW8gcmVmZXJlbmNpYSBwb3Igc2kgYSBhbGd1bmUgbGUgaW50ZXJlc2FuLgoKQWwgaWd1YWwgcXVlIHBhc2EgY29uIGVsIEhUTUwgcGFyYSBwb2RlciB1c2FyIGxhIGluZm9ybWFjacOzbiBkZSB1biBKU09OIHZhbW9zIGEgdGVuZXIgcXVlICpwYXJzZWFybG8qLiBQb3Igc3VlcnRlIGNvbW8gZXMgdW4gZm9ybWF0byBzdXBlciBlc3TDoW5kYXIgeWEgaGF5IHBhcXVldGVzIHByZXBhcmFkb3MgcGFyYSBoYWNlcmxvLiBFbiBlc3RlIGNhc28sIGVsIHBhcXVldGUgcXVlIHZhbW9zIGEgdXNhciBzZSBsbGFtYSBganNvbmxpdGVgLiBSZWN1ZXJkZW4gaW5zdGFsYXJsbyBzaSBubyBsbyB0aWVuZW4gY29uIGBpbnN0YWxsLnBhY2thZ2VzKCJqc29ubGl0ZSIpYAoKYGBge3J9CnJlcXVpcmUoanNvbmxpdGUpICAjIGxvIGNhcmdhbW9zCmBgYAoKYGBge3J9Cm11c2VvcyA9IGZyb21KU09OKCJodHRwczovL3d3dy5jdWx0dXJhLmdvYi5hci9hcGkvdjIuMC9tdXNlb3MvIikgIyBMbGFtYW1vcyBhbCBlbmRwb2ludCBhaG9yYSB5YSAicGFyc2VhbmRvIiBlbCBKU09OCnN1bW1hcnkobXVzZW9zKQpgYGAKCmBgYHtyfQptdXNlb3MkcmVzdWx0cwpgYGAKClZlbW9zIHF1ZSBlbCB0ZXh0byBzZSBjb252aXJ0aW8gZW4gdW4gb2JqZXRvIGRlIFIgY29uIGxvcyBtaXNtb3MgY2FtcG9zIHF1ZSBwb2RpYW1vcyBsZWVyIGVuIGVsIGZvcm1hdG8gZGUgbGxhdmVzIHkgY29taWxsYXMKCiFbXSguL2pzb25fdG9fUi5wbmcpCgojIyBFamVtcGxvcwoKVmFtb3MgYSB2ZXIgYWxndW5vcyBlamVtcGxvcyBkZSBvdHJhcyBBUElzIHBhcmEgcG9uZXIgZXN0YXMgaWRlYXMgZW4gcHLDoWN0aWNhLiBFbiBjYWRhIGVqZW1wbG8gc2UgcGxhbnRlYW4gdW5hIHNlcmllIGRlIGVqZXJjaWNpb3MKCiMjIyBBUEkgZGUgZGF0b3MgZGUgcmVjZXRhcyBkZSB0cmFnb3MKCkNvbW8gcHJpbWVyIGVqZW1wbG8gZGUgdW5hIGFwaSBzZW5jaWxsYSB2YW1vcyBhIHZlciBlc3RhIGh0dHBzOi8vd3d3LnRoZWNvY2t0YWlsZGIuY29tL2FwaS5waHAuIEFoaSBwb2RlbW9zIHZlciBzdXMgKmVuZHBvaW50cyouIFkgdmFtb3MgYSB2ZXIgYWxnbyBpbnRlcmVzYW50ZSBxdWUgbm8gaGFiaWFtb3MgdmlzdG8gZW4gbGEgQVBJIGRlIGN1bHR1cmEgcXVlIGVzIHF1ZSBsb3MgZW5kcG9pbnRzIHNvbiBtw6l0b2RvcyBxdWUgcHVlZGVuIHJlY2liaXIgKnBhcsOhbWV0cm9zKi4gUG9yIGVqZW1wbG8gdmVtb3MgcXVlIGVsIGVuZHBvaW50IHd3dy50aGVjb2NrdGFpbGRiLmNvbS9hcGkvanNvbi92MS8xL2ZpbHRlci5waHA/aT0qKkdpbioqIG5vcyBwZXJtaXRlIGhhY2VyIHVuYSBiw7pzcXVlZGEgcG9yIGluZ3JlZGllbnRlIChlbCBpbmdyZWRpZW50ZSAqKkdpbioqIGVzIGVsIHZhbG9yIGFzaWduYWRvIGFsIHBhcsOhbWV0cm8gKippKiopLiBWZWFtb3M6CmBgYHtyfQp0cmFnb3MgPSBmcm9tSlNPTigiaHR0cDovL3d3dy50aGVjb2NrdGFpbGRiLmNvbS9hcGkvanNvbi92MS8xL2ZpbHRlci5waHA/aT1HaW4iKQp0cmFnb3MKYGBgClkgc2kgYWhvcmEgcXVlcmVtb3MgdHJhZ29zIHF1ZSBsbGV2ZW4gbmFyYW5qYSBzaW1wbGVtZW50ZSByZWVtcGxhemFtb3MgZWwgaW5ncmVkaWVudGU6CgpgYGB7cn0KdHJhZ29zID0gZnJvbUpTT04oImh0dHA6Ly93d3cudGhlY29ja3RhaWxkYi5jb20vYXBpL2pzb24vdjEvMS9maWx0ZXIucGhwP2k9T3JhbmdlIikKdHJhZ29zCmBgYAoKU2kgcXVlcmVtb3Mgc2FiZXIgcXVlIGluZ3JlZGllbnRlcyBoYXkgZGlzcG9uaWJsZXMgcG9kZW1vcyB1c2FyIGVsICplbmRwb2ludCogaHR0cDovL3d3dy50aGVjb2NrdGFpbGRiLmNvbS9hcGkvanNvbi92MS8xL2xpc3QucGhwP2k9bGlzdAoKYGBge3J9CmluZ3JlZGllbnRlcyA9IGZyb21KU09OKCJodHRwOi8vd3d3LnRoZWNvY2t0YWlsZGIuY29tL2FwaS9qc29uL3YxLzEvbGlzdC5waHA/aT1saXN0IikKaW5ncmVkaWVudGVzCmBgYAoKCiMjIyMgRWplcmNpY2lvcwoKMS4gRWxpamFuIDEwIHRyYWdvcyBhbCBhemFyIChidXNxdWVuIGVuIGxhIFtkb2N1bWVudGFjaW9uXShodHRwczovL3d3dy50aGVjb2NrdGFpbGRiLmNvbS9hcGkucGhwKSBoYXkgdW4gKmVuZHBvaW50KiBwYXJhIHRyYWVyIGRlIDEgdHJhZ28gYWwgYXphciAtZWwgZGUgMTAgZXMgcGFnby0pIHkgY2FsY3VsZW4gbGEgY2FudGlkYWQgZGUgaW5ncmVkaWVudGVzIHByb21lZGlvIHBvciB0cmFnby4gUmVjdWVyZGVuIGVzZSBuw7ptZXJvLCBsbyB2YW1vcyBhIGNvbXBhcnRpciBhbCBmaW5hbCBkZSBsYSBjbGFzZS4gCjEuIEVsaWphbiB1biBpbmdyZWRpZW50ZSBkZWwgZGF0YWZyYW1lIGRlIGluZ3JlZGllbnRlcyB5IGJ1c3F1ZW4gdHJhZ29zIGNvbiBlc2UgaW5ncmVkaWVudGVzOiDCv0N1w6FudG9zIHNvbj8KMS4gwr9DdcOhbCBlcyBsYSBkaXN0cmlidWNpw7NuIGRlIHRpcG9zIGRlIHZhc28gZW4gbG9zIHRyYWdvcyBxdWUgdXNhbiBHaW4/CjEuIMK/U2UgcGFyZWNlIGEgbGEgZGUgdHJhZ29zIHF1ZSB1c2FuIENoYW1wYWduZT8KMS4gVG9tZW4gbGFzIGluc3RydWNjaW9uZXMgcGFyYSBoYWNlciBlbCB0cmFnbyBkZSAxMCB0cmFnb3MgZGlzdGludG9zIGVuIGxvcyBkaXN0aW50b3MgaWRpb21hcyB5IGN1ZW50ZW4gbGEgZnJlY3VlbmNpYSBkZSBjYWRhIGxldHJhIChQaXN0YTogdGFibGUoc3Ryc3BsaXQodGV4dG9faW5zdHJ1Y2Npb25lcywgJycpKSkKMS4gSGFnYW4gdW5vIG8gbcOhcyBncmFmaWNvcyBkZSBiYXJyYXMgwr9TZSBwYXJlY2VuIGxhcyBkaXN0cmlidWNpb25lcz8KMS4gwr9DdcOhbCBlcyBsYSBsZXRyYSBxdWUgbWVqb3IgZGlzdGluZ3VlIGVsIGFsZW3DoW4gZGVsIGl0YWxpYW5vPyAKCgojIyMgQVBJIGRlIGRhdG9zIGRlbCBCYW5jbyBtdW5kaWFsCgpFbiBlc3RhIHBhcnRlIGRlIGxhIGNsYXNlIHZhbW9zIGEgdHJhYmFqYXIgY29uIG90cmEgYXBpIHF1ZSB0aWVuZSBkYXRvcyBlY29uw7NtaWNvcyB5IHNvY2lhbGVzIGRlIGRpc3RpbnRvcyBwYWlzZXMuIEVzIGxhIGFwaSBkZWwgYmFuY28gbXVuZGlhbC4gRXMgdW5hIEFQSSBiYXN0YW50ZSBjb21wbGVqYSwgYWPDoSBwdWVkZW4gZW5jb250cmFyIGxhIGRvY3VtZW50YWNpw7NuIHBhcmEgdXNhcmxhOiBodHRwczovL2RhdGFoZWxwZGVzay53b3JsZGJhbmsub3JnL2tub3dsZWRnZWJhc2UvdG9waWNzLzEyNTU4OS1kZXZlbG9wZXItaW5mb3JtYXRpb24KCkRhZGEgbGEgY2FudGlkYWQgRU5PUk1FIGRlIGluZGljYWRvcmVzIHF1ZSB0aWVuZSBlbCBiYW5jbyBtdW5kaWFsLCB2YW1vcyBhIHVzYXIgZXN0ZSBzaXRpbyBxdWUgbm9zIHBlcm1pdGUgZXhwbG9yYXJsb3MgdmlzdWFsbWVudGUgeSBsdWVnbyBlbGVnaXIgY29uIGN1YWxlcyB2YW1vcyBhIHF1ZXJlciB0cmFiYWphciBodHRwczovL2RhdG9zLmJhbmNvbXVuZGlhbC5vcmcvIEFoaSBwdWVkZW4gYnVzY2FyIHBvciBlamVtcGxvIFBCSSAoaGF5IHZhcmlhcyB2YXJpYW50ZXMgcHVlZGUgc2VyIHBvciBlamVtcGxvIC1QSUIgcGVyIGPDoXBpdGEsIFBQQSAoJCBhIHByZWNpb3MgaW50ZXJuYWNpb25hbGVzIGFjdHVhbGVzKS0gKS4gU2kgZW50cmFuIHZhbiBhIHZlciBxdWUgYXBhcmVjZSB1biBncmFmaWNvIHBhcmEgZWwgbXVuZG8geSBoYXkgdW4gYm90b24gZGUgZGV0YWxsZXMuIFNpIGxvICpjbGlja2VhbiogdmFuIGEgcG9kZXIgZW5jb250cmFyIHVuIGlkIHBhcmEgZWwgaW5kaWNhZG9yLiBFbiBlc3RlIGNhc28gYE5ZLkdEUC5QQ0FQLlBQLkNEYC4gRXNlIGVzIGVsIGlkIHF1ZSB2YW1vcyBuZWNlc2l0YXIgcGFyYSBoYWNlciBsYSBjb25zdWx0YToKIVtdKC4vd2JfaWQucG5nKQoKCmBgYHtyfQpkYXRhID0gZnJvbUpTT04oImh0dHBzOi8vYXBpLndvcmxkYmFuay5vcmcvdjIvZXMvY291bnRyeS9hbGwvaW5kaWNhdG9yL05ZLkdEUC5QQ0FQLlBQLkNEP2Zvcm1hdD1qc29uIikKZGF0YQpgYGAKVmVtb3MgcXVlIGxhIHJlc3B1ZXN0YSBpbmNsdXllIGRvcyBwYXJ0ZXMsIHVub3MgW21ldGFkYXRvc10oaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvTWV0YWRhdG9zKSBxdWUgbm9zIGluZGljYW4gaW5mb3JtYWNpw7NuIGdlbmVyYWwgc29icmUgZWwgcmVjdXJzbyBxdWUgYWNhYmFtb3MgZGUgcGVkaXIuIFBvciBlamVtcGxvIHF1ZSBsYSBjYW50aWRhZCB0b3RhbCBkZSByZWdpc3Ryb3MgZGVsIGRhdGFzZXQgZXMgMTYyMjYgeSBxdWUgc2UgYWN0dWFsaXphcm9uIHBvciDDumx0aW1hIHZleiBlbCAzMCBkZSBKdWxpbyBkZSAyMDIxLiBUYW1iacOpbiBxdWUgc29sbyByZWNpYmltb3MgZW4gZXN0ZSBwZWRpZG8gNTAgZGF0b3MuIEVuIGVzdGUgY2FzbyBsb3MgZGF0b3MgZXN0w6FuIHBhZ2luYWRvcyBlbiAzMjUgcMOhZ2luYXMgZGUgNTAgZWxlbWVudG9zIGNhZGEgdW5hLiBIYXkgdW4gcGFyw6FtZXRybyBkZSBsYSBhcGkgcXVlIG5vcyBwZXJtaXRlIHRyYWVyIHRvZG8gZGUgdW5hOgoKc2kgYWdyZWdhbW9zICZwZXJfcGFnZT0yMDAwMCBjYW1iaWFtb3MgZWwgbWF4aW1vIHBvciBww6FnaW5hIHkgdHJhZW1vcyB0b2RvcyBsb3MgZGF0b3MgZW4gdW5hIHNvbGEgY29uc3VsdGEKCmBgYHtyfQpkYXRhID0gZnJvbUpTT04oImh0dHBzOi8vYXBpLndvcmxkYmFuay5vcmcvdjIvZXMvY291bnRyeS9hbGwvaW5kaWNhdG9yL05ZLkdEUC5QQ0FQLlBQLkNEP2Zvcm1hdD1qc29uJnBlcl9wYWdlPTIwMDAwIikKcGJpID0gZGF0YVtbMl1dCmBgYAp5IHBvZGVtb3MgZ3JhZmljYXIgZWwgcGJpIHBhcmEgQXJnZW50aW5hIHkgQnJhc2lsIGVuIGZ1bmNpw7NuIGRlbCB0aWVtcG8KCmBgYHtyfQp2YWxvcl9hciA9IHBiaVtwYmkkY291bnRyeWlzbzNjb2RlID09ICdBUkcnLCAndmFsdWUnXQphbmlvX2FyID0gcGJpW3BiaSRjb3VudHJ5aXNvM2NvZGUgPT0gJ0FSRycsICdkYXRlJ10KcGxvdChhbmlvX2FyLCB2YWxvcl9hciwgdHlwZT0nbCcsIGNvbD0ncmVkJywgeGxhYiA9ICdBw7FvJywgeWxhYj0nUEJJIFBlci4gQ2FwLiBQUFAnKQoKdmFsb3JfYnIgPSBwYmlbcGJpJGNvdW50cnlpc28zY29kZSA9PSAnQlJBJywgJ3ZhbHVlJ10KYW5pb19iciA9IHBiaVtwYmkkY291bnRyeWlzbzNjb2RlID09ICdCUkEnLCAnZGF0ZSddCmxpbmVzKGFuaW9fYnIsIHZhbG9yX2JyLCBjb2w9J2JsdWUnKQpsZWdlbmQoMTk2MCwgMTUwMDAsIGxlZ2VuZD1jKCJBcmdlbnRpbmEiLCAiQnJhc2lsIiksCiAgICAgICBjb2w9YygicmVkIiwgImJsdWUiKSwgbHR5PWMoMSwgMSksIGNleD0wLjgpCmBgYAoKIyMjIyBFamVyY2ljaW9zCgpUZW5nYW4gcHJlc2VudGUgbGEgZG9jdW1lbnRhY2lvbiBwYXJhIGhhY2VyIGxvcyBlamVyY2ljaW9zIGh0dHBzOi8vZGF0YWhlbHBkZXNrLndvcmxkYmFuay5vcmcva25vd2xlZGdlYmFzZS9hcnRpY2xlcy84OTg1ODEtYXBpLWJhc2ljLWNhbGwtc3RydWN0dXJlcyAKCjEuIEVsaWphbiBvdHJvIGluZGljYWRvciB1c2FuZG8gZWwgZXhwbG9yYWRvciBkZWwgYmFuY28gbXVuZGlhbCB5IHRyYWlnYW4gbG9zIGRhdG9zIHZpYSBBUEkKMS4gUHJ1ZWJlbiB0cmFlciBkYXRvcyBzb2xvIHBhcmEgdW4gcGFyIGRlIHBhaXNlcyB5IGRlbnRybyBkZSB1biByYW5nbyBwYXJ0aWN1bGFyIGRlIGHDsW9zIChjb25zdWx0ZW4gbGEgZG9jdW1lbnRhY2lvbiBwYXJhIHZlciBxdWUgcGFydGUgZGUgbGEgVVJMIG1vZGlmaWNhcikKMS4gSGFnYW4gdW4gZ3JhZmljbyBkZSBiYXJyYXMgY29uIGxvcyAxMCBwYWlzZXMgY29uIG1heW9yIGVzcGVyYW56YSBkZSB2aWRhIChjb25zaWRlcmFuZG8gc29sbyBwYWlzZXMgZGUgYWwgbWVub3MgMTBNIGRlIGhhYml0YW50ZXMpIGVuIGxvcyA4MCcgeSBob3kuIMK/Q2FtYmlvIGVsIHRvcCAxMD8KMS4gRGVudHJvIGRlIGxvcyBwYWlzZXMgZGUgbWFzIGRlIDEwTSBkZSBoYWJpdGFudGVzIMK/Q3XDoWxlcyBhdW1lbnRhcm9uIG3DoXMgc3UgZXNwZXJhbnphIGRlIHZpZGEgZW4gbG9zIMO6bHRpbW9zIDQwIGHDsW9zPwoKCiMjIyMgQVBJcyBncmFuZGVzIHN1ZWxlbiB0ZW5lciBwYXF1ZXRlcwoKTGFzIEFQSXMgZ3JhbmRlcyBvIG11eSB1c2FkYXMgc3VlbGVuIHRlbmVyIHBhcXVldGVzIGluc3RhbGFibGVzIHBhcmEgbG9zIGxlbmd1YWplcyBtw6FzIHBvcHVsYXJlcyBkZSBtYW5lcmEgdGFsIHF1ZSBubyBoYWNlIGZhbHRhIGVzdGFyIGdlbmVyYW5kbyBhIG1hbm8gbGFzIFVSTHMgeSBjb25zdWx0YXMgSFRUUC4gTXVjaGFzIHZlY2VzIGVzb3MgcGFxdWV0ZXMgc29uIGNvbnN0cnVpZG9zIHBvciBsYSBwcm9waWEgY29tdW5pZGFkIGRlIGNhZGEgbGVuZ3VhamUgeSBubyBuZWNlc2FyaWFtZW50ZSBwb3IgcXVpw6luIGNyZW8gbGEgQVBJLiBFc2UgZXMgZWwgY2FzbyBkZSBsYSBhcGkgZGVsIGJhbmNvIG11bmRpYWwgcXVlIHRpZW5lIGRpc3BvbmlibGUgZWwgcGFxdWV0ZSBXREkgcGFyYSBSLiAgUGFyYSBpbnN0YWxhcmxvIGBpbnN0YWxsLnBhY2thZ2VzKCdXREknKWAgLiBQdWVkZW4gdmVyIGxhIGRvY3VtZW50YWNpw7NuIGVuIGh0dHBzOi8vZ2l0aHViLmNvbS92aW5jZW50YXJlbGJ1bmRvY2svV0RJIAoKMS4gUHJ1ZWJlbiB1c2FyIGRpcmVjdGFtZW50ZSBlbCBwYXF1ZXRlIGVuIGx1Z2FyIGRlIHVzYXIgbGEgVVJMIHBhcmEgcmVwZXRpciBhbGd1bm8gZGUgbG9zIHB1bnRvcyBkZWwgZWplcmNpY2lvIGFudGVyaW9yCgojIyMgQVBJIGRlIGRhdG9zIGdlb3JyZWZlcmVuY2lhZG9zIGRlIGFyZ2VudGluYS5nb2IuYXIKCkZpbmFsbWVudGUgdmFtb3MgYSB0YWJhamFyIGNvbiB1bmEgQVBJIHVuIHBvY28gZGlzdGludGEgcGFyYSBlbnRlbmRlciBxdWUgbm8gc29sbyBzaXJ2ZW4gcGFyYSBjb25zdWx0YXIgZGF0b3Mgc2lubyBxdWUgcG9kZW1vcyB1c2FybGFzIGNvbW8gc2VydmljaW9zIGUgaW5jbHVpcmxhcyBjb21vIHBhcnRlIGRlIG51ZXN0cmFzIHJ1dGluYXMgZGUgYW7DoWxpc2lzLiBDb21vIGVqZW1wbG8gdmFtb3MgYSB1c2FyIGVzdGEgQVBJIGRlIGdlb3JyZWZlcmVuY2lhY2nDs24gbmFjaW9uYWwgaHR0cHM6Ly9kYXRvc2dvYmFyLmdpdGh1Yi5pby9nZW9yZWYtYXItYXBpLwoKRXN0YSBhcGkgdGllbmUgZGlzdGludG9zICplbmRwb2ludHMqIHBhcmEgY2FkYSBjYXNvIGRlIHVzbywgcG9yIGVqZW1wbG8gcG9kZW1vcyBzYWJlciBhIHF1ZSBtdW5jaXBpbywgZGVwYXJ0YW1lbnRvIHkgcHJvdmluY2lhIHBlcnRlbmVjZW4gY2llcnRhcyBjb29yZGVuYWRhczoKCgpgYGB7cn0KZnJvbUpTT04oImh0dHBzOi8vYXBpcy5kYXRvcy5nb2IuYXIvZ2VvcmVmL2FwaS91YmljYWNpb24/bGF0PS0yNy4yNzQxJmxvbj0tNjYuNzUyOSIpCmBgYApPIGN1YWwgZXMgbGEgdWJpY2FjacOzbiBkZSB1bmEgZGFkYSBkaXJlY2Npw7NuLiBQb3IgZWplbXBsbywgwr9Ew7NuZGUgcXVlZGEgQXZlbmlkYSBSaXZhZGF2aWEgMzAwMD8KCmBgYHtyfQpyZXMgPSBmcm9tSlNPTigiaHR0cHM6Ly9hcGlzLmRhdG9zLmdvYi5hci9nZW9yZWYvYXBpL2RpcmVjY2lvbmVzP2RpcmVjY2lvbj1hdi4lMjByaXZhZGF2aWElMjAzMDAwIikKcmVzJGRpcmVjY2lvbmVzJG5vbWVuY2xhdHVyYQpgYGAKVmVtb3MgcXVlIGhheSB2YXJpYXMgb3BjaW9uZXMgY29tcGF0aWJsZXMgY29uIGVzYSBkaXJlY2Npw7NuIHkgcmVjaWJpbW9zIG11Y2hhIGluZm9ybWFjacOzbiBhZGljaW9uYWwgc29icmUgZXNhcyBkaXJlY2Npb25lcywgcG9yIGVqZW1wbG8gc3VzIGNvb3JkZW5hZGFzOgpgYGB7cn0KcmVzJGRpcmVjY2lvbmVzJHViaWNhY2lvbgpgYGAKU2kgYWdyZWdhbW9zIGFsZ28gZGUgaW5mb3JtYWNpb24gZXh0cmEsIGNvbW8gbGEgcHJvdmluY2lhIG8gZGlzdHJpdG8gc29icmUgbGEgcXVlIGVzdGFtb3MgaGFjaWVuZG8gbGEgYnVzcXVlZGEgKHBvciBlamVtcGxvIENBQkEpIHBvZGVtb3MgY29uc2VndWlyIHVuYSByZXNwdWVzdGEgw7puaWNhCmBgYHtyfQp4ID0gZnJvbUpTT04oImh0dHBzOi8vYXBpcy5kYXRvcy5nb2IuYXIvZ2VvcmVmL2FwaS9kaXJlY2Npb25lcz9kaXJlY2Npb249YXYuJTIwcml2YWRhdmlhJTIwMzAwMCZwcm92aW5jaWE9Y2FiYSIpCngkZGlyZWNjaW9uZXMkbm9tZW5jbGF0dXJhCmBgYApIYWJyw6FuIG5vdGFkbyBxdWUgZXN0YW1vcyBpbnRyb2R1Y2llbmRvIGxhcyBkaXJlY2Npb25lcyBkZSB1bmEgbWFuZXJhIHVuIHBvY28gZXh0cmHDsWEgZW4gbGEgdXJsLCBlc3RvIGVzIHBvcnF1ZSBsYXMgVVJMcyBubyBwdWVkZW4gY29udGVuZXIgY2llcm9zIGNhcmFjdGVyZXMgKGNvbW8gZXNwYWNpb3MpIHkgZW50b25jZXMgaGF5IHF1ZSBjb252ZXJ0aXJsb3MgYSBvdHJhcyBzZWN1ZW5jaWFzIGRlIGNhcmFjdGVyZXMgcGVybWl0aWRvcy4gUG9yIHN1ZXJ0ZSBoYXkgdW5hIGZ1bmNpw7NuIHF1ZSB5YSBsbyBoYWNlIHF1ZSBwb2RlbW9zIHVzYXI6CgpgYGB7cn0KVVJMZW5jb2RlKCJBdi4gZGUgbWF5byAxNjU4IikKYGBgCgpgYGB7cn0KeCA9IGZyb21KU09OKFVSTGVuY29kZSgiaHR0cHM6Ly9hcGlzLmRhdG9zLmdvYi5hci9nZW9yZWYvYXBpL2RpcmVjY2lvbmVzP2RpcmVjY2lvbj1hdi4gZGVsIGxpYmVydGFkb3IgMTQwMCZwcm92aW5jaWE9Y2FiYSIpKQpwcmludCh4JGRpcmVjY2lvbmVzJG5vbWVuY2xhdHVyYSkKcHJpbnQoeCRkaXJlY2Npb25lcyR1YmljYWNpb24pCmBgYAoKIyMjIyBFamVyY2ljaW9zCgpFc2NyaWJhbiB1biBwZXF1ZcOxbyBzY3JpcHQgcXVlIHBhcmEgY2FkYSBtdXNlbyBkZSBsYSBBUEkgZGVsIG1pbmlzdGVyaW8gZGUgY3VsdHVyYSBvYnRlbmdhIHN1cyBjb29yZGVuYWRhcyB1c2FuZG8gbGEgQVBJIGRlIGdlb3JyZWZlcmVuY2lhY2nDs24uIFBhcnRhbiBlbCBwcm9ibGVtYSBlbiBwYXNvczoKCjEuIENvbnNpZ2FuIGxhIGRpcmVjY2nDs24gZGUgdW4gbXVzZW8KMS4gRXh0cmFpZ2FuIGxhcyBjb29yZGVuYWRhcyBjb24gbGEgYXBpIGRlIGdlb3JyZWYgcGFyYSBlc2UgbXVzZW8KMS4gUG9uZ2FuIGVzb3MgZG9zIGPDs2RpZ29zIGp1bnRvcyB5IGbDrWplbnNlIHF1ZSBhbmRlCjEuIENvbnN0cnV5YW4gdW4gKmZvciBsb29wKiBxdWUgcmVjb3JyYSB0b2RvcyBsb3MgbXVzZW9zIHkgZWplY3V0ZSBlbCBjw7NkaWdvIHF1ZSBhcm1hcm9uIGVuIGVsIHB1bnRvIGFudGVyaW9yCjEuIEVsaWphbiB1biB0cmFnbyBjb24gbyBzaW4gYWxjb2hvbCBkZSBsYSBBUEkgZGUgdHJhZ29zIHkgYnJpbmRlbiBwb3Igc3UgZWplcmNpY2lvCgoKIyBTZXNzaW9uIEluZm8KUmVmZXJlbmNpYSBkZSBsYSBzZXNpb24gY29uIGxhIHF1ZSBmdWUgY29ycmlkbyBlc3RlIG5vdGVib29rCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK