use crate::{ error::*, logic::plain_parse_elem as plain_parse, models::{ search::{ Paginated, character::CharacterSearchItem, }, character::GrandCompanyInfo, } }; use ffxiv_types::World; use scraper::{Html, ElementRef}; use url::Url; use std::str::FromStr; selectors!( ITEM_ENTRY => ".ldst__window .entry"; ITEM_ID => ".entry__link"; ITEM_FACE => ".entry__chara__face > img"; ITEM_NAME => ".entry__name"; ITEM_WORLD => ".entry__world"; ITEM_GRAND_COMPANY => ".entry__chara_info .js__tooltip"; ITEM_FREE_COMPANY => ".entry__freecompany__link"; ); pub fn parse(html: &str) -> Result> { let html = Html::parse_document(html); let pagination = crate::logic::search::parse_pagination(&html)?; // has results but requested an invalid page if pagination.total_results != 0 && pagination.current_page == 0 { return Err(Error::InvalidPage(pagination.total_pages)); } let results: Vec = html .select(&*ITEM_ENTRY) .map(parse_single) .collect::>()?; Ok(Paginated { pagination, results, }) } pub(crate) fn parse_single(html: ElementRef) -> Result { let id = parse_id(html)?; let name = plain_parse(html, &*ITEM_NAME)?; let world = parse_world(html)?; let grand_company = parse_grand_company(html)?; let free_company_id = parse_free_company_id(html)?; let face = parse_face(html)?; Ok(CharacterSearchItem { id, name, world, grand_company, free_company_id, face, }) } fn parse_id(html: ElementRef) -> Result { let e = html .select(&*ITEM_ID) .next() .ok_or_else(|| Error::missing_element(&*ITEM_ID))?; crate::logic::parse_id(e.value()) } fn parse_world(html: ElementRef) -> Result { let parts_str = plain_parse(html, &*ITEM_WORLD)?; let mut parts = parts_str.split(" ["); let world_str = parts.next() .ok_or_else(|| Error::invalid_content("world with data centre in parens", Some(&parts_str)))?; World::from_str(world_str) .map_err(|_| Error::invalid_content("valid world", Some(&world_str))) } fn parse_free_company_id(html: ElementRef) -> Result> { let elem = match html .select(&*ITEM_FREE_COMPANY) .next() { Some(e) => e, None => return Ok(None), }; crate::logic::parse_id(elem.value()).map(Some) } fn parse_grand_company(html: ElementRef) -> Result> { let text = html .select(&*ITEM_GRAND_COMPANY) .next() .and_then(|x| x.value().attr("data-tooltip")); let text = match text { Some(t) => t, None => return Ok(None), }; crate::logic::parse_grand_company(text).map(Some) } fn parse_face(html: ElementRef) -> Result { let face_elem = html .select(&*ITEM_FACE) .next() .ok_or_else(|| Error::missing_element(&*ITEM_FACE))?; let src = face_elem .value() .attr("src") .ok_or_else(|| Error::invalid_content("src on face img element", None))?; Url::from_str(src).map_err(Error::InvalidUrl) }