---
title: "Aplicação de XPath com R"
subtitle: "Uso do `xml2`"
author: "[Walmes Marques Zeviani](http://leg.ufpr.br/~walmes)"
output:
  # rmdformats::html_clean:
  rmdformats::readthedown:
    #theme: yeti
    highlight: haddock
    fig_caption: yes
    fig_align: center
---

# Uso de predicados em Xpath

## Predicado para Xpath

  * Predicados permitem criar filtros baseados em propriedades dos
    elementos.
  * Predicados são descritos nos colchetes após o nome do elemento.
    * `//div[<predicado>]`
  * Os predicados podem usar:
    * Índices ou posições.
    * Considerar o conteúdo.
    * Propriedades numéricas.
    * Processamento de strings.
    * Relacionamento entre elementos.

### Índice

É a forma mais simples de uso de predicados Xpath.  Os operadores
relacionais (`=`, `!=`, `<`, `<=`, `>`, `>=`) podem ser usados, bem como
os operadores lógicos `and` e `or` (o `not` será usado adiante).

```{r}
library(xml2)

s <-
'<preferencias>
  <ul type="bebida">
    <li>Cerveja</li>
    <li>Café</li>
    <li>Suco</li>
    <li>Refrigerante</li>
  </ul>
  <ul type="comida">
    <li>Churrasco</li>
    <li>Pizza</li>
    <li>Hamburger</li>
  </ul>
</preferencias>'

doc <- read_xml(s)
xml_structure(doc)
xml_name(doc)
xml_length(doc)

xml_find_all(doc, xpath = "//ul/li[2]")

xml_find_all(doc, xpath = "//ul[1]")
xml_find_all(doc, xpath = "//ul[1]/li[4]")
xml_find_all(doc, xpath = "//ul[2]/li[1]")
xml_find_all(doc, xpath = "//ul/li[position() = 2]")
xml_find_all(doc, xpath = "//ul/li[position() <= 2]")
xml_find_all(doc, xpath = "//ul/li[position() = last()]")
xml_find_all(doc, xpath = "//ul/li[position() = last() - 1]")
xml_find_all(doc, xpath = "//ul/li[position() >= 2 and position() < last()]")
```

### Conteúdo

Considera o conteúdo do elemento. Por exemplo:

  * `//tr[./th]`: pega as linha da tabela `<tr>` (table row) que possuem
    elementos filhos sendo cédulas de cabeçalho `<th>` (table header).
  * `//tr[@class]`: pega as linhas da tabela que possuem o atributo `class`.
  * `//tr[@class='even']`: pega as linhas da tabela que possuem o
    atributo `class` com valor igual a `even` (linha ímpar).

```{r}
s <-
'<table>
  <tr>
    <th>Firstname</th>
    <th>Lastname</th>
    <th>Age</th>
  </tr>
  <tr class="odd">
    <td>Jill</td>
    <td>Smith</td>
    <td>50</td>
  </tr>
  <tr class="even">
    <td>Eve</td>
    <td>Jackson</td>
    <td>94</td>
  </tr>
</table>'

doc <- read_xml(s)
doc %>% xml_structure()
doc %>% xml_name()
doc %>% xml_length()

xml_find_all(doc, xpath = "//tr[./th]")
xml_find_all(doc, xpath = "//tr[@class]")
xml_find_all(doc, xpath = "//tr[@class = 'even']")

xml_find_all(doc, xpath = "//tr[not(@class)]")
xml_find_all(doc, xpath = "//tr[@class != 'even']")
xml_find_all(doc, xpath = "//tr[not(@class) or @class != 'even']")
```

### Mais exemplos

```{r, eval = FALSE}
s <-
'<body>
  <div type="orçamento">
    <p>
      Essa sobremesa custa <span class="valor">10 reais</span>
      e serve <span class="quantidade">6 pessoas</span>.
    </p>
  </div>
  <div type="sobremesa">
    <ul class="ingredientes">
      <li>3 ovos.</li>
      <li>150 ml de leite.</li>
      <li>3 colheres de manteiga.</li>
      <li>Massa preparada de bolo.</li>
    </ul>
    <ul class="preparo">
      <li>Deixe o forno preaquecer por 20 min a 180 graus.</li>
      <li>Bata os ingredientes até uniformizar.</li>
      <li>Unte a forma.</li>
      <li>Despeje na forma.</li>
      <li>Leve ao fogo por aproximadamente 1 hora.</li>
    </ul>
  </div>
</body>'

doc <- read_xml(s)
xml_structure(doc)
xml_name(doc)
xml_length(doc)

xml_find_all(doc, xpath = "//span")
xml_find_all(doc, xpath = "//span[@class = 'valor']")

xml_find_all(doc, xpath = "//ul[2]")
xml_find_all(doc, xpath = "//ul/li[2]")
xml_find_all(doc, xpath = "//ul/li[position() = 2]")
xml_find_all(doc, xpath = "//ul/li[last()]")
xml_find_all(doc, xpath = "//ul/li[position() = last()]")
xml_find_all(doc, xpath = "//ul/li[position() != last()]")
xml_find_all(doc, xpath = "//ul/li[position() < last()]")
xml_find_all(doc, xpath = "//ul[count(li) >= 3]")
xml_find_all(doc, xpath = "//ul[@class]")
xml_find_all(doc, xpath = "//ul[@class = 'preparo']")
xml_find_all(doc, xpath = "//ul[@class != 'preparo']")
xml_find_all(doc, xpath = "//ul[not(@class = 'preparo')]")
xml_find_all(doc, xpath = "//ul[@class = 'ingredientes' or @class = 'preparo']")
```

### Parentesco

A especificação do parentesco é feito usando uma palavra-chave antes do
nome dos elementos. As palavras são

  * `self`: O próprio elemento.
  * `parent`: O elemento pai.
  * `ancestor`: Todos os ancestrais.
  * `ancestor-or-self`: O elemento e todos os ancestrais.
  * `child`: Os filhos.
  * `descendant`: Todos os descendentes.
  * `descendant-or-self`: O elemento e todos os dencendentes.
  * `following`: Todos os elementos de mesmo nível que estão à frente.
  * `following-sibling`: Os próximos elemento de mesmo nível à frente
    (irmão mais novo) com mesmo pai.
  * `preceding`: Todos os elementos de mesmo nível que estão atrás.
  * `preceding-sibling`: Os elementos anteriores de mesmo nível (irmão
     mais velho) com mesmo pai.

```{r}
# Aponta para todos os <li> filhos do primeiro <ul>.
xml_find_all(doc, xpath = "//ul[1]/li")
xml_find_all(doc, xpath = "//ul[1]/child::li")

# Aponta para todos os <ul> que tem algum filho <li>.
xml_find_all(doc, xpath = "//li/parent::ul")
xml_find_all(doc, xpath = "//ul[li]")

# Aponta para todos os <li> que descendem de <div>.
xml_find_all(doc, xpath = "//div//li")
xml_find_all(doc, xpath = "//div/descendant::li")

# Aponta para todos <ul> que são filho de <div>.
xml_find_all(doc, xpath = "//div/ul")
xml_find_all(doc, xpath = "//div/child::ul")

# ATTENTION: Não são equivalentes.
xml_find_all(doc, xpath = "//li/ancestor::div")   # Aponta para o <div>.
xml_find_all(doc, xpath = "//div/descendant::li") # Aponta para os <li>.
xml_find_all(doc, xpath = "//div//li")            # Aponta para os <li>.

# Apontam para todos <li> após o 2 <li> de mesmo pai.
xml_find_all(doc, xpath = "//ul[1]/li[2]/following-sibling::li")
xml_find_all(doc, xpath = "//ul[1]/li[position() > 2]")

# Apontam para todos <li> antes do penúltimo <li> de mesmo pai.
xml_find_all(doc, xpath = "//ul[1]/li[last() - 1]/preceding-sibling::li")
xml_find_all(doc, xpath = "//ul[1]/li[position() < last() - 1]")

# Apontam para o <li> irmão logo após o 2 <li> de mesmo pai.
xml_find_all(doc, xpath = "//ul[1]/li[2]/following-sibling::li[1]")
xml_find_all(doc, xpath = "//ul[1]/li[position() = 3]")

# Apontam para o <li> irmão logo antes do penúltimo <li> de mesmo pai.
xml_find_all(doc, xpath = "//ul[1]/li[last() - 1]/preceding-sibling::li[1]")
xml_find_all(doc, xpath = "//ul/li[last() - 1]/preceding-sibling::li[1]")
xml_find_all(doc, xpath = "//ul[1]/li[position() = last() - 2]")

# ATTENTION: Não são equivalentes.
xml_find_all(doc, xpath = "//ul/li[last() - 1]/following-sibling::li")
xml_find_all(doc, xpath = "//ul/li[last() - 1]/following::li")
```

### Representadores e operadores de string

```{r}
s <-
'<div>
  <div>
    <h1>Web Scraping</h1>
    <h2>Captura e processamento e páginas Web</h2>
  </div>
  <div>
    <h3>Links importantes</h3>
    <ul>
      <li>
        <a href="extras/regex.html">Clique aqui</a>
      </li>
      <li>
        <a href="extras/json.html"></a>
      </li>
    </ul>
    <img src="images/xpath-esquema.png"/>
  </div>
</div>'

doc <- read_xml(s)
xml_structure(doc)
xml_name(doc)
xml_length(doc)

xml_find_all(doc, xpath = "//a[text()]")
xml_find_all(doc, xpath = "//a[text() = 'Clique aqui']")
xml_find_all(doc, xpath = "//a[. = 'Clique aqui']")
xml_find_all(doc, xpath = "//a[. = '']")
xml_find_all(doc, xpath = "//a[starts-with(text(), 'Clique')]")
xml_find_all(doc, xpath = "//a[contains(text(), 'aqui')]")
xml_find_all(doc, xpath = "//a[contains(., 'aqui')]")
xml_find_all(doc, xpath = "//img[contains(@src, '.png')]")
xml_find_all(doc, xpath = "//a[contains(@href, '.html')]")
xml_find_all(doc, xpath = "//div/*[starts-with(name(), 'h')]")
```
```{r, eval = FALSE}
# A libxml2 usa XPath 1.0 que não possui a função ends-with() que é da
# versão 2.0.
xml_find_all(doc, xpath = "//img[ends-with(@src, '.png')]")
```

### Extratores

```{r}
xml_find_all(doc, xpath = "//a")
xml_find_all(doc, xpath = "//a/text()")
xml_find_all(doc, xpath = "//a/@href")
xml_find_all(doc, xpath = "//a/attribute::href")
xml_find_all(doc, xpath = "//ul/*")
xml_find_all(doc, xpath = "//ul/child::node()")
xml_find_all(doc, xpath = "//ul/child::node()/a")
xml_find_all(doc, xpath = "//ul/*/a")
```

# Informações da sessão

```{r, echo = FALSE}
cat(format(Sys.time(), format = "%A, %d de %B de %Y, %H:%M"),
    "----------------------------------------", sep = "\n")
devtools::session_info()
```
