1. Introducción al lenguaje XPATH

XPATH significa lenguaje XML Path, y nos permite, como su nombre lo indica, atravesar un camino en un árbol HTML, dándonos un enfoque alternativo a los selectores CSS.

En particular, este no solo navega hacia abajo en un árbol HTML, sino también hacia arriba. Además nos permite seleccionar nodos basados en las propiedades de otros nodos, logrando así que selecciones más avanzadas y customizadas sean posibles.

3. ¿Cómo seleccionar nodos con XPATH?

La forma más sencilla de seleccionar nodos con XPATH es usando el tipo de nodo. Para esto escribiremos el tipo de nodo antecedido por //. Veamos un ejemplo.

library(rvest)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.4     ✓ purrr   0.3.4
✓ tibble  3.1.2     ✓ dplyr   1.0.7
✓ tidyr   1.1.3     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.1
── Conflicts ─────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter()         masks stats::filter()
x readr::guess_encoding() masks rvest::guess_encoding()
x dplyr::lag()            masks stats::lag()
url <- "https://www.york.ac.uk/teaching/cws/wws/webpage1.html"
html <- url %>% read_html()
html %>% 
  html_element(xpath = "//ul") %>% 
  html_text()
[1] "create your own simple pages\nread and appreciate pages created by others\ndevelop an understanding of the creative and literary implications of web-texts\nhave the confidence to branch out into more complex web design \n"

La selección que acabamos de hacer es equivalente a tomar el selector ul:

html %>% 
  html_element(css = "ul") %>% 
  html_text()
[1] "create your own simple pages\nread and appreciate pages created by others\ndevelop an understanding of the creative and literary implications of web-texts\nhave the confidence to branch out into more complex web design \n"

Para seleccionar la descendencia de un nodo, tan solo lo añadiremos al XPATH el tipo de nodo hijo, precedido de un /. Veamos un ejemplo.

html %>% 
  html_element(xpath = "//ul/li") %>% 
  html_text()
[1] "create your own simple pages\n"

Si lo hacemos con CSS, tendríamos:

html %>% 
  html_element(css = "ul > li") %>% 
  html_text()
[1] "create your own simple pages\n"

Ahora, seleccionemos un nodo en base a la característica de un hijo (algo que solo es posible con XPATH). Específicamente, seleccionemos aquellos nodos p que tienen un hijo a. Para ello, denotaremos la característica del nodo entre [].

html %>% 
  html_elements(xpath = "//p[a]")
{xml_nodeset (1)}
[1] <p>start formatting in <a href="webpage2.html">lesson two</a>\n<br><a href="col3.html">bac ...

Así, la sintaxis general de XPATH viene definida por ejes, pasos y predicados. Los ejes corresponden a los símbolos // o / que ya hemos utilizado y definen la relación entre nodos. / corresponde una relación de hijo, mientras que // corresponde a una relación general de descendencia. Los pasos por otro lado, se definen entre los ejes y corresponden a un elemento HTML. Finalmente, los predicados se especifican entre [] y declaran condiciones que deben ser cumplidas por elemento HTML que le preceda.

Al igual que en el código CSS, el operador * selecciona todos los elementos HTML encontrados.

4. Funciones XPATH y predicados avanzados

Además de los ejes, pasos y predicados, otra parte fundamental del lenguaje XPATH son las funciones. Con estas, el obtener elementos específicos de un sitio web se vuelve aún más fácil. Para ello, revisaremos algunas funciones.

  • La función position() nos ayuda a referenciar la posición un elemento HTML que estemos buscando, utilizándola dentro de un predicado, como se muestra a continuación. Cabe notar que esta funciona similar a la pseudo-clase nth-child, sin embargo, esta permita usar operadores como >, <, != y = para ampliar el espectro de selección.
html %>% 
  html_elements(xpath = "//p[position()=2]") %>% 
  html_text()
[1] "HTML isn't computer code, but is a language that uses US English to enable texts (words, images, sounds) to be inserted and formatting such as colo(u)r and centre/ering to be written in. The process is fairly simple; the main difficulties often lie in small mistakes - if you slip up while word processing your reader may pick up your typos, but the page will still be legible. However, if your HTML is inaccurate the page may not appear - writing web pages is, at the least, very good practice for proof reading!"
html %>% 
  html_elements(xpath = "//p[position()<=2]") %>% 
  html_text()
[1] "There are lots of ways to create web pages using already coded programmes. These lessons will teach you how to use the underlying HyperText Markup Language -  HTML. \n"                                                                                                                                                                                                                                                                                                                                                          
[2] "HTML isn't computer code, but is a language that uses US English to enable texts (words, images, sounds) to be inserted and formatting such as colo(u)r and centre/ering to be written in. The process is fairly simple; the main difficulties often lie in small mistakes - if you slip up while word processing your reader may pick up your typos, but the page will still be legible. However, if your HTML is inaccurate the page may not appear - writing web pages is, at the least, very good practice for proof reading!"
  • La función count() nos ayuda a buscar los nodos que tienen un número específico de hijos de un determinado tipo. Veamos un ejemplo.
html %>% 
  html_element(xpath = "//ul[count(li)=4]")
{html_node}
<ul>
[1] <li>create your own simple pages\n</li>\n
[2] <li>read and appreciate pages created by others\n</li>\n
[3] <li>develop an understanding of the creative and literary implications of web-texts\n</li>\n
[4] <li>have the confidence to branch out into more complex web design \n</li>
html %>% 
  html_element(xpath = "//ul[count(li)=3]")
{xml_missing}
<NA>

En esta selección, hemos ubicado a los elementos ul con 4 hijos de tipo li, si cambiamos a 3, este no encuentra nada, dado que en la página web de ejemplo no existen ul con menos o más de 4 li. Nótese que esta función también puede ser utilizada con los operadores >, <, != y =.

  • La función text() nos ayuda a buscar nodos que contengan un determinado texto. Veámoslo en acción.
html %>% 
  html_elements(xpath = "//li[text()='create your own simple pages\n']")
{xml_nodeset (1)}
[1] <li>create your own simple pages\n</li>\n

Esta función también nos permite hacer la búsqueda de un nodo por texto, omitiendo clases y demás características.

html %>% 
  html_elements(xpath = "//*[text()='create your own simple pages\n']")
{xml_nodeset (1)}
[1] <li>create your own simple pages\n</li>\n
  • En el mismo sentido, la función contains() nos permite buscar el nodo con una coincidencia parcial de texto.
html %>% 
  html_elements(xpath = "//*[contains(text(),'HEAD')]")
{xml_nodeset (1)}
[1] <li>&lt; HEAD &gt; this is a kind of preface of vital information that doesn't appear on t ...

5. Otras opciones en XPATH

Usando XPATH también podemos combinar varias condiciones dentro de un predicado, sea usando el operador and u or. A continuación un ejemplo.

html %>% 
  html_elements(xpath = "//*[contains(text(),'vital information') or contains(text(),'understanding of the creative')]") %>% 
  html_text()
[1] "develop an understanding of the creative and literary implications of web-texts\n"            
[2] "< HEAD > this is a kind of preface of vital information that doesn't appear on the screen. \n"

Finalmente, podemos seleccionar elementos padre de un nodo a través del operador .., como se muestra a continuación.

html %>% 
  html_elements(xpath = "//*[text()='create your own simple pages\n']") %>% 
  html_elements(xpath = "..")
{xml_nodeset (1)}
[1] <ul>\n<li>create your own simple pages\n</li>\n<li>read and appreciate pages created by ot ...

Con este capítulo cerramos los conocimientos necesarios para utilizar rvest y pasaremos a los ejercicios.

LS0tCnRpdGxlOiAiV2ViIFNjcmFwcGluZyBjb24gUiIKc3VidGl0bGU6ICdDYXDDrXR1bG8gNTogTGVuZ3VhamUgWFBBVEgnCmF1dGhvcjogSHVnbyBQb3JyYXMKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgY3NzOiBFc3RpbG9zLmNzcwogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiB0cnVlCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlCmJpYmxpb2dyYXBoeTogQmlibGlvZ3JhZmlhLmJpYgpjc2w6IGNlcGFsLnhtbAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyAqKjEuIEludHJvZHVjY2nDs24gYWwgbGVuZ3VhamUgWFBBVEgqKgoKIVtdKGh0dHBzOi8vZW5jcnlwdGVkLXRibjAuZ3N0YXRpYy5jb20vaW1hZ2VzP3E9dGJuOkFOZDlHY1RzVk9tSVh2OTF5TGI1SHBUdDFYT2VhMk9Ia1hrdEJHUjVxUSZ1c3FwPUNBVSkKClhQQVRIIHNpZ25pZmljYSBsZW5ndWFqZSBYTUwgUGF0aCwgeSBub3MgcGVybWl0ZSwgY29tbyBzdSBub21icmUgbG8gaW5kaWNhLCAqKmF0cmF2ZXNhciB1biBjYW1pbm8gZW4gdW4gw6FyYm9sIEhUTUwqKiwgZMOhbmRvbm9zIHVuIGVuZm9xdWUgYWx0ZXJuYXRpdm8gYSBsb3Mgc2VsZWN0b3JlcyBDU1MuCgpFbiBwYXJ0aWN1bGFyLCBlc3RlIG5vIHNvbG8gbmF2ZWdhIGhhY2lhIGFiYWpvIGVuIHVuIMOhcmJvbCBIVE1MLCAqKnNpbm8gdGFtYmnDqW4gaGFjaWEgYXJyaWJhKiouIEFkZW3DoXMgbm9zIHBlcm1pdGUgKipzZWxlY2Npb25hciBub2RvcyBiYXNhZG9zIGVuIGxhcyBwcm9waWVkYWRlcyBkZSBvdHJvcyBub2RvcyoqLCBsb2dyYW5kbyBhc8OtIHF1ZSBzZWxlY2Npb25lcyBtw6FzIGF2YW56YWRhcyB5IGN1c3RvbWl6YWRhcyBzZWFuIHBvc2libGVzLiAKCiMgKiozLiDCv0PDs21vIHNlbGVjY2lvbmFyIG5vZG9zIGNvbiBYUEFUSD8qKgoKTGEgZm9ybWEgbcOhcyBzZW5jaWxsYSBkZSBzZWxlY2Npb25hciBub2RvcyBjb24gWFBBVEggZXMgdXNhbmRvIGVsIHRpcG8gZGUgbm9kby4gUGFyYSBlc3RvIGVzY3JpYmlyZW1vcyBlbCB0aXBvIGRlIG5vZG8gYW50ZWNlZGlkbyBwb3IgKiovLyoqLiBWZWFtb3MgdW4gZWplbXBsby4KCmBgYHtyfQpsaWJyYXJ5KHJ2ZXN0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKdXJsIDwtICJodHRwczovL3d3dy55b3JrLmFjLnVrL3RlYWNoaW5nL2N3cy93d3Mvd2VicGFnZTEuaHRtbCIKaHRtbCA8LSB1cmwgJT4lIHJlYWRfaHRtbCgpCmh0bWwgJT4lIAogIGh0bWxfZWxlbWVudCh4cGF0aCA9ICIvL3VsIikgJT4lIAogIGh0bWxfdGV4dCgpCmBgYApMYSBzZWxlY2Npw7NuIHF1ZSBhY2FiYW1vcyBkZSBoYWNlciBlcyBlcXVpdmFsZW50ZSBhIHRvbWFyIGVsIHNlbGVjdG9yIHVsOgoKYGBge3J9Cmh0bWwgJT4lIAogIGh0bWxfZWxlbWVudChjc3MgPSAidWwiKSAlPiUgCiAgaHRtbF90ZXh0KCkKYGBgCgpQYXJhIHNlbGVjY2lvbmFyIGxhICoqZGVzY2VuZGVuY2lhKiogZGUgdW4gbm9kbywgdGFuIHNvbG8gbG8gYcOxYWRpcmVtb3MgYWwgWFBBVEggZWwgdGlwbyBkZSBub2RvIGhpam8sIHByZWNlZGlkbyBkZSB1biAqKi8qKi4gVmVhbW9zIHVuIGVqZW1wbG8uCgpgYGB7cn0KaHRtbCAlPiUgCiAgaHRtbF9lbGVtZW50KHhwYXRoID0gIi8vdWwvbGkiKSAlPiUgCiAgaHRtbF90ZXh0KCkKYGBgCgpTaSBsbyBoYWNlbW9zIGNvbiBDU1MsIHRlbmRyw61hbW9zOgoKYGBge3J9Cmh0bWwgJT4lIAogIGh0bWxfZWxlbWVudChjc3MgPSAidWwgPiBsaSIpICU+JSAKICBodG1sX3RleHQoKQpgYGAKCkFob3JhLCBzZWxlY2Npb25lbW9zIHVuIG5vZG8gZW4gYmFzZSBhIGxhIGNhcmFjdGVyw61zdGljYSBkZSB1biBoaWpvIChhbGdvIHF1ZSBzb2xvIGVzIHBvc2libGUgY29uIFhQQVRIKS4gRXNwZWPDrWZpY2FtZW50ZSwgc2VsZWNjaW9uZW1vcyBhcXVlbGxvcyBub2RvcyAqKnAqKiBxdWUgdGllbmVuIHVuIGhpam8gKiphKiouIFBhcmEgZWxsbywgZGVub3RhcmVtb3MgbGEgY2FyYWN0ZXLDrXN0aWNhIGRlbCBub2RvIGVudHJlICoqW10qKi4KCmBgYHtyfQpodG1sICU+JSAKICBodG1sX2VsZW1lbnRzKHhwYXRoID0gIi8vcFthXSIpCmBgYAoKQXPDrSwgbGEgKipzaW50YXhpcyBnZW5lcmFsIGRlIFhQQVRIKiogdmllbmUgZGVmaW5pZGEgcG9yICoqZWplcyoqLCAqKnBhc29zKiogeSAqKnByZWRpY2Fkb3MqKi4gTG9zIGVqZXMgY29ycmVzcG9uZGVuIGEgbG9zIHPDrW1ib2xvcyAqKi8vKiogbyAqKi8qKiBxdWUgeWEgaGVtb3MgdXRpbGl6YWRvIHkgZGVmaW5lbiBsYSByZWxhY2nDs24gZW50cmUgbm9kb3MuICoqLyoqIGNvcnJlc3BvbmRlIHVuYSByZWxhY2nDs24gZGUgaGlqbywgbWllbnRyYXMgcXVlICoqLy8qKiBjb3JyZXNwb25kZSBhIHVuYSByZWxhY2nDs24gZ2VuZXJhbCBkZSBkZXNjZW5kZW5jaWEuIExvcyBwYXNvcyBwb3Igb3RybyBsYWRvLCBzZSBkZWZpbmVuIGVudHJlIGxvcyBlamVzIHkgY29ycmVzcG9uZGVuIGEgdW4gZWxlbWVudG8gSFRNTC4gRmluYWxtZW50ZSwgbG9zIHByZWRpY2Fkb3Mgc2UgZXNwZWNpZmljYW4gZW50cmUgKipbXSoqIHkgZGVjbGFyYW4gY29uZGljaW9uZXMgcXVlIGRlYmVuIHNlciBjdW1wbGlkYXMgcG9yIGVsZW1lbnRvIEhUTUwgcXVlIGxlIHByZWNlZGEuIAoKIVtdKGh0dHBzOi8vd3d3LnNjaWVudGVjaGVhc3kuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE5LzA4L3hwYXRoLXN5bnRheC5wbmcpCgpBbCBpZ3VhbCBxdWUgZW4gZWwgY8OzZGlnbyBDU1MsIGVsIG9wZXJhZG9yICoqXCoqKiBzZWxlY2Npb25hIHRvZG9zIGxvcyBlbGVtZW50b3MgSFRNTCBlbmNvbnRyYWRvcy4KCiMgKio0LiBGdW5jaW9uZXMgWFBBVEggeSBwcmVkaWNhZG9zIGF2YW56YWRvcyoqCgpBZGVtw6FzIGRlIGxvcyBlamVzLCBwYXNvcyB5IHByZWRpY2Fkb3MsIG90cmEgcGFydGUgZnVuZGFtZW50YWwgZGVsIGxlbmd1YWplIFhQQVRIIHNvbiBsYXMgKipmdW5jaW9uZXMqKi4gQ29uIGVzdGFzLCBlbCBvYnRlbmVyIGVsZW1lbnRvcyBlc3BlY8OtZmljb3MgZGUgdW4gc2l0aW8gd2ViIHNlIHZ1ZWx2ZSBhw7puIG3DoXMgZsOhY2lsLiBQYXJhIGVsbG8sIHJldmlzYXJlbW9zIGFsZ3VuYXMgZnVuY2lvbmVzLgoKKyBMYSBmdW5jacOzbiAqKnBvc2l0aW9uKCkqKiBub3MgYXl1ZGEgYSByZWZlcmVuY2lhciBsYSBwb3NpY2nDs24gdW4gZWxlbWVudG8gSFRNTCBxdWUgZXN0ZW1vcyBidXNjYW5kbywgdXRpbGl6w6FuZG9sYSBkZW50cm8gZGUgdW4gcHJlZGljYWRvLCBjb21vIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuLiBDYWJlIG5vdGFyIHF1ZSBlc3RhIGZ1bmNpb25hIHNpbWlsYXIgYSBsYSBwc2V1ZG8tY2xhc2UgKipudGgtY2hpbGQqKiwgc2luIGVtYmFyZ28sIGVzdGEgcGVybWl0YSB1c2FyIG9wZXJhZG9yZXMgY29tbyAqKj4qKiwgKio8KiosICoqIT0qKiB5ICoqPSoqIHBhcmEgYW1wbGlhciBlbCBlc3BlY3RybyBkZSBzZWxlY2Npw7NuLgoKYGBge3J9Cmh0bWwgJT4lIAogIGh0bWxfZWxlbWVudHMoeHBhdGggPSAiLy9wW3Bvc2l0aW9uKCk9Ml0iKSAlPiUgCiAgaHRtbF90ZXh0KCkKYGBgCmBgYHtyfQpodG1sICU+JSAKICBodG1sX2VsZW1lbnRzKHhwYXRoID0gIi8vcFtwb3NpdGlvbigpPD0yXSIpICU+JSAKICBodG1sX3RleHQoKQpgYGAKCisgTGEgZnVuY2nDs24gKipjb3VudCgpKiogbm9zIGF5dWRhIGEgYnVzY2FyIGxvcyBub2RvcyBxdWUgdGllbmVuIHVuIG7Dum1lcm8gZXNwZWPDrWZpY28gZGUgaGlqb3MgZGUgdW4gZGV0ZXJtaW5hZG8gdGlwby4gVmVhbW9zIHVuIGVqZW1wbG8uCgpgYGB7cn0KaHRtbCAlPiUgCiAgaHRtbF9lbGVtZW50KHhwYXRoID0gIi8vdWxbY291bnQobGkpPTRdIikKYGBgCgpgYGB7cn0KaHRtbCAlPiUgCiAgaHRtbF9lbGVtZW50KHhwYXRoID0gIi8vdWxbY291bnQobGkpPTNdIikKYGBgCgpFbiBlc3RhIHNlbGVjY2nDs24sIGhlbW9zIHViaWNhZG8gYSBsb3MgZWxlbWVudG9zICoqdWwqKiBjb24gNCBoaWpvcyBkZSB0aXBvICoqbGkqKiwgc2kgY2FtYmlhbW9zIGEgMywgZXN0ZSBubyBlbmN1ZW50cmEgbmFkYSwgZGFkbyBxdWUgZW4gbGEgcMOhZ2luYSB3ZWIgZGUgZWplbXBsbyBubyBleGlzdGVuICoqdWwqKiBjb24gbWVub3MgbyBtw6FzIGRlIDQgKipsaSoqLiBOw7N0ZXNlIHF1ZSBlc3RhIGZ1bmNpw7NuIHRhbWJpw6luIHB1ZWRlIHNlciB1dGlsaXphZGEgY29uIGxvcyBvcGVyYWRvcmVzICoqPioqLCAqKjwqKiwgKiohPSoqIHkgKio9KiouCgorIExhIGZ1bmNpw7NuICoqdGV4dCgpKiogbm9zIGF5dWRhIGEgYnVzY2FyIG5vZG9zIHF1ZSBjb250ZW5nYW4gdW4gZGV0ZXJtaW5hZG8gdGV4dG8uIFZlw6Ftb3NsbyBlbiBhY2Npw7NuLgoKYGBge3J9Cmh0bWwgJT4lIAogIGh0bWxfZWxlbWVudHMoeHBhdGggPSAiLy9saVt0ZXh0KCk9J2NyZWF0ZSB5b3VyIG93biBzaW1wbGUgcGFnZXNcbiddIikKYGBgCgpFc3RhIGZ1bmNpw7NuIHRhbWJpw6luIG5vcyBwZXJtaXRlIGhhY2VyIGxhIGLDunNxdWVkYSBkZSB1biBub2RvIHBvciB0ZXh0bywgb21pdGllbmRvIGNsYXNlcyB5IGRlbcOhcyBjYXJhY3RlcsOtc3RpY2FzLgoKYGBge3J9Cmh0bWwgJT4lIAogIGh0bWxfZWxlbWVudHMoeHBhdGggPSAiLy8qW3RleHQoKT0nY3JlYXRlIHlvdXIgb3duIHNpbXBsZSBwYWdlc1xuJ10iKQpgYGAKCisgRW4gZWwgbWlzbW8gc2VudGlkbywgbGEgZnVuY2nDs24gKipjb250YWlucygpKiogbm9zIHBlcm1pdGUgYnVzY2FyIGVsIG5vZG8gY29uIHVuYSBjb2luY2lkZW5jaWEgKipwYXJjaWFsKiogZGUgdGV4dG8uCgpgYGB7cn0KaHRtbCAlPiUgCiAgaHRtbF9lbGVtZW50cyh4cGF0aCA9ICIvLypbY29udGFpbnModGV4dCgpLCdIRUFEJyldIikKYGBgCgojICoqNS4gT3RyYXMgb3BjaW9uZXMgZW4gWFBBVEgqKgoKVXNhbmRvIFhQQVRIIHRhbWJpw6luIHBvZGVtb3MgY29tYmluYXIgdmFyaWFzIGNvbmRpY2lvbmVzIGRlbnRybyBkZSB1biBwcmVkaWNhZG8sIHNlYSB1c2FuZG8gZWwgb3BlcmFkb3IgKiphbmQqKiB1ICoqb3IqKi4gQSBjb250aW51YWNpw7NuIHVuIGVqZW1wbG8uCgpgYGB7cn0KaHRtbCAlPiUgCiAgaHRtbF9lbGVtZW50cyh4cGF0aCA9ICIvLypbY29udGFpbnModGV4dCgpLCd2aXRhbCBpbmZvcm1hdGlvbicpIG9yIGNvbnRhaW5zKHRleHQoKSwndW5kZXJzdGFuZGluZyBvZiB0aGUgY3JlYXRpdmUnKV0iKSAlPiUgCiAgaHRtbF90ZXh0KCkKYGBgCgpGaW5hbG1lbnRlLCBwb2RlbW9zIHNlbGVjY2lvbmFyICoqZWxlbWVudG9zIHBhZHJlKiogZGUgdW4gbm9kbyBhIHRyYXbDqXMgZGVsIG9wZXJhZG9yICoqLi4qKiwgY29tbyBzZSBtdWVzdHJhIGEgY29udGludWFjacOzbi4KCmBgYHtyfQpodG1sICU+JSAKICBodG1sX2VsZW1lbnRzKHhwYXRoID0gIi8vKlt0ZXh0KCk9J2NyZWF0ZSB5b3VyIG93biBzaW1wbGUgcGFnZXNcbiddIikgJT4lIAogIGh0bWxfZWxlbWVudHMoeHBhdGggPSAiLi4iKQpgYGAKCkNvbiBlc3RlIGNhcMOtdHVsbyBjZXJyYW1vcyBsb3MgY29ub2NpbWllbnRvcyBuZWNlc2FyaW9zIHBhcmEgdXRpbGl6YXIgcnZlc3QgeSBwYXNhcmVtb3MgYSBsb3MgZWplcmNpY2lvcy4KCgoKCgoK