/* * Copyright 2015, 2016 Anael Mobilia * * This file is part of NextINpact-Unofficial. * * NextINpact-Unofficial is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NextINpact-Unofficial is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NextINpact-Unofficial. If not, see <http://www.gnu.org/licenses/> */ package com.pcinpact.parseur; import android.util.Log; import com.pcinpact.R; import com.pcinpact.items.ArticleItem; import com.pcinpact.items.CommentaireItem; import com.pcinpact.utils.Constantes; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.parser.Tag; import org.jsoup.select.Elements; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashSet; import java.util.TimeZone; /** * Parseur du code HTML. * * @author Anael */ public class ParseurHTML { /** * Parse la liste des articles. * * @param unContenu contenu HTML brut * @param urlPage URL de la page * @return liste d'articleItem */ public static ArrayList<ArticleItem> getListeArticles(final String unContenu, final String urlPage) { ArrayList<ArticleItem> mesArticlesItem = new ArrayList<>(); // Lancement du parseur sur la page Document pageNXI = Jsoup.parse(unContenu, urlPage); // Les articles Elements lesArticles = pageNXI.select("article[data-acturowid][data-datepubli]"); ArticleItem monArticleItem; // Pour chaque article for (Element unArticle : lesArticles) { monArticleItem = new ArticleItem(); // ID de l'article monArticleItem.setId(Integer.valueOf(unArticle.attr("data-acturowid"))); // Date de publication de l'article String laDate = unArticle.attr("data-datepubli"); monArticleItem.setTimeStampPublication(convertToTimeStamp(laDate, Constantes.FORMAT_DATE_ARTICLE)); // URL de l'illustration Element image = unArticle.select("img[class=ded-image]").get(0); monArticleItem.setUrlIllustration(image.absUrl("data-frz-src")); // URL de l'article Element url = unArticle.select("h1 > a[href]").get(0); monArticleItem.setUrl(url.absUrl("href")); // Titre de l'article (liée à l'URL) monArticleItem.setTitre(url.text()); // Sous titre Element sousTitre = unArticle.select("span[class=soustitre]").get(0); // Je supprime le "- " en début du sous titre String monSousTitre = sousTitre.text().substring(2); monArticleItem.setSousTitre(monSousTitre); // Nombre de commentaires Element commentaires = unArticle.select("span[class=nbcomment]").get(0); try { monArticleItem.setNbCommentaires(Integer.valueOf(commentaires.text())); } catch (NumberFormatException e) { // Nouveaux commentaires : "172 + 5" String valeur = commentaires.text(); // Récupération des éléments int positionOperateur = valeur.indexOf("+"); String membreGauche = valeur.substring(0, positionOperateur).trim(); String membreDroit = valeur.substring(positionOperateur + 1).trim(); // On additionne int total = Integer.valueOf(membreGauche) + Integer.valueOf(membreDroit); // Et on renvoit ! monArticleItem.setNbCommentaires(total); // DEBUG if (Constantes.DEBUG) { Log.w("ParseurHTML", "getListeArticles() - Nombre de commentaires : " + valeur + " => " + total); } } // Statut abonné Elements badgeAbonne = unArticle.select("img[alt=badge_abonne]"); // Ai-je trouvé des éléments ? if (badgeAbonne.size() > 0) { monArticleItem.setAbonne(true); // DEBUG if (Constantes.DEBUG) { Log.w("ParseurHTML", "getListeArticles() - [abonné] => " + monArticleItem.getTitre()); } } else { monArticleItem.setAbonne(false); } // Et je le stocke mesArticlesItem.add(monArticleItem); } return mesArticlesItem; } /** * Parse le contenu d'un article. * * @param unContenu contenu HTML brut * @param urlPage URL de la page * @return ArticleItem */ public static ArticleItem getArticle(final String unContenu, final String urlPage) { ArticleItem monArticleItem = new ArticleItem(); // Lancement du parseur sur la page Document pageNXI = Jsoup.parse(unContenu, urlPage); // L'article Elements lArticle = pageNXI.select("article"); // L'ID de l'article Element articleID = pageNXI.select("div[class=actu_content][data-id]").get(0); int unID = Integer.valueOf(articleID.attr("data-id")); monArticleItem.setId(unID); // Suppression des éléments non requis try { // Image article Element monElement = pageNXI.select("article > section").get(0); monElement.remove(); // Légende image article monElement = pageNXI.select("article > div[class=thumb-cat-container]").get(0); monElement.remove(); // Temps de lecture monElement = pageNXI.select("div[class=read-time]").get(0); monElement.remove(); // Image auteur monElement = pageNXI.select("div[class=infos-article] > div > img").get(0); monElement.remove(); } catch (Exception e) { // DEBUG if (Constantes.DEBUG) { Log.e("ParseurHTML", "getArticle() - Nettoyage article", e); } } // Suppression des liens sur les images (zoom, avec dl) Elements lesImagesLiens = lArticle.select("a[href] > img"); // Set assure l'unicité de la balise (ex : <a...> <img... /> <img... /> </a>) HashSet<Element> baliseA = new HashSet<>(); // Récupération de toutes les balises <a...> avant <img...> for (Element uneImage : lesImagesLiens) { // J'enregistre le lien <a...> baliseA.add(uneImage.parent()); } // Pour chaque balise <a...> for (Element uneBalise : baliseA) { // On prend chacun de ses enfants for (Element unEnfant : uneBalise.children()) { // Et on l'injecte après la balise <a...> uneBalise.after(unEnfant); } // On supprime la balise <a...> uneBalise.remove(); } // Gestion des iframe Elements lesIframes = lArticle.select("iframe"); // généralisation de l'URL en dehors du scheme String[] schemes = { "https://", "http://", "//" }; // Pour chaque iframe for (Element uneIframe : lesIframes) { // URL du lecteur String urlLecteur = uneIframe.attr("src"); for (String unScheme : schemes) { if (urlLecteur.startsWith(unScheme)) { // Suppression du scheme urlLecteur = urlLecteur.substring(unScheme.length()); // DEBUG if (Constantes.DEBUG) { Log.w("ParseurHTML", "getArticle() - Iframe : utilisation du scheme " + unScheme + " => " + urlLecteur); } } } // ID de la vidéo String idVideo = urlLecteur.substring(urlLecteur.lastIndexOf("/") + 1).split("\\?")[0].split("#")[0]; // Ma substitution Element monRemplacement = new Element(Tag.valueOf("div"), ""); // Gestion des lecteurs vidéos if (urlLecteur.startsWith("www.youtube.com/embed/videoseries")) { /** * Liste de lecture Youtube */ // Recalcul de l'ID de la vidéo (cas particulier) idVideo = urlLecteur.substring(urlLecteur.lastIndexOf("list=") + "list=".length()).split("\\?")[0].split("#")[0]; monRemplacement.html("<a href=\"http://www.youtube.com/playlist?list=" + idVideo + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_liste_youtube + "\" /></a>"); } else if (urlLecteur.startsWith("www.youtube.com/embed/") || urlLecteur.startsWith( "www.youtube-nocookie.com/embed/")) { /** * Youtube */ monRemplacement.html("<a href=\"http://www.youtube.com/watch?v=" + idVideo + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_youtube + "\" /></a>"); } else if (urlLecteur.startsWith("www.dailymotion.com/embed/video/")) { /** * Dailymotion */ monRemplacement.html("<a href=\"http://www.dailymotion.com/video/" + idVideo + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_dailymotion + "\" /></a>"); } else if (urlLecteur.startsWith("player.vimeo.com/video/")) { /** * VIMEO */ monRemplacement.html( "<a href=\"http://www.vimeo.com/" + idVideo + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_vimeo + "\" /></a>"); } else if (urlLecteur.startsWith("static.videos.gouv.fr/player/video/")) { /** * Videos.gouv.fr */ monRemplacement.html("<a href=\"http://static.videos.gouv.fr/player/video/" + idVideo + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_videos_gouv_fr + "\" /></a>"); } else if (urlLecteur.startsWith("vid.me")) { /** * Vidme */ monRemplacement.html("<a href=\"https://vid.me/" + idVideo + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_vidme + "\" /></a>"); } else if (urlLecteur.startsWith("w.soundcloud.com/player/")) { /** * Soundcloud (l'URL commence bien par w.soundcloud !) */ monRemplacement.html("<a href=\"" + urlLecteur + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_soundcloud + "\" /></a>"); } else if (urlLecteur.startsWith("www.scribd.com/embeds/")) { /** * Scribd */ monRemplacement.html("<a href=\"" + urlLecteur + "\"><img src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_scribd + "\" /></a>"); } else if (urlLecteur.startsWith("player.canalplus.fr/embed/")) { /** * Canal+ */ monRemplacement.html("<a href=\"" + urlLecteur + "\"><img " + "src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_canalplus + "\" /></a>"); } else if (urlLecteur.startsWith("www.arte.tv/")) { /** * Arte */ monRemplacement.html("<a href=\"" + urlLecteur + "\"><img " + "src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_arte + "\" /></a>"); } else { /** * Déchet (catch all) */ monRemplacement.html( "<a href=\"" + uneIframe.absUrl("src") + "\"><img " + "src=\"" + Constantes.SCHEME_IFRAME_DRAWABLE + R.drawable.iframe_non_supportee + "\" /></a>"); // DEBUG if (Constantes.DEBUG) { Log.e("ParseurHTML", "getArticle() - Iframe non gérée dans " + monArticleItem.getId() + " : " + uneIframe.absUrl("src")); } } // Je remplace l'iframe par mon contenu uneIframe.replaceWith(monRemplacement); // DEBUG if (Constantes.DEBUG) { Log.i("ParseurHTML", "Remplacement par une iframe : " + monRemplacement.html()); } } // Gestion des URL relatives des liens Elements lesLiens = lArticle.select("a[href]"); // Pour chaque lien for (Element unLien : lesLiens) { // Assignation de son URL absolue unLien.attr("href", unLien.absUrl("href")); } // Gestion des URL relatives des images Elements lesImages = lArticle.select("img[src]"); // Pour chaque image for (Element uneImage : lesImages) { // Assignation de son URL absolue uneImage.attr("src", uneImage.absUrl("src")); } // J'enregistre le contenu monArticleItem.setContenu(lArticle.toString()); return monArticleItem; } /** * Nombre de commentaires d'un article à partir d'une page de commentaires. * * @param unContenu contenu HTML brut * @param urlPage URL de la page * @return nb de commentaires de l'article */ public static int getNbCommentaires(final String unContenu, final String urlPage) { // Lancement du parseur sur la page Document pageNXI = Jsoup.parse(unContenu, urlPage); // Nombre de commentaires Element elementNbComms = pageNXI.select("span[class=actu_separator_comms]").get(0); // Représentation textuelle "nn commentaires" String stringNbComms = elementNbComms.text(); // Isolation du chiffre uniquement (avant l'espace) int positionEspace = stringNbComms.indexOf(" "); String valeur = stringNbComms.substring(0, positionEspace).trim(); // Parsage de la valeur int nbComms = Integer.valueOf(valeur); // DEBUG if (Constantes.DEBUG) { Log.i("ParseurHTML", "getNbCommentaires() - " + nbComms); } return nbComms; } /** * Parse les commentaires. * * @param unContenu contenu HTML brut * @param urlPage URL de la page * @return liste de CommentaireItem */ public static ArrayList<CommentaireItem> getCommentaires(final String unContenu, final String urlPage) { // mon retour ArrayList<CommentaireItem> mesCommentairesItem = new ArrayList<>(); // Calcul du numéro de page int numeroPage = Integer.valueOf( urlPage.substring(urlPage.indexOf("&") + Constantes.NEXT_INPACT_URL_COMMENTAIRES_PARAM_NUM_PAGE.length() + 2)); // Lancement du parseur sur la page Document pageNXI = Jsoup.parse(unContenu, urlPage); // ID de l'article concerné Element refArticle = pageNXI.select("aside[data-relnews]").get(0); int idArticle = Integer.valueOf(refArticle.attr("data-relnews")); // Les commentaires // Passage par une regexp => https://github.com/jhy/jsoup/issues/521 Elements lesCommentaires = pageNXI.select("div[class~=actu_comm ],div[class~=actu_comm_author]"); // Contenu // Supprimer les liens internes (<a> => <div>) // "En réponse à ...", "... à écrit" Elements lesLiensInternes = lesCommentaires.select("a[class=link_reply_to], div[class=quote_bloc]>div[class=qname]>a"); lesLiensInternes.tagName("div"); // Blockquote Elements lesCitations = lesCommentaires.select("div[class=link_reply_to], div[class=quote_bloc]"); lesCitations.tagName("blockquote"); // Gestion des URL relatives Elements lesLiens = lesCommentaires.select("a[href]"); // Pour chaque lien for (Element unLien : lesLiens) { // Assignation de son URL absolue unLien.attr("href", unLien.absUrl("href")); } // Calcul de l'indice du premier commentaire (gestion des commentaires supprimés) int idCommPrecedent = (numeroPage - 1) * Constantes.NB_COMMENTAIRES_PAR_PAGE; int uuidCommPrecedent = 0; CommentaireItem monCommentaireItem; // Pour chaque commentaire for (Element unCommentaire : lesCommentaires) { monCommentaireItem = new CommentaireItem(); // ID de l'article monCommentaireItem.setArticleId(idArticle); // UUID du commentaire int monUUID; try { monUUID = Integer.valueOf(unCommentaire.attr("data-content-id")); } catch (NumberFormatException e) { // Commentaire supprimé : UUID précédent + 1 monUUID = uuidCommPrecedent + 1; } // Mise à jour de l'indice stocké uuidCommPrecedent = monUUID; // Enregistrement de l'UUID monCommentaireItem.setUuid(monUUID); // Auteur Elements monAuteur = unCommentaire.select("span[class=author_name]"); if (!monAuteur.isEmpty()) { monCommentaireItem.setAuteur(monAuteur.get(0).text()); } else { // Gestion des commentaires supprimés monCommentaireItem.setAuteur("-"); } // Date Elements maDate = unCommentaire.select("span[class=date_comm]"); if (!maDate.isEmpty()) { String laDate = maDate.get(0).text(); monCommentaireItem.setTimeStampPublication(convertToTimeStamp(laDate, Constantes.FORMAT_DATE_COMMENTAIRE)); } else { // Gestion des commentaires supprimés monCommentaireItem.setTimeStampPublication(0); } // Id du commentaire Elements monID = unCommentaire.select("span[class=actu_comm_num]"); if (!monID.isEmpty()) { // Le premier caractère est un "#" String lID = monID.get(0).text().substring(1); monCommentaireItem.setId(Integer.valueOf(lID)); // MàJ du numéro du dernier commentaire idCommPrecedent = Integer.valueOf(lID); } else { // Gestion des commentaires supprimés monCommentaireItem.setId(idCommPrecedent + 1); // MàJ du numéro du dernier commentaire idCommPrecedent++; } // Contenu Elements monContenu = unCommentaire.select("div[class=actu_comm_content]"); if (!monContenu.isEmpty()) { monCommentaireItem.setCommentaire(monContenu.get(0).toString()); } else { // Gestion des commentaires supprimés - Récupération de la chaîne du détail de modération monContenu = unCommentaire.select("div[class~=actu_comm_author]"); if (!monContenu.isEmpty()) { monCommentaireItem.setCommentaire(monContenu.get(0).toString()); } else { // Gestion de l'erreur de récupération de la modération (en cas de modif du code html évite une // exception... !) monCommentaireItem.setCommentaire("--- Erreur ---"); } } // Et je le stocke mesCommentairesItem.add(monCommentaireItem); } return mesCommentairesItem; } /** * Convertit une date texte en timestamp. * * @param uneDate date au format textuel * @param unFormatDate format de la date * @return timestamp */ private static long convertToTimeStamp(final String uneDate, final String unFormatDate) { DateFormat dfm = new SimpleDateFormat(unFormatDate, Constantes.LOCALE); dfm.setTimeZone(TimeZone.getTimeZone("Europe/Paris")); long laDateTS = 0; try { // Récupération du timestamp laDateTS = dfm.parse(uneDate).getTime(); } catch (ParseException e) { if (Constantes.DEBUG) { Log.e("ParseurHTML", "convertToTimeStamp() - erreur parsage date : " + uneDate, e); } } return laDateTS; } }