@Override protected Boolean doInBackground(String... params) { try { Document doc = Jsoup.connect(params[0]).get(); Element body = doc.body(); Elements titleEs = body.select("td.title"); Elements subTitleEs = body.select("td.subtext"); int index = 1; if (!titleEs.isEmpty()) { if (mType == TYPE_REFRESH && mNews.size() > 0) { mNews.clear(); } Iterator<Element> iterator = titleEs.iterator(); Iterator<Element> subIt = subTitleEs.iterator(); NewEntity entity = null; User user = null; while (iterator.hasNext()) { Element e = iterator.next(); if (index % 2 == 0) { Element subE = subIt.next(); Elements aTag = e.select("a"); Elements spanTag = e.select("span.comhead"); Elements subEa = subE.select("a"); user = new User(); user.setId(subEa.get(0).text()); entity = new NewEntity( aTag.get(0).attr("href"), aTag.get(0).text(), spanTag.isEmpty() ? null : spanTag.get(0).text(), subE.html()); entity.setDiscussUrl(subEa.get(1).attr("href")); // Log.i(LOG_TAG, entity.toString()); mNews.add(entity); } index++; } } Elements more = doc.getElementsByAttributeValueStarting("href", "/x?fnid="); if (!more.isEmpty()) { mMoreURLPath = more.get(1).attr("href"); } return true; } catch (IOException e) { Log.e(LOG_TAG, "", e); return false; } }
@Scheduled(fixedDelay = 900000) public void loadBooksInfo() { int booksListPageNumber = 1; while (true) { Document document = null; try { URL url = new URL( "http://www.labirint.ru/genres/2308/?page=" + Integer.toString(booksListPageNumber++)); document = Jsoup.parse(url, 5000); } catch (MalformedURLException ex) { } catch (IOException ex) { } Elements elements = document.getElementsByClass("product"); if (elements.size() == 0) break; Iterator<Element> iterator = elements.iterator(); while (iterator.hasNext()) { try { bookDao.saveBook(bookInfo(iterator.next())); } catch (Exception ex) { if (ex instanceof ConstraintViolationException) continue; } } } }
// Busca os endereços pelo número do CEP. public List<Address> getByCep(String cep) throws IOException { listEnderecos = new ArrayList<Address>(); // mapeamento dos parametros que será passado na requisição Map<String, String> query = new HashMap<String, String>(); query.put("CEP", cep); query.put("Metodo", "listaLogradouro"); query.put("TipoConsulta", "cep"); query.put("StartRow", "1"); query.put("EndRow", "10"); // Faz uma requisição no site do correios (www.buscacep.com.br) com Json, passando os parametros // mapeados, // requisição deverá ser do tipo post. // Armazena o retorno em uma variavel doc. Document doc = Jsoup.connect(Utils.adressCorreios) .data(query) .header("Origin", "http://www.buscacep.correios.com.br") .header("Referer", "http://www.buscacep.correios.com.br") .post(); // Acessa o retorno do doc e percorre o resultado buscando as informações dos endereços // Armazena os resultados na lista de endereços criadas e retorna a mesma para que outras // classes possam acessar. Elements elements = doc.select("table").eq(2); Elements rows = elements.select("tr"); Iterator<Element> rowIterator = rows.iterator(); while (rowIterator.hasNext()) { Address enderecos = new Address(); Element element = rowIterator.next(); Elements logradouro = element.children().select("td").eq(0); enderecos.setLogradouro(logradouro.text()); Elements bairro = element.children().select("td").eq(1); enderecos.setBairro(bairro.text()); Elements cidade = element.children().select("td").eq(2); Elements estado = element.children().select("td").eq(3); StringBuilder sbLocalidade = new StringBuilder(); sbLocalidade.append(cidade.text()); sbLocalidade.append("/"); sbLocalidade.append(estado.text()); enderecos.setLocalidade(sbLocalidade.toString()); Elements codigopostal = element.children().select("td").eq(4); enderecos.setCEP(codigopostal.text()); listEnderecos.add(enderecos); } return listEnderecos; }
private List<Comment> parseComments(Element commentsElement, Book book) { List<Comment> comments = new ArrayList<Comment>(); Elements elements = commentsElement.getElementsByClass("product-comment"); Iterator<Element> iterator = elements.iterator(); String str = ""; while (iterator.hasNext()) { Comment comment = new Comment(); Element element = iterator.next(); try { str = element .getElementsByClass("comment-user-avatar") .get(0) .getElementsByTag("a") .get(0) .attr("title"); comment.setAuthor((str.length() <= 100) ? str : str.substring(0, 99)); } catch (Exception ex) { comment.setAuthor(""); } try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.mm.yyyy HH:mm:ss"); str = element .getElementsByClass("comment-footer") .get(0) .getElementsByClass("date") .get(0) .text() .trim(); comment.setDate(new Date(simpleDateFormat.parse(str).getTime())); } catch (Exception ex) { comment.setDate(null); } try { if (element.getElementsByAttributeValueContaining("id", "fullcomment").size() > 0) { str = element.getElementsByAttributeValueContaining("id", "fullcomment").get(0).text(); comment.setComment((str.length() <= 5000) ? str : str.substring(0, 4999)); } else if (element.getElementsByClass("comment-text").size() > 0) { str = element.getElementsByClass("comment-text").get(0).text(); comment.setComment((str.length() <= 5000) ? str : str.substring(0, 4999)); } else { str = element.getElementsByAttributeValueContaining("id", "shortcomment").get(0).text(); comment.setComment((str.length() <= 5000) ? str : str.substring(0, 4999)); } } catch (Exception ex) { comment.setComment(""); } comment.setBook(book); comments.add(comment); } return comments; }
/** * Extract content with jsoup maybe later. * * @param doc * @return */ public static List<Item> extractItem(Document doc) { List<Item> itemList = new ArrayList<Item>(); Elements itemRows = doc.select("tr"); Iterator iterator = itemRows.iterator(); while (iterator.hasNext()) { Element element = (Element) iterator.next(); Element titleElement = element.select(".title a").first(); if (titleElement == null) { continue; } String titleStr = titleElement.text().trim(); String urlStr = titleElement.attr("href").trim(); Element comHeadElement = element.select(".comhead").first(); if (comHeadElement == null) { continue; } String comheadStr = comHeadElement.text().trim(); Element pointsElement = element.select("span[id^=score_]").first(); if (pointsElement == null) { continue; } String pointsStr = pointsElement.text(); if (pointsStr == null) { continue; } String[] pointsArr = pointsStr.split(" "); if (pointsArr.length != 2) { continue; } int points = -1; try { points = Integer.parseInt(pointsArr[0]); } catch (NumberFormatException e) { } if (points < 0) { continue; } Element userElement = element.select("a[href^=user]").first(); if (userElement == null) { continue; } String user = userElement.text().trim(); Element dateElement = element.select(".subtext").first(); } return itemList; }
// Busca o Cep pelo logradouro. public List<String> getByAdress(String address) throws IOException { listAddress = new ArrayList<String>(); // mapeamento dos parametros que será passado na requisição Map<String, String> query = new HashMap<String, String>(); query.put("relaxation", address); query.put("TipoCep", "ALL"); query.put("semelhante", "N"); query.put("cfm", "1"); query.put("Metodo", "listaLogradouro"); query.put("TipoConsulta", "relaxation"); query.put("StartRow", "1"); query.put("EndRow", "10"); // Faz uma requisição no site do correios (www.buscacep.com.br) com Json, passando os parametros // mapeados, // requisição deverá ser do tipo post. // Armazena o retorno em uma variavel doc. Document doc = Jsoup.connect(Utils.adressCorreios) .timeout(20000) .data(query) .header("Origin", "http://www.buscacep.correios.com.br") .header("Referer", "http://www.buscacep.correios.com.br") .post(); // Acessa o retorno do doc e percorre o resultado buscando as informações de Cep de acordo com o // endereço passado. // Armazena os resultados na lista criada e retorna a mesma para que outras classes possam // acessar Elements elements = doc.select("table").eq(2); Elements rows = elements.select("tr"); Iterator<Element> rowIterator = rows.iterator(); while (rowIterator.hasNext()) { Address enderecos = new Address(); Element element = rowIterator.next(); Elements codigopostal = element.children().select("td").eq(4); enderecos.setCEP(codigopostal.text()); listAddress.add(enderecos.getCEP()); } return listAddress; }
static { try { locData = Jsoup.parse(new FileInputStream(LocLabel.locFile), "UTF-8", "", Parser.xmlParser()); locStrList = new LinkedList<String>(); Elements locElements = locData.select("[Name]"); Iterator<Element> eleIterator = locElements.iterator(); while (eleIterator.hasNext()) { Element curElement = eleIterator.next(); locStrList.add(curElement.attr("Name")); } } catch (IOException e) { e.printStackTrace(); } }
private static void alterElement(Element e) { org.jsoup.select.Elements s = e.children(); Iterator<Element> ele = s.iterator(); int i = 0; while (ele.hasNext()) { Element r = ele.next(); if (!r.tag().getName().equals("p")) { r.tagName("p"); // plain replace // Element rtemp = r.clone(); // Element ep = new Element(Tag.valueOf("p"), ""); // ep.appendChild(rtemp); // r.replaceWith(ep); // StringBuffer bf = new StringBuffer(); // bf.append("<k>").append(r.toString()).append("</k>"); // r.html(bf.toString()); // System.out.println(r.tagName()); } i++; } }
public boolean scrapInternal(String url, String marker) throws IOException { Document document; try { document = Jsoup.connect(url).get(); } catch (IOException e) { // try again document = Jsoup.connect(url).get(); } Elements hrefElements = document.select("a"); Iterator<Element> eltIterator = hrefElements.iterator(); boolean postFound = false; while (eltIterator.hasNext()) { String href = eltIterator.next().attr("href"); if (StringUtil.isBlank(href)) { continue; } else if (parser.isPostPage(href)) { try { BlogPost blogPost = parser.getRepository().findOneByUrl(href); if (blogPost == null) { scrapBlogPost(href, null, false, marker); postFound = true; } else if (!marker.equals(blogPost.getMarker())) { scrapBlogPost(href, blogPost, true, marker); } } catch (Exception exp) { exp.printStackTrace(); } } else if (parser.isListPage(href) && !summaryQueue.contains(href)) { summaryQueue.add(href); } } return postFound; }
Hashtable<String, String> getFilters(String nodeId) { Hashtable<String, String> sdata = new Hashtable<String, String>(); Elements ttFilterNodes = baseNode.select("#doc #bd-wrap #bd #sidebar .bd #" + nodeId + " .default_show"); for (Iterator<Element> iterator = ttFilterNodes.iterator(); iterator.hasNext(); ) { String key = "", value = ""; Element liElement = (Element) iterator.next(); if (liElement.getElementsByTag("a") != null && liElement.getElementsByTag("a").size() > 0) { Element link = liElement.getElementsByTag("a").get(0); key = link.text().toString(); } else { Element activeNode = liElement.getElementsByClass("active").get(0); key = activeNode.text().toString(); } if (liElement.getElementsByClass("refiner_count") != null && liElement.getElementsByClass("refiner_count").size() > 0) { Element countNode = liElement.getElementsByClass("refiner_count").get(0); value = countNode.text().toString().replace("(", "").replace(")", ""); } sdata.put(key, value); } return sdata; }
public List<Standing> parse() throws IOException { List<Standing> ret = new ArrayList<Standing>(); Database db = Database.getInstance(); Document doc; try { doc = Jsoup.parse(new URL(url), 5000); } catch (Exception ex) { logger.warn("Failed fetching standings at " + url, ex); return ret; } Elements rows = doc.select("div.Section1 table"); Iterator<Element> tableIter = rows.iterator(); tableIter.next(); // skip 2, selector won't work for some reason tableIter.next(); Element table = tableIter.next(); for (Element row : table.select("tr:gt(1)")) { // skip first 2 headers AscentStanding s = (AscentStanding) game.createStanding(); // 0: rank // 1: team # // 2: qualification score // 3: AP (?) // 4: coopertition points // 5: teleop points // 6: record (wins-losses-ties) // 7 disqualifications // 8: matches played s.setRank(Integer.parseInt(row.child(0).text())); Team team = db.getTeam(Integer.parseInt(row.child(1).text())); if (team == null) { logger.warn("Unknown team " + row.child(1).text() + ", skipping"); continue; } s.setTeam(game.getEntry(team)); s.setQualificationScore(Float.parseFloat(row.child(2).text())); // todo: old/invalid! // s.setHybridPoints(Float.parseFloat(row.child(3).text())); // s.setBridgePoints(Float.parseFloat(row.child(4).text())); s.setTeleopPoints(Float.parseFloat(row.child(5).text())); s.setCoopertitionPoints((int) Float.parseFloat(row.child(4).text())); String[] wlt = StringUtils.split(row.child(6).text(), '-'); s.setWins(Integer.parseInt(wlt[0])); s.setLosses(Integer.parseInt(wlt[1])); s.setTies(Integer.parseInt(wlt[2])); s.setDisqualifications(Integer.parseInt(row.child(7).text())); s.setMatchesPlayed(Integer.parseInt(row.child(8).text())); ret.add(s); } return ret; }
public void onPostExecute(AnswerObject result) { Document doc = Jsoup.parse(result.getHTML()); sendHTMLatBug(doc.html()); if (doc.select("span.notLoggedText").text().length() > 0) { Intent BackToLoginIntent = new Intent(this, TuCanMobileActivity.class); BackToLoginIntent.putExtra("lostSession", true); startActivity(BackToLoginIntent); } else { if (PREPCall == false) { String Title = doc.select("h1").text(); TextView SingleEventTitle = (TextView) findViewById(R.id.singleevent_title); SingleEventTitle.setText(Title); Elements Deltarows = doc.select("table[courseid]").first().select("tr"); Element rows; if (Deltarows.size() == 1) { rows = Deltarows.get(0).select("td").first(); } else { rows = Deltarows.get(1).select("td").first(); } Elements Paragraphs = rows.select("p"); Iterator<Element> PaIt = Paragraphs.iterator(); ArrayList<String> titles = new ArrayList<String>(); ArrayList<String> values = new ArrayList<String>(); while (PaIt.hasNext()) { Element next = PaIt.next(); String[] information = crop(next.html()); titles.add(information[0]); values.add(information[1]); } PropertyValueAdapter = new SingleEventAdapter(titles, values); setListAdapter(PropertyValueAdapter); // Termin-Selektor: // Terminselektor Iterator<Element> captionIt = doc.select("caption").iterator(); Iterator<Element> DateTable = null; Iterator<Element> materialTable = null; while (captionIt.hasNext()) { Element next = captionIt.next(); if (next.text().equals("Termine")) { System.out.println(next.parent().html()); DateTable = next.parent().select("tr").iterator(); } else if (next.text().contains("Material")) { materialTable = next.parent().select("tr").iterator(); } } ArrayList<String> eventNumber = new ArrayList<String>(); ArrayList<String> eventDate = new ArrayList<String>(); ArrayList<String> eventTime = new ArrayList<String>(); ArrayList<String> eventRoom = new ArrayList<String>(); ArrayList<String> eventInstructor = new ArrayList<String>(); while (DateTable.hasNext()) { Element next = DateTable.next(); Elements cols = next.select("td"); eventNumber.add(cols.get(0).text()); eventDate.add(cols.get(1).text()); eventTime.add(cols.get(2).text() + "-" + cols.get(3).text()); eventRoom.add(cols.get(4).text()); eventInstructor.add(cols.get(5).text()); } DateAppointmentAdapter = new AppointmentAdapter(eventDate, eventTime, eventNumber, eventRoom, eventInstructor); int ct = 0; ArrayList<String> materialNumber = new ArrayList<String>(); ArrayList<String> materialName = new ArrayList<String>(); ArrayList<String> materialDesc = new ArrayList<String>(); materialLink = new ArrayList<String>(); ArrayList<String> materialFile = new ArrayList<String>(); if (materialTable != null) { while (materialTable.hasNext()) { Element next = materialTable.next(); if (next.select("td").size() > 1) { ct++; System.out.println(ct + " " + (ct % 3)); int mod = (ct % 3); switch (mod) { case 1: materialNumber.add(next.select("td").get(0).text()); materialName.add(next.select("td").get(1).text()); break; case 2: materialDesc.add(next.select("td").get(1).text()); if (next.attr("class").equals("tbdata_nob")) { ct++; materialLink.add(""); materialFile.add(""); } break; case 0: materialLink.add(next.select("td").get(1).select("a").attr("href")); materialFile.add(next.select("td").get(1).select("a").text()); break; } } } } if (ct > 2) { FileAdapter = new AppointmentAdapter( materialNumber, materialFile, null, materialName, materialDesc); thereAreFiles = true; } else FileAdapter = new ArrayAdapter<String>( this, android.R.layout.simple_list_item_1, new String[] {"Kein Material"}); } else { String nextlink = TucanMobile.TUCAN_PROT + TucanMobile.TUCAN_HOST + doc.select("div.detailout").select("a").attr("href"); SimpleSecureBrowser callOverviewBrowser = new SimpleSecureBrowser(this); RequestObject thisRequest = new RequestObject(nextlink, localCookieManager, RequestObject.METHOD_GET, ""); PREPCall = false; callOverviewBrowser.execute(thisRequest); } } }
public DataRecipe getRecipeData() { DataRecipe output = new DataRecipe(); Hashtable<String, String> sdata; int i = 0; // Element baseNodeE = baseNode.select("body").first(); String img; Elements baseNodes = baseNode.select("#doc #bd-wrap #bd #results #cols #left #main #web ol .recipe-article"); for (Iterator<Element> iterator = baseNodes.iterator(); iterator.hasNext(); ) { sdata = new Hashtable<String, String>(); Element divElement = (Element) iterator.next(); Elements imgElements = divElement.select("img"); if (imgElements != null && imgElements.size() > 0) { img = imgElements.get(0).attr("data-src"); int ind = img.indexOf("http", 2); if (-1 != ind) { img = img.substring(ind); } } else { img = "http://illpop.com/img_illust/food/t_rice_m28.jpg"; } sdata.put("image", img); Elements titleLink = divElement.select(".yschttl"); String title = titleLink .get(0) .text() .toString() .replace("<b>", "") .replace("</b>", "") .replace("\n", ""); String url = titleLink.get(0).attr("href"); sdata.put("title", title); int ind = url.indexOf("http", 2); if (-1 != ind) { url = URLDecoder.decode(url.substring(ind)); } sdata.put("URL", url); Elements totalTimeNode = divElement.select(".totaltime .attr-content"); String totaltime = ""; if (totalTimeNode != null && totalTimeNode.size() > 0) { totaltime = totalTimeNode .get(0) .text() .toString() .replace("</span>", "") .replace("<span class=\"num\">", "") .replace("\n", ""); } sdata.put("duration", totaltime); sdata.put("rating", ""); Elements ingredientsNode = divElement.select(".ingredients .attr-content"); String ingredients = ""; if (ingredientsNode != null && ingredientsNode.size() > 0) { ingredients = ingredientsNode.get(0).text().toString(); } sdata.put("ingredients", ingredients); output.put(i++, sdata); } return output; }
protected Book bookInfo(Element bookIcon) { Document document = null; String productId = bookIcon.attr("data-product-id"); try { URL url = new URL("http://www.labirint.ru/books/" + productId); document = Jsoup.parse(url, 5000); } catch (MalformedURLException ex) { } catch (IOException ex) { } Book book = new Book(); Element bookElement = document.getElementById("product"); String[] strs; String str; book.setProductId(Integer.parseInt(productId)); try { String name; name = bookElement.getElementById("product-title").getElementsByTag("h1").text().trim(); book.setName((name.length() <= 150) ? name : name.substring(0, 149)); } catch (Exception ex) { book.setName(""); } try { String author = ""; String editor = ""; Elements authorElements = bookElement.getElementById("product-specs").getElementsByClass("authors"); Iterator<Element> iterator = authorElements.iterator(); while (iterator.hasNext()) { str = iterator.next().text(); if (str.contains("Автор")) { strs = str.split(":"); if (strs.length == 2) author = strs[1].replaceAll("\"", "").trim(); else author = strs[0].replaceAll("\"", "").trim(); } if (str.contains("Редактор")) { strs = str.split(":"); if (strs.length == 2) editor = strs[1].replaceAll("\"", "").trim(); else editor = strs[0].replaceAll("\"", "").trim(); } } book.setAuthor((author.length() <= 100) ? author : author.substring(0, 99)); book.setEditor((editor.length() <= 100) ? editor : editor.substring(0, 99)); } catch (Exception ex) { book.setAuthor(""); book.setEditor(""); } try { String publisherAndYear = ""; str = bookElement.getElementById("product-specs").getElementsByClass("publisher").get(0).text(); strs = str.split(":"); if (strs.length == 2) publisherAndYear = strs[1].replaceAll("\"", "").trim(); else publisherAndYear = strs[0].replaceAll("\"", "").trim(); book.setPublisherAndYear( (publisherAndYear.length() <= 200) ? publisherAndYear : publisherAndYear.substring(0, 199)); } catch (Exception ex) { book.setPublisherAndYear(""); } try { if (bookElement .getElementById("product-specs") .getElementsByClass("buying-pricenew-val-number") .size() > 0) str = bookElement .getElementById("product-specs") .getElementsByClass("buying-pricenew-val-number") .get(0) .text(); else str = bookElement .getElementById("product-specs") .getElementsByClass("buying-price-val-number") .get(0) .text(); book.setPrice(Double.parseDouble(str.length() <= 10 ? str : str.substring(0, 9))); } catch (Exception ex) { book.setPrice(null); } try { String isbn = ""; str = bookElement.getElementById("product-specs").getElementsByClass("isbn").get(0).text(); strs = str.split(":"); if (strs.length == 2) isbn = strs[1].replaceAll("\"", "").trim(); else isbn = strs[0].replaceAll("\"", "").trim(); book.setIsbn((isbn.length() <= 20) ? isbn : isbn.substring(0, 19)); } catch (Exception ex) { book.setIsbn(""); } try { String pagesCount = ""; str = bookElement.getElementById("product-specs").getElementsByClass("pages2").get(0).text(); strs = str.split(":"); if (strs.length == 2) pagesCount = strs[1].replaceAll("\"", "").trim(); else pagesCount = strs[0].replaceAll("\"", "").trim(); book.setPagesCount((pagesCount.length() <= 30) ? pagesCount : pagesCount.substring(0, 29)); } catch (Exception ex) { book.setPagesCount(""); } try { String decor = ""; Document decorHtml = Jsoup.parse(new URL("http://www.labirint.ru/ajax/design/" + productId), 5000); book.setDecor( (decorHtml.text().length() <= 400) ? decorHtml.text() : decorHtml.text().substring(0, 399)); } catch (Exception ex) { book.setDecor(""); } try { String weight = ""; str = bookElement.getElementById("product-specs").getElementsByClass("weight").get(0).text(); strs = str.split(":"); if (strs.length == 2) weight = strs[1].replaceAll("\"", "").trim(); else weight = strs[0].replaceAll("\"", "").trim(); book.setWeight((weight.length() <= 20) ? weight : weight.substring(0, 19)); } catch (Exception ex) { book.setWeight(""); } try { String dimensions = ""; str = bookElement .getElementById("product-specs") .getElementsByClass("dimensions") .get(0) .text(); strs = str.split(":"); if (strs.length == 2) dimensions = strs[1].replaceAll("\"", "").trim(); else dimensions = strs[0].replaceAll("\"", "").trim(); book.setDimensions((dimensions.length() <= 30) ? dimensions : dimensions.substring(0, 29)); } catch (Exception ex) { book.setDimensions(""); } try { if (bookElement.getElementById("fullannotation") != null) book.setAnnotation( (bookElement.getElementById("fullannotation").text().length() <= 2000) ? bookElement.getElementById("fullannotation").text() : bookElement.getElementById("fullannotation").text().substring(0, 1999)); else if (bookElement.getElementById("product-about") != null) book.setAnnotation( (bookElement.getElementById("product-about").text().length() <= 2000) ? bookElement.getElementById("product-about").text() : bookElement.getElementById("product-about").text().substring(0, 1999)); else book.setAnnotation( (bookElement.getElementById("smallannotation").text().length() <= 2000) ? bookElement.getElementById("smallannotation").text() : bookElement.getElementById("smallannotation").text().substring(0, 1999)); } catch (Exception ex) { book.setAnnotation(""); } try { String imageUrl = ""; if (document.getElementsByAttributeValue("property", "og:image") != null) imageUrl = document.getElementsByAttributeValue("property", "og:image").get(0).attr("content"); else imageUrl = "http://img.labirint.ru/design/emptycover.png"; book.setCoverImgUrl(imageUrl); } catch (Exception ex) { book.setCoverImgUrl(""); } if (document.getElementById("product-comments") != null) book.setComments(parseComments(document.getElementById("product-comments"), book)); return book; }