#-----------------------------------------------------------------------
#                                            Prof. Dr. Walmes M. Zeviani
#                                leg.ufpr.br/~walmes · github.com/walmes
#                                        walmes@ufpr.br · @walmeszeviani
#                      Laboratory of Statistics and Geoinformation (LEG)
#                Department of Statistics · Federal University of Paraná
#                                       2019-abr-21 · Curitiba/PR/Brazil
#-----------------------------------------------------------------------

#-----------------------------------------------------------------------
# Carrega os módulos.

import os
os.getcwd()

# Instalar com `anaconda3/bin/pip install requests`
import requests as rqst
import webbrowser as wb

# Para fazer o exame (parse) e a extração de conteúdo.
from bs4 import BeautifulSoup

# Caminho para o diretório de pacotes.
import site
site.getsitepackages()

# Estrutura do módulo.
os.system("tree -L 2 " + site.getsitepackages()[0] + "/bs4")

# NOTE: instale o `tree` no Linux com `sudo apt-get install tree`.

# Para consultar métodos a atributos de um objeto/classe.
from see import see

#-----------------------------------------------------------------------
# Importação e exame de arquivo XML.

# Arquivo XML com catálogo botânico.
url = "http://www.w3schools.com/Xml/plant_catalog.xml"
wb.open(url)

r = rqst.get(url)
r.status_code
print(r.text)

# Faz o parsing.
page = BeautifulSoup(markup = r.text)

type(page)   # Classe de objeto.
dir(page)    # Métodos e atributos.
page         # Exibe o documento.
page.builder # Parser.

# Só o conteúdo dos nós.
print(page.text)

# Exibição arrumadinha para exame da estrutura.
print(page.prettify())

# NOTE: nome das tags foram para caixa baixa.

# Exibe os métodos e atributos do objeto.
see(page)

#-----------------------------------------------------------------------
# Selecionar elementos da árvore.

# Busca pelos elementos pelo nome da tag.
page.find(name = "plant")
page.find_all(name = "plant")
page.find_all(name = "plant", limit = 3)

u = page.find_all(name = "common")

type(u)
type(u[0])

see(u)    # Nada mais que uma lista.
see(u[0]) # Objeto de classe Tag.

# Extrai o nome comum.
[node.text for node in u]

# Pode usar com uma lista. Elementos estarão na ondem em que aparecem.
page.find_all(name = ["common", "price"], limit = 4)

# QUESTION: Como manter as tuplas integras/agrupadas?

#-----------------------------------------------------------------------
# Iterar sobre o conteúdo fazendo extração/organização.

plants = page.find_all(name = "plant")
len(plants)
plants[:2]

# Varredura com compreensão de listas.
tup = [node.find_all(["common", "price"], limit = 2) for node in plants]
tup[0][0].text
tup[0][1].text

# Crescendo em sofisticação.
[node[0].text for node in tup]
[node[1].text for node in tup]
[[node[0].text, node[1].text] for node in tup]
[[el.text for el in node] for node in tup]

#-----------------------------------------------------------------------
# Mais manipulação.

print(page.prettify()[:400])

# O nó raiz.
page.name

# Navegação pelos nós.
page.html.catalog
page.html.catalog.plant
page.html.catalog.plant.common
page.html.catalog.plant.common.text

# Cria uma árvore com o `<catalog>` como raíz.
u = page.find("catalog")
u.name

# Nome de cada elemento. ATTENTION: elemento `None` aparece.
[i.name for i in u.children]

# Primeiro nó filho, útil quando o nome e desconhecido.
u.findChild().name
u.findChild().findChild().name

# QUESTION: é possível iterar nas plantas do catálogo criando um
# dicionário?

# u está apontando para `<catalog>`. Duas formas de pegar o primeiro.
plant = u.findChild()
plant = u.find("plant")
len(plant)

[x.name for x in plant]  # PROBLEM: Surge um None.
[type(x) for x in plant] # EXPLANATION: Objetos de classes diferentes.

# Compreensão de lista com condicional. Duas opções úteis.
[x.name for x in plant if not isinstance(x, str)]
[x.name for x in plant if not x.name is None]

# Criando uma lista de dicionários de tamanho 1.
[{x.name: x.text} for x in plant if not x.name is None]

# Cria o dicionário a partir de coerção de lista de tuplas de 2 valores.
dict([(x.name, x.text) for x in plant if not x.name is None])

# Solução mais rápida para esse caso.
plant_child = u.findChild().findChildren()
dict([(x.name, x.text) for x in plant_child])

#-----------------------------------------------------------------------
#-----------------------------------------------------------------------
# Lendo eventos climáticos. O XML tem maior profundidade. Dados de
# interesse mais esparramados vertical e horizontalmente.

# URL dos serviços da APIs.
url_count = "https://earthquake.usgs.gov/fdsnws/event/1/count?"
url_query = "https://earthquake.usgs.gov/fdsnws/event/1/query?"

# Parâmetros para um dia de eventos sísmicos.
params = {"format"   : "xml",
          "starttime": "2018-02-02T00:00:00",
          "endtime"  : "2018-02-02T00:30:00"} # TODO

r = rqst.get(url_count, params)
r.text

r = rqst.get(url_query, params)
print(r.text[:1000])

# Cria a estrutura de ávore.
page = BeautifulSoup(markup = r.text)
see(page)

print(page.prettify()[:1000])

# Nó raíz.
page.name

# Iterador para os nós filhos do raíz.
page.children

# Descendo na árvore.
[x.name for x in page]
[x.name for x in page.html]
[x.name for x in page.html.body]
[x.name for x in page.html.body.findChild()]
[x.name for x in page.html.body.findChild().findChild()]

# `html` é primeiro nó do documento.
page.html.name
page.html.findChild().name
page.html.body.findChild().findChild().name
page.html.body.findChild().findChild()

# Para extrair as informações dos eventos.
event = page.find("event")
event.name

len(event)
[x.name for x in event.children]
[len(x) for x in event.children]

print(event.origin.prettify())
print(event.magnitude.prettify())

# Extração individual com caminhos absolutos.
event.origin.time.value.text
event.origin.longitude.value.text
event.origin.latitude.value.text
event.origin.depth.value.text
event.magnitude.mag.value.text

# OBS: usar `.value.text` e apenas `.text` resulta na mesma coisa. É
# importante existir equilíbrio entre precisão e robustez ao fazer as
# consultas. É a mesma lógica de expressões regulares.

# Campos para consulta dos valores.
fields = ["time", "longitude", "latitude", "depth", "mag"]
event_vals = event.find_all(fields)
len(event_vals)

# Dicionário com as variáveis buscadas.
dict([(x.name, x.text) for x in event_vals])

# Função para aplicar todos os eventos com compreensão de listas.
def extract_event(event, fields):
    """
    event: nó da árvore.
    fields: lista de subnós para extração do valor.
    """
    event_vals = event.find_all(fields)
    return dict([(x.name, x.text) for x in event_vals])

# Lista com todos os eventos.
events = page.find_all("event")
len(events)

# Extração das informações de cada evento.
[extract_event(x, fields) for x in events]

#-----------------------------------------------------------------------
# Outras formas de seleção.

page.find_all(name = "value")   # Nome do elemento.
page.select(selector = "value") # Seletor CSS.

# Seleção com CSS.
page.select(selector = "mag value")
page.select(selector = "magnitude mag")
page.select(selector = "magnitude mag value")
page.select(selector = "magnitude value")
page.select(selector = "magnitude > value")

# ATTENTION: o BeatifulSoup não suporta expressões XPath. Na realidade,
# as expressões de seleção CSS são traduzidas para XPath que são, então,
# passadas para baixo (`lxml`). As expressões de seleção CSS permitem
# seleções bem acuradas. Nem todas as expressões de seleção CSS que
# navegador entende/aplica são válidas na BeatifulSoup.

#-----------------------------------------------------------------------
# Mais um exemplo com XML.

# Tokens de acesso para algumas APIs.
import json
with open("tokens.json") as token_file:
    tokens = json.load(token_file)

url = "http://www.omdbapi.com/"
params = {"apikey": tokens.get("omdbapi"),
          "s": "Avengers",
          "r": "xml"}
params

r = rqst.get(url, params)
r.url
print(r.text)

page = BeautifulSoup(markup = r.text)
print(page.prettify())

# NOTE: a informação está na forma de atributo de elemento.

# Extração de todos atributos na forma de dicionário.
page.find("result").attrs

# Extração de um atributo.
page.find("result").get("title")
page.find("result")["title"]

# Seleção dos atributos desejados.
[page.find("result").attrs[x] for x in ["title", "year"]]

# Seleção com filtro nos atributos.
page.find_all("result", attrs = {"title": "The Avengers"})
page.find_all("result", attrs = {"type": "movie"})

# Aplicando para todos os resultados da busca.
results = page.find_all("result", attrs = {"type": "movie"})
len(results)

# Extração do título e ano de cada filme.
[[res[attr] for attr in ["title", "year"]] for res in results]

# Link para a próxima página com resultados para iterar.
params = {"apikey": tokens.get("omdbapi"),
          "s": "Avengers",
          "r": "xml",
          "page": "2"}
params

r = rqst.get(url, params)
r.text

#-----------------------------------------------------------------------
