diff --git a/README.md b/README.md index 726b69dfcd28c97844742fa0a4e0f9b6110f0eb3..b8c46f783a430bfc0b6040807bc301e06eb632a6 100644 --- a/README.md +++ b/README.md @@ -25,12 +25,18 @@ PDF ayant un contenu textuel en français. La segmentation consiste a organiser le contenu textuel d'un document en une liste de blocs distincts. Chacun de ces blocs sera transformé en une section de page html : -H1, H2, P, FigCaption, Footer, Header. +H1, H2, P, FigCaption, Footer, Header et Table. Ce programme est basée sur l'outil pdftohtml et pdftotext, deux outils de la librairie [poppler](https://poppler.freedesktop.org/). +Pour la reconnaissance des tableaux, il utilise +[camelot-py](https://pypi.org/project/camelot-py/) +([documentation](https://camelot-py.readthedocs.io/en/master/index.html)). +Camelot peut s'appuyer sur ghostscript ou poppler. Nous avons bien sûr +choisi le second, qui nécessite [pdftopng](https://pypi.org/project/pdftopng/) +(basé sur poppler). Le programme prend en entrée un fichier au format PDF, et produit en sortie un fichier html. diff --git a/corpusEvaluation/pdf2blocs/20190619_LOR_BSV_Viticulture_cle8c2fdf.html b/corpusEvaluation/pdf2blocs-v1.0/20190619_LOR_BSV_Viticulture_cle8c2fdf.html similarity index 100% rename from corpusEvaluation/pdf2blocs/20190619_LOR_BSV_Viticulture_cle8c2fdf.html rename to corpusEvaluation/pdf2blocs-v1.0/20190619_LOR_BSV_Viticulture_cle8c2fdf.html diff --git a/corpusEvaluation/pdf2blocs/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html b/corpusEvaluation/pdf2blocs-v1.0/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html similarity index 100% rename from corpusEvaluation/pdf2blocs/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html rename to corpusEvaluation/pdf2blocs-v1.0/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html diff --git a/corpusEvaluation/pdf2blocs/als_vit_no15_2016-07-26_cle879161-1.html b/corpusEvaluation/pdf2blocs-v1.0/als_vit_no15_2016-07-26_cle879161-1.html similarity index 100% rename from corpusEvaluation/pdf2blocs/als_vit_no15_2016-07-26_cle879161-1.html rename to corpusEvaluation/pdf2blocs-v1.0/als_vit_no15_2016-07-26_cle879161-1.html diff --git a/corpusEvaluation/pdf2blocs-v1.1/20190619_LOR_BSV_Viticulture_cle8c2fdf.html b/corpusEvaluation/pdf2blocs-v1.1/20190619_LOR_BSV_Viticulture_cle8c2fdf.html new file mode 100644 index 0000000000000000000000000000000000000000..bd50027683ae84d627b1ac9a7caec7c311f6f7b5 --- /dev/null +++ b/corpusEvaluation/pdf2blocs-v1.1/20190619_LOR_BSV_Viticulture_cle8c2fdf.html @@ -0,0 +1,161 @@ +<!DOCTYPE html> +<html lang="fr"> +<head><meta charset="utf-8" /> +<link rel="stylesheet" href="http://ontology.inrae.fr/bsv/html/bsv.css" /> +<link rel="stylesheet" href="bsv.css" /> + <title></title> +</head> +<body> +<h3 class="title 3" blk="0">BSV n° 09 - 19 Juin 2019</h3> +<h2 class="title 2" blk="1">A RETENIR CETTE SEMAINE</h2> +<h3 class="title 3" blk="2">Bilan météo p 2</h3> +<p class="paragraph" blk="3">Phénologie : H 17 " boutons séparés " à I 20 " début floraison " p 2</p> +<p class="paragraph" blk="4">Mildiou : pas de contamination sur le réseau p 2</p> +<p class="paragraph" blk="5">Oïdium : un signalement sur feuille hors réseau p 3</p> +<p class="paragraph" blk="6">Tordeuses : fin du vol de 1ère génération - pas de glomérule p 3</p> +<p class="paragraph" blk="7">Annexes : note nationale abeilles p 4</p> +<h3 class="title 3" blk="8">PROCHAIN BSV : 26 juin 2019</h3> +<footer class="bottom page" blk="9">1<br /></footer> +<footer class="bottom page" blk="10">BSV n° 09<br /></footer> +<hr /><!-- =============== Page 2 =============== --> +<h2 class="title 2" blk="11">BILAN MÉTÉO</h2> +<h1 class="title 1" blk="12">FRUIT</h1> +<h3 class="title 3" blk="13">Données du 10/06 au 17/06</h3> +<figure><figcaption class="caption" blk="14">Date</figcaption></figure> +<div class="unknown"></div> +<figure><figcaption class="caption" blk="16">Lucey</figcaption></figure> +<figure><figcaption class="caption" blk="17">Corny-sur-Moselle</figcaption></figure> +<p class="paragraph" blk="18">Température moyenne 16,2 °C Tableau pluviométrie (mm)</p> +<figure><figcaption class="caption" blk="19">10/06</figcaption></figure> +<figure><figcaption class="caption" blk="20">14,5</figcaption></figure> +<figure><figcaption class="caption" blk="21">17,6</figcaption></figure> +<figure><figcaption class="caption" blk="22">11/06</figcaption></figure> +<figure><figcaption class="caption" blk="23">13</figcaption></figure> +<figure><figcaption class="caption" blk="24">9,6</figcaption></figure> +<figure><figcaption class="caption" blk="25">12/06</figcaption></figure> +<figure><figcaption class="caption" blk="26">1</figcaption></figure> +<figure><figcaption class="caption" blk="27">3,6</figcaption></figure> +<figure><figcaption class="caption" blk="28">13/06</figcaption></figure> +<figure><figcaption class="caption" blk="29">0</figcaption></figure> +<figure><figcaption class="caption" blk="30">0</figcaption></figure> +<figure><figcaption class="caption" blk="31">14/06</figcaption></figure> +<figure><figcaption class="caption" blk="32">7</figcaption></figure> +<figure><figcaption class="caption" blk="33">8</figcaption></figure> +<figure><figcaption class="caption" blk="34">Cumul semaine</figcaption></figure> +<figure><figcaption class="caption" blk="35">35,5</figcaption></figure> +<figure><figcaption class="caption" blk="36">38,8</figcaption></figure> +<figure><figcaption class="caption" blk="37">Cumul au 01/01</figcaption></figure> +<p class="paragraph" blk="38">225</p> +<figure><figcaption class="caption" blk="39">396,8</figcaption></figure> +<figure><figcaption class="caption" blk="40">Ochey</figcaption></figure> +<figure><figcaption class="caption" blk="41">Hattonville</figcaption></figure> +<figure><figcaption class="caption" blk="42">Remich</figcaption></figure> +<figure><figcaption class="caption" blk="43">5,6</figcaption></figure> +<figure><figcaption class="caption" blk="44">1,8</figcaption></figure> +<figure><figcaption class="caption" blk="45">15,6</figcaption></figure> +<figure><figcaption class="caption" blk="46">19,1</figcaption></figure> +<figure><figcaption class="caption" blk="47">13,8</figcaption></figure> +<figure><figcaption class="caption" blk="48">1,2</figcaption></figure> +<figure><figcaption class="caption" blk="49">1,4</figcaption></figure> +<figure><figcaption class="caption" blk="50">3,8</figcaption></figure> +<figure><figcaption class="caption" blk="51">0</figcaption></figure> +<figure><figcaption class="caption" blk="52">0,4</figcaption></figure> +<figure><figcaption class="caption" blk="53">6,2</figcaption></figure> +<figure><figcaption class="caption" blk="54">8,7</figcaption></figure> +<figure><figcaption class="caption" blk="55">9,4</figcaption></figure> +<figure><figcaption class="caption" blk="56">5,6</figcaption></figure> +<figure><figcaption class="caption" blk="57">34,6</figcaption></figure> +<figure><figcaption class="caption" blk="58">26,8</figcaption></figure> +<figure><figcaption class="caption" blk="59">31,2</figcaption></figure> +<figure><figcaption class="caption" blk="60">290</figcaption></figure> +<figure><figcaption class="caption" blk="61">328</figcaption></figure> +<figure><figcaption class="caption" blk="62">Source http://www.meteociel.fr</figcaption></figure> +<h2 class="title 2" blk="63">PHENOLOGIE</h2> +<h1 class="title 1" blk="64">FRUIT</h1> +<p class="paragraph" blk="65">La floraison débute sur le vignoble lorrain. Les stades varient entre H 17 " boutons floraux séparés " et I 20 " début floraison ". En 2018, la floraison avait démarré au 4 juin.</p> +<figure><figcaption class="caption" blk="66">Stade I20" début floraison "</figcaption></figure> +<figure><figcaption class="caption" blk="67">(Claire HARTARD -FREDON LORRAINE)</figcaption></figure> +<h2 class="title 2" blk="68">MILDIOU</h2> +<p class="paragraph" blk="69">Tous les éléments de biologie, de lutte alternative et de suivi du mildiou disponibles en cliquant sur ce lien</p> +<h3 class="title 3" blk="70">Situation actuelle</h3> +<p class="paragraph" blk="71">Le vignoble reste sain. Des averses significatives ont été enregistrées entre lundi 10 et vendredi 14 juin. Le risque de contamination est à prendre en compte si la pluviométrie est supérieure à 2 mm. Avec les températures moyennes, les cycles d'incubation durent environ 7 jours. Des taches pourraient sortir entre le 17 et le 21 juin.</p> +<h3 class="title 3" blk="72">Analyse de risque</h3> +<p class="paragraph" blk="73">Le risque mildiou est important jeudi et samedi car des pluies sont annoncées. Surveillez les cumuls de pluviométrie sur vos parcelles et vérifiez l'état sanitaire de vos parcelles .</p> +<p class="paragraph" blk="74">Si vous observez des taches de mildiou, merci de nous en informer et de nous envoyer si possible une photo.</p> +<footer class="bottom page" blk="75">2<br /></footer> +<footer class="bottom page" blk="76">BSV n° 09<br /></footer> +<hr /><!-- =============== Page 3 =============== --> +<h2 class="title 2" blk="77">OÃDIUM</h2> +<h1 class="title 1" blk="78">FRUIT</h1> +<p class="paragraph" blk="79">Tous les éléments de biologie, de lutte alternative et de suivi de l'oïdium disponibles en cliquant sur ce lien</p> +<h3 class="title 3" blk="80">Situation actuelle</h3> +<p class="paragraph" blk="81">Aucune tache n'a été observée sur les parcelles du réseau mais des symptômes d'oïdium sur feuilles ont été observés dans le Toulois sur un secteur très sensible.</p> +<h3 class="title 3" blk="82">Analyse de risque</h3> +<p class="paragraph" blk="83">Le risque oïdium est important entre jeudi et samedi avec les pluies prévues et la période de grande sensibilité de la vigne<br />(floraison). Surveillez vos parcelles notamment celles sensibles en cherchant la présence de feutrage gris foncé, poussiéreux sur la face inférieure des feuilles.</p> +<h2 class="title 2" blk="84">TORDEUSES</h2> +<h1 class="title 1" blk="85">FRUIT</h1> +<p class="paragraph" blk="86">Tous les éléments de biologie, de lutte alternative et de suivi des tordeuses disponibles en cliquant sur ce lien</p> +<h3 class="title 3" blk="87">Situation actuelle</h3> +<p class="paragraph" blk="88">Le vol de première génération est terminé. Les glomérules et les larves de 1ère génération vont être visibles sur les inflorescences. En ce début de semaine aucun glomérule n'a été observé.</p> +<p class="paragraph" blk="89">Cochylis : 0 capture</p> +<p class="paragraph" blk="90">Eudémis : 0 à 4 captures (1,0 en moyenne, 4/11 parcelles avec des captures)</p> +<h3 class="title 3" blk="91">Analyse de risque</h3> +<p class="paragraph" blk="92">Les conditions de la semaine sont favorables au vol.</p> +<p class="paragraph" blk="93">Le comptage des larves de 1ère génération a débuté. Pour évaluer leur importance, vous pouvez suivre le protocole de comptage en page 3. Une lutte spécifique n'est pas nécessaire sur les larves de 1ère génération car les attaques ont un faible impact sur la vigne.</p> +<p class="paragraph" blk="94">Relevez régulièrement vos pièges pour localiser le pic de vol de la 2ème génération qui peut provoquer des dégâts plus significatifs.</p> +<footer class="bottom page" blk="95">3<br /></footer> +<footer class="bottom page" blk="96">BSV n° 09<br /></footer> +<hr /><!-- =============== Page 4 =============== --> +<h2 class="title 2" blk="97">ANNEXES</h2> +<h1 class="title 1" blk="98">FRUIT</h1> +<p class="paragraph" blk="99">NOTE NATIONALE BSV : Les abeilles, des alliées pour nos cultures : protégeons-les !</p> +<p class="paragraph" blk="100">A l'approche de la floraison, utilisez un insecticide ou acaricide portant la mention " abeilles " et intervenez en dehors des périodes de butinage (très tôt le matin ou en soirée), lorsque la température est inférieure à 13°C, par temps nuageux.</p> +<p class="paragraph" blk="101">PENSEZ A OBSERVER VOS CULTURES AVANT DE TRAITER CAR IL EST INTERDIT DE TRAITER EN PRESENCE D'ABEILLES MEME SI LE PRODUIT COMPORTE LA MENTION " ABEILLES " !</p> +<p class="paragraph" blk="102">Pour en savoir plus :</p> +<p class="paragraph" blk="103">Note nationale BSV Abeilles</p> +<p class="paragraph" blk="104">Les abeilles butinent</p> +<footer class="bottom page" blk="105">4<br /></footer> +<footer class="bottom page" blk="106">BSV n° 09<br /></footer> +<hr /><!-- =============== Page 5 =============== --> +<p class="paragraph" blk="107">Retrouvez gratuitement le BSV toutes les semaines sur les sites Internet de la Chambre Régionale d'Agriculture Grand Est :</p> +<p class="paragraph" blk="108">http://www.grandest.chambre-agriculture.fr/index.php?id=2853502</p> +<h2 class="title 2" blk="109">et de la DRAAF :</h2> +<p blk="110"><small class="misc">http://draaf.grand-est.agriculture.gouv.fr/Surveillance-des-organismes</small></p> +<figure><figcaption class="caption" blk="111">ÉDITÉ</figcaption></figure> +<figure><figcaption class="caption" blk="112">SOUS</figcaption></figure> +<h3 class="title 3" blk="113">LA</h3> +<figure><figcaption class="caption" blk="114">RESPONSABILITÉ</figcaption></figure> +<h3 class="title 3" blk="115">DE</h3> +<h3 class="title 3" blk="116">LA</h3> +<figure><figcaption class="caption" blk="117">CHAMBRE</figcaption></figure> +<figure><figcaption class="caption" blk="118">RÉGIONALE D 'A GRICULTURE G RAND E ST SUR LA BASE</figcaption></figure> +<figure><figcaption class="caption" blk="119">DES OBSERVATIONS RÉALISÉES PAR LES PARTENAIRES</figcaption></figure> +<figure><figcaption class="caption" blk="120">DU RÉSEAU VIGNE</figcaption></figure> +<figure><figcaption class="caption" blk="121">Viticulteurs</figcaption></figure> +<figure><figcaption class="caption" blk="122">volontaires</figcaption></figure> +<h3 class="title 3" blk="123">Chambre</h3> +<h3 class="title 3" blk="124">d'Agriculture</h3> +<figure><figcaption class="caption" blk="125">de</figcaption></figure> +<figure><figcaption class="caption" blk="126">la</figcaption></figure> +<figure><figcaption class="caption" blk="127">Meuse</figcaption></figure> +<h3 class="title 3" blk="128">Chambre</h3> +<h3 class="title 3" blk="129">Régionale</h3> +<p blk="130"><small class="misc">d'Agriculture Grand Est FREDON Lorraine</small></p> +<p blk="131"><small class="misc">Rédaction : FREDON Lorraine et Chambre Régionale d'Agriculture Grand Est (CRAGE)</small></p> +<p blk="132"><small class="misc">Dans une démarche d'amélioration continue de qualité de la surveillance biologique du territoire, la DRAAF</small></p> +<p blk="133"><small class="misc">assure un contrôle de second niveau sur l'ensemble du processus d'élaboration des BSV</small></p> +<p blk="134"><small class="misc">Crédits photos: FREDON Lorraine</small></p> +<p blk="135"><small class="misc">Animation du réseau Vigne :</small></p> +<p blk="136"><small class="misc">Amélie MARI FREDON Lorraine 03.83.33.86.76 amelie.mari@fredon-lorraine.com</small></p> +<figure><figcaption class="caption" blk="137">Coordination et renseignements :</figcaption></figure> +<p blk="138"><small class="misc">Claire COLLOT CRAGE 03 83 96 85 02 claire.collot@grandest.chambagri.fr</small></p> +<p blk="139"><small class="misc">Karim BENREDJEM CRAGE 03 26 65 18 52 karim.benredjem@grandest.chambagri.fr</small></p> +<figure><figcaption class="caption" blk="140">Pour recevoir le Bulletin de Santé du Végétal par courrier électronique, vous pouvez en faire la</figcaption></figure> +<p blk="141"><small class="misc">demande sur le site internet de la Chambre d'Agriculture du Grand Est</small></p> +<figure><figcaption class="caption" blk="142">http://www.grandest.chambre-agriculture.fr/productions-agricoles/ecophyto/bulletins-de-sante-du-vegetal/abonnez<br />-vous-gratuitement-a-nos-bsv/</figcaption></figure> +<p blk="143"><small class="misc">Action pilotée par le ministère chargé de l'agriculture, avec l'appui financier de l'Agence Française de Biodiversité, par les</small></p> +<p blk="144"><small class="misc">crédits issus de la redevance pour pollutions diffuses attribués au financement du plan ECOPHYTO II.</small></p> +<footer class="bottom page" blk="145">5<br /></footer> +<footer class="bottom page" blk="146">BSV n° 09<br /></footer> +</body><!-- 837 words --> +</html> diff --git a/corpusEvaluation/pdf2blocs-v1.1/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html b/corpusEvaluation/pdf2blocs-v1.1/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html new file mode 100644 index 0000000000000000000000000000000000000000..14072c9ffc960badde7eccbd5854ad280cff2b08 --- /dev/null +++ b/corpusEvaluation/pdf2blocs-v1.1/BSV_NA_VIGNE_CHARENTES_02_20190409_cle8ac81e.html @@ -0,0 +1,139 @@ +<!DOCTYPE html> +<html lang="fr"> +<head><meta charset="utf-8" /> +<link rel="stylesheet" href="http://ontology.inrae.fr/bsv/html/bsv.css" /> +<link rel="stylesheet" href="bsv.css" /> +<title>N°02</title> +</head> +<body> +<h1 class="doc title" blk="0">N°02</h1> +<h2 class="title 2" blk="1">Edition</h2> +<h1 class="title 1" blk="2">Charentes</h1> +<h2 class="title 2" blk="3">09/04/2019</h2> +<figure><figcaption class="caption" blk="4">Bulletin disponible sur bsv.na.chambagri.fr et sur le site de la DRAAF draaf.nouvelle-aquitaine.agriculture.gouv.fr/BSV-Nouvelle-Aquitaine-2018</figcaption></figure> +<figure><figcaption class="caption" blk="5">Recevez le Bulletin de votre choix GRATUITEMENT en cliquant sur Formulaire d'abonnement au BSV</figcaption></figure> +<h1 class="title 1" blk="6">Ce qu'il faut retenir</h1> +<p blk="7"><small class="misc">Animateur filière</small></p> +<p blk="8"><small class="misc">Magdalena GIRARD Chambre d'agriculture de la Charente-Maritime magdalena.girard@charente-maritime.chambagri.fr</small></p> +<p blk="9"><small class="misc">Directeur de publication</small></p> +<p blk="10"><small class="misc">Dominique GRACIET Président de la Chambre Régionale Nouvelle-Aquitaine Boulevard des Arcades 87060 LIMOGES Cedex 2 accueil@na.chambagri.fr</small></p> +<p blk="11"><small class="misc">Supervision</small></p> +<p blk="12"><small class="misc">DRAAF Service Régional de l'Alimentation Nouvelle-Aquitaine 22 Rue des Pénitents Blancs 87000 LIMOGES</small></p> +<p blk="13"><small class="misc">Supervision site de Bordeaux</small></p> +<p blk="14"><small class="misc">Reproduction intégrale de ce bulletin autorisée. Reproduction partielle autorisée avec la mention<br />" extrait du bulletin de santé du végétal Nouvelle-Aquitaine Vigne / Edition Charentes N°2 du 09/04/2019 "</small></p> +<h2 class="title 2" blk="15">Phénologie</h2> +<p class="paragraph" blk="16">Stade moyen (Ugni blanc) : stade 03 bourgeon dans le coton</p> +<h2 class="title 2" blk="17">Mildiou</h2> +<h5 class="title 5" blk="18">Aucun risque pour le moment, pression faible</h5> +<h2 class="title 2" blk="19">Black rot</h2> +<h5 class="title 5" blk="20">Risque potentiel fort mais pas de contaminations prévues</h5> +<p class="paragraph" blk="21">Le bulletin de cette semaine est réalisé à partir des données d'observations du réseau de parcelles, complétées par des données " tour de plaine ".</p> +<p class="paragraph" blk="22">La qualité des données du BSV dépend, en grande partie, de la qualité et de la taille du réseau d'observations du vignoble Charentais. Participez, vous aussi, tout au long de la saison à l'amélioration du réseau d'observations du BSV en multipliant vos signalements (maladies, ravageurs, évènements climatiques ) sur le site Web Alerte Vigne.</p> +<footer class="bottom page" blk="23">Bulletin de Santé du Végétal Nouvelle-Aquitaine / Edition Charentes<br /> Vigne N°02 du 09 Avril 2019<br /></footer> +<footer class="bottom page" blk="24">1/6<br /></footer> +<hr /><!-- =============== Page 2 =============== --> +<h1 class="title 1" blk="25">Phénologie</h1> +<p class="paragraph" blk="26">La baisse des températures a nettement ralenti l'évolution des bourgeons. L'Ugni blanc est au stade moyen 03 (bourgeon dans le coton), ce qui ne représente plus que 3 jours d'avance par rapport à la moyenne sur 20 ans. Les bourgeons les plus avancés atteignent le stade 07 (première feuille étalée). Les stades sont hétérogènes d'une parcelle à l'autre.</p> +<figure><figcaption class="caption" blk="27">Stade 03<br />(bourgeon dans le coton)</figcaption></figure> +<figure><figcaption class="caption" blk="28">Stade 07<br />(première feuille étalée)</figcaption></figure> +<p class="paragraph" blk="29">Certains bourgeons des cépages précoces atteignent le stade 12 (5/6 feuilles étalées).</p> +<h1 class="title 1" blk="30">Climatologie</h1> +<h2 class="title 2" blk="31">De la semaine passée</h2> +<h3 class="title 3" blk="32">Températures</h3> +<p class="paragraph" blk="33">Les températures de la semaine écoulée ont été en nette baisse, avec 8.7°C en moyenne.</p> +<h3 class="title 3" blk="34">Pluies</h3> +<p class="paragraph" blk="35">Sur la semaine passée, le réseau de stations météo a enregistré 18.2 mm de pluies en moyenne, de 10.8 mm pour Guimps à 33.5 mm pour Arces.</p> +<h2 class="title 2" blk="36">Prévisions météo</h2> +<p class="paragraph" blk="37">Les prévisions météo annoncent une belle semaine avec des températures fraîches.</p> +<h1 class="title 1" blk="38">Gel, grêle</h1> +<p class="paragraph" blk="39">Très localement, quelques dégâts de gel et de grêle sont signalés, suite aux épisodes du 4 avril.</p> +<h1 class="title 1" blk="40">Maladies</h1> +<h2 class="title 2" blk="41">Rappel modélisation</h2> +<p class="paragraph" blk="42">Pour apprécier le développement des principales maladies fongiques (mildiou, oïdium et black-rot) sur la vigne, le BSV utilise le modèle Potentiel Système. Cet outil indique si l'environnement est favorable ou non au développement de chacun de ces pathogènes et signale chaque évènement climatique qu'il estime être contaminant. Pour parvenir à ce résultat, le modèle est alimenté de relevés météorologiques (pluie et température, fournies par Météo France) et de prévisions adaptées aux particularités des secteurs géographiques auxquels elles sont attribuées. Le modèle confronte ces données au référentiel météorologique historique le plus proche. Les écarts à la normale définissent le comportement des pathogènes : le modèle les retranscrit sous la forme d'une évolution des indicateurs au cours du temps.</p> +<p class="paragraph" blk="43">Deux types d'indicateurs sont accessibles :<br /> Le premier caractérise l'état du pathogène : sa phénologie, son agressivité, sa capacité à germer La retranscription globale du potentiel infectieux du pathogène est faite sous la forme de cartographique indiquant le risque potentiel. Plus il est favorable au pathogène, plus les conditions sont favorables à son développement : cela se traduit notamment par des contaminations plus sévères en cas de pluie. Inversement si le risque potentiel est très faible, les conditions de développement sont alors très défavorables pour le pathogène : une</p> +<footer class="bottom page" blk="44">Bulletin de Santé du Végétal Nouvelle-Aquitaine / Edition Charentes<br /> Vigne N°02 du 09 Avril 2019<br /></footer> +<footer class="bottom page" blk="45">2/6<br /></footer> +<hr /><!-- =============== Page 3 =============== --> +<p class="paragraph" blk="46">des manifestations de cette situation est la quantité plus faible voire même l'absence de contaminations en cas de pluies.</p> +<p class="paragraph" blk="47">Le second indique les périodes de contaminations et les quantifie. Deux sortes de contaminations sont définies :<br />-pré-épidémiques, qui correspondent à une minorité de la population du pathogène capable de se développer en début de saison. Ces contaminations se traduisent sur le terrain par de très rares symptômes non préoccupants.<br />épidémiques, qui se traduisent par des sorties significatives de symptômes et appellent à la vigilance.</p> +<p class="paragraph" blk="48"> Les niveaux de risque indiqués dans les différents bulletins que vous pouvez consulter (BSV, Chambres d'Agriculture, distribution, ) sont généralement issus des calculs des modèles mathématiques. Selon les différents modèles employés, la façon de les utiliser et d'interpréter les données, les résultats ne seront forcément pas identiques. Les différentes hypothèses météo choisies sont également source de divergences. Les modèles restent des Outils d'Aide à la Décision (OAD), à prendre en compte parmi d'autres indicateurs.</p> +<h2 class="title 2" blk="49">Mildiou :</h2> +<h3 class="title 3" blk="50">Rappel des éléments de biologie</h3> +<p class="paragraph" blk="51">Le mildiou de la vigne se conserve sous forme d'oospores (Å“ufs d'hiver) présentes sur les feuilles attaquées à l'automne et tombées au sol. Après leur maturation, ces Å“ufs germent dans l'eau à partir d'une température moyenne de 11°C, et libèrent des zoospores qui peuvent provoquer les contaminations. Après une incubation de 10 à 20 jours suivant les températures, apparaissent les conidiophores (fructifications contenant les conidies) sur la face inférieure des feuilles. Les conidies assurent les contaminations secondaires ou repiquages en présence de pluies. La phase d'incubation (période entre contamination et apparition des symptômes) est directement liée à la température et peut se limiter à 5 jours en été. Les contaminations ne se réalisent qu'en cas de pluies mais les repiquages sur une vigne contaminée peuvent se réaliser à la faveur de rosées matinales ou de brouillards épais. L'optimum thermique de P. viticola est de l'ordre de 25°C, et sa plage d'activité se situe entre 11 et 30°C.</p> +<p class="paragraph" blk="52">Les conditions nécessaires pour les contaminations de mildiou sont les suivantes :<br /> germination des Å“ufs d'hiver en moins de 24 heures,<br /> vigne réceptive (au moins 1 feuille étalée),<br /> températures moyennes supérieures à 11 °C,<br /> pluviométrie suffisante (3-5 mm minimum).</p> +<h3 class="title 3" blk="53">Suivi biologique des Å“ufs d'hiver</h3> +<p class="paragraph" blk="54">Au laboratoire, les Å“ufs de mildiou sont ne sont toujours pas arrivés à maturité.</p> +<h3 class="title 3" blk="55">Modélisation (source IFV)</h3> +<p class="paragraph" blk="56">Sur la semaine passée Le risque potentiel est resté faible sur la majorité du vignoble. Seules quelques zones délimitées dans le sud du vignoble affichaient un risque potentiel fort. Nous sommes encore dans la phase de maturation des Å“ufs.</p> +<p class="paragraph" blk="57">Aucune contamination pré-épidémique n'a été à ce jour détectée par le modèle sur l'ensemble des points de calcul.</p> +<p class="paragraph" blk="58">Dans les trois jours à venir La prévision météorologique la plus probable annonce 5 à 10 mm sur le continent et 2 mm sur les îles. Pour l'hypothèse la plus humide, elle annonce 15 à 20 mm sur le continent et 8 mm sur les îles. L'hypothèse la plus sèche annonce un cumul de pluie inférieur à 1 mm. Les températures seront de 3-7°C pour les minimales et 14-16°C pour les maximales.</p> +<p class="paragraph" blk="59">Aucune évolution notable du risque potentiel n'est relevée dans les trois jours à venir. Aucune contamination pré-épidémique n'est envisagée pour ces prochains jours.</p> +<h4 class="title 4" blk="60"> Consultez la fiche " mildiou " du Guide de l'Observateur</h4> +<footer class="bottom page" blk="61">Bulletin de Santé du Végétal Nouvelle-Aquitaine / Edition Charentes<br /> Vigne N°02 du 09 Avril 2019<br /></footer> +<footer class="bottom page" blk="62">3/6<br /></footer> +<hr /><!-- =============== Page 4 =============== --> +<p class="paragraph" blk="63">Evaluation du risque : Les Å“ufs d'hiver de mildiou ne sont pas mûrs, le niveau de risque est globalement faible, le risque de contaminations pré-épidémiques est nul.</p> +<h5 class="title 5" blk="64">Risque très faible</h5> +<h2 class="title 2" blk="65"> Black rot</h2> +<h3 class="title 3" blk="66">Eléments de biologie</h3> +<p class="paragraph" blk="67">Le Black-rot se conserve l'hiver sur les baies momifiées (grappillons non récoltés, restés accrochés au palissage, ou tombés sur le sol), les vrilles, les feuilles infectées tombées au sol et sur les chancres présents sur les sarments, sous forme de conceptacles indifférenciés qui évoluent en périthèces durant l'hiver et au printemps. Au printemps l'augmentation de la température, associée à une humidité importante, induit la production d'ascospores qui sont projetées durant plusieurs mois des périthèces matures ; celles-ci contaminent la vigne, notamment les feuilles et les jeunes baies, et sont responsables des contaminations primaires en présence d'une humidité relative suffisante. Les ascospores peuvent être éjectées après une rosée ou une pluie même faible. Cette contamination peut durer jusqu'à 8h après l'arrêt des pluies. Les contaminations primaires peuvent se faire sur de longues distances grâce au vent. Par la suite, des ponctuations brunes à noires apparaissent sur les tissus altérés, ce sont les pycnides qui contiennent des conidies qui assureront des contaminations secondaires surtout sur les jeunes baies situées en dessous. Les contaminations secondaires se font sur de courtes distances grâce aux pluies et aux éclaboussures qui projettent les conidies. Le Black-rot a besoin de pluies fréquentes et durables et de températures comprises entre 9°C et au maximum 32°C, son optimum se situant autour de 26°C.</p> +<p class="paragraph" blk="68">Le feuillage de la vigne est réceptif de la sortie des premières feuilles à quelques jours après la floraison, les grappes jusqu'à la véraison.</p> +<p class="paragraph" blk="69">Facteurs favorisants :<br /> Présence de baies contaminées momifiées (grappillons non récoltés, restés accrochés au palissage, ou tombés sur le sol) sur la parcelle. Proximité d'une parcelle abandonnée et contaminée.<br /> Humidité stagnante sur les parcelles.</p> +<p class="paragraph" blk="70">Contamination primaire : les ascospores ont une capacité de germination différente en fonction de l'humidité relative et de la température :<br /> 10°C : 24h d'humectation nécessaires<br /> 13°C 24°C : 7 12h d'humectation<br /> 27°C : 6h d'humectation<br /> 32°C et plus : pas de contamination</p> +<h3 class="title 3" blk="71">Modélisation (source IFV)</h3> +<p class="paragraph" blk="72">Sur la semaine passée Le risque potentiel s'est situé à un niveau fort sur la presque totalité du territoire, cependant, probablement à cause des températures basses, le modèle n'a enregistré aucune contamination durant la semaine écoulée.</p> +<p class="paragraph" blk="73">Dans les trois jours à venir Aucune évolution notable du risque potentiel n'est relevée dans les trois jours à venir. Avec les conditions climatiques sèches, aucune contamination n'est relevée pour ces trois prochains jours.</p> +<p class="paragraph" blk="74">Evaluation du risque : Malgré un risque potentiel fort, la probabilité de contaminations est actuellement nulle.</p> +<h5 class="title 5" blk="75">Risque faible</h5> +<h4 class="title 4" blk="76"> Consultez la fiche " black rot " du Guide de l'Observateur</h4> +<footer class="bottom page" blk="77">Bulletin de Santé du Végétal Nouvelle-Aquitaine / Edition Charentes<br /> Vigne N°02 du 09 Avril 2019<br /></footer> +<footer class="bottom page" blk="78">4/6<br /></footer> +<hr /><!-- =============== Page 5 =============== --> +<h2 class="title 2" blk="79"> Excoriose</h2> +<h3 class="title 3" blk="80">Observations</h3> +<p class="paragraph" blk="81">Quelques symptômes d'excoriose ont été observés sur jeunes feuilles dans des parcelles de cépages sensibles historiquement atteintes (Montils, Colombard ).</p> +<h3 class="title 3" blk="82">Seuil indicatif de risque</h3> +<p class="paragraph" blk="83">Le seuil indicatif de risque est de 20% des rameaux laissés à la taille contaminés par l'excoriose. Au-delà de ce seuil, la maladie peut avoir des conséquences sur le vignoble. Il faudra raisonner en fonction de l'historique parcellaire, de la sensibilité des cépages et des conditions climatiques au cours de la période de sensibilité.</p> +<p class="paragraph" blk="84">Evaluation du risque : Au vu des conditions sèches, pas de risque pour le moment, même pour les bourgeons ayant atteint le stade de sensibilité (éclatement).</p> +<h5 class="title 5" blk="85">Risque faible</h5> +<h4 class="title 4" blk="86"> Consultez la fiche " excoriose " du Guide de l'Observateur</h4> +<h1 class="title 1" blk="87">Ravageurs</h1> +<h2 class="title 2" blk="88"> Tordeuses</h2> +<h3 class="title 3" blk="89">Eléments de biologie</h3> +<figure><figcaption class="caption" blk="90">Cochylis<br />(Crédit photo INRA)</figcaption></figure> +<figure><figcaption class="caption" blk="91">Eudémis<br />(Crédit photo INRA)</figcaption></figure> +<p class="paragraph" blk="92">Les adultes issus des chrysalides hivernantes sortent en avril. Les mâles sortent avant les femelles au début du 1er vol. Il y a un décalage d'environ une semaine. La période de vol dure environ un mois. Les papillons sont nocturnes ou crépusculaires. Les Å“ufs sont déposés sur les boutons floraux et sur les feuilles. Ils sont pondus isolément. Une femelle peut pondre une cinquantaine d'Å“ufs au cours de sa vie qui dure une dizaine de jours. L'incubation des Å“ufs dure entre huit et quinze jours.</p> +<p class="paragraph" blk="93">La chenille présente un stade baladeur puis elle perfore les boutons floraux qu'elle agglomère par un fil soyeux : le glomérule. La nymphose dure une quinzaine de jours. Les adultes de 2ème génération sortent vers fin juin. Le vol peut s'étaler jusqu'à fin juillet. La ponte se fait isolément sur les baies. Après éclosion, la chenille perfore les baies et se développe à l'intérieur. Elle peut s'attaquer aux baies voisines. Les perforations des tordeuses sont très souvent à l'origine des premiers foyers de Botrytis cinerea. Pour l'Eudémis une 3ème génération est présente à l'approche des vendanges.</p> +<h2 class="title 2" blk="94">Observations</h2> +<p class="paragraph" blk="95">Aucune capture à ce jour.</p> +<h2 class="title 2" blk="96">Méthodes alternatives</h2> +<p class="paragraph" blk="97">Les mises en place des diffuseurs de phéromones utilisés pour la confusion sexuelle doivent être effectuées avant le démarrage du 1er vol.</p> +<h4 class="title 4" blk="98"> Consultez la fiche " tordeuses " du Guide de l'Observateur</h4> +<h2 class="title 2" blk="99"> Mange-bourgeons</h2> +<p class="paragraph" blk="100">De nouveaux bourgeons évidés ont été signalés dans quelques parcelles. L'intensité des dégâts reste faible. Le seuil de nuisibilité est de 15% des ceps présentant des symptômes.</p> +<h4 class="title 4" blk="101"> Consultez la fiche " mange-bourgeons " du Guide de l'Observateur</h4> +<footer class="bottom page" blk="102">Bulletin de Santé du Végétal Nouvelle-Aquitaine / Edition Charentes<br /> Vigne N°02 du 09 Avril 2019<br /></footer> +<footer class="bottom page" blk="103">5/6<br /></footer> +<hr /><!-- =============== Page 6 =============== --> +<h2 class="title 2" blk="104"> Escargots</h2> +<p class="paragraph" blk="105">A la faveur d'une météo nettement plus humide, les escargots ont repris leur activité et sont localement très présents dans les ceps. Il s'agit principalement de gros escargots des espèces Cornu aspersum et Helix lucorum.</p> +<figure><figcaption class="caption" blk="106">Escargots sur le cep<br />(Crédit photo M. Catania CA17)</figcaption></figure> +<h2 class="title 2" blk="107">Le Mémo de l'Observateur</h2> +<h3 class="title 3" blk="108">A faire :</h3> +<p class="paragraph" blk="109">Estimer le stade phénologique des parcelles suivies</p> +<p class="paragraph" blk="110">Compter les ceps attaqués par les mange-bourgeons</p> +<p class="paragraph" blk="111">Poser les pièges sexuels tordeuses</p> +<p class="paragraph" blk="112">Enregistrer les premières observations</p> +<h3 class="title 3" blk="113">Les protocoles correspondants sont disponibles ici :</h3> +<h5 class="title 5" blk="114">Suivi des pièges sexuels</h5> +<p class="paragraph" blk="115">Suivi des pièges alimentaires</p> +<p class="paragraph" blk="116">Suivi des témoins non traités et parcelles de référence</p> +<p class="paragraph" blk="117">Vous retrouverez tous les protocoles et tutoriels des suivis BSV en cliquant ici</p> +<p class="paragraph" blk="118">N'hésitez pas à nous demander le Guide de l'Observateur, également disponible en ligne</p> +<figure><figcaption class="caption" blk="119">Les structures partenaires dans la réalisation des observations nécessaires à l'élaboration du Bulletin de santé du végétal Nouvelle-Aquitaine - Vigne / Edition Charentes sont les suivantes : les Chambres d'Agriculture de la Charente et de la Charente Maritime, la Coopérative Agricole d'Achats en Commun et d'Approvisionnement (ÃŽle d'Oléron), la Coopérative Agricole de la Région de Cognac, la Coopérative Agricole Terre Atlantique, le Groupe Coopératif Océalia, la Coopérative Agricole du canton de Matha, la Coopérative des Vignerons de l'ÃŽle de Ré, Vitivista, le Groupe Isidore, les Ets Fortet-Dufaud, les Ets Soufflet Agriculture, les Ets Landreau et Fils, les Ets Piveteau, les Ets Nau, les Ets Niort Agricole, BGD Conseil, la FDCETA, la FREDON Poitou-Charentes, l'Institut Français de la Vigne et du Vin, la Station Viticole du BNIC et les Établissements d'enseignement agricole de Saintes, Jonzac, Barbezieux et l'Oisellerie.</figcaption></figure> +<p blk="120"><small class="misc">Ce bulletin est produit à partir d'observations ponctuelles réalisées sur un réseau de parcelles. S'il donne une tendance de la situation sanitaire régionale, celle-ci ne peut pas être transposée telle quelle à chacune des parcelles. La Chambre Régionale d'Agriculture Nouvelle-Aquitaine dégage donc toute responsabilité quant aux décisions prises par les agriculteurs pour la protection de leurs cultures. Celle-ci se décide sur la base des observations que chacun réalise sur ses parcelles et s'appuie le cas échéant sur les préconisations issues de bulletins techniques (la traçabilité des observations est nécessaire).</small></p> +<p blk="121"><small class="misc">" Action pilotée par le Ministère chargé de l'agriculture et le Ministère de l'Ecologie, avec l'appui financier de l'Agence Française de Biodiversité, par les crédits issus de la redevance pour pollutions diffuses attribués au financement du plan Ecophyto ".</small></p> +<footer class="bottom page" blk="122">Bulletin de Santé du Végétal Nouvelle-Aquitaine / Edition Charentes<br /> Vigne N°02 du 09 Avril 2019<br /></footer> +<footer class="bottom page" blk="123">6/6<br /></footer> +</body><!-- 2505 words --> +</html> diff --git a/corpusEvaluation/pdf2blocs-v1.1/als_vit_no15_2016-07-26_cle879161-1.html b/corpusEvaluation/pdf2blocs-v1.1/als_vit_no15_2016-07-26_cle879161-1.html new file mode 100644 index 0000000000000000000000000000000000000000..f55dd4c1c69dcee4c01ada29cde177f4a984b049 --- /dev/null +++ b/corpusEvaluation/pdf2blocs-v1.1/als_vit_no15_2016-07-26_cle879161-1.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<html lang="fr"> +<head><meta charset="utf-8" /> +<link rel="stylesheet" href="http://ontology.inrae.fr/bsv/html/bsv.css" /> +<link rel="stylesheet" href="bsv.css" /> +<title>26 juillet 2016 N°15</title> +</head> +<body> +<h1 class="doc title" blk="0">26 juillet 2016 N°15</h1> +<h2 class="title 2" blk="1">Point météo phénologie</h2> +<h1 class="title 1" blk="2">Viticulture</h1> +<h2 class="title 2" blk="3">et</h2> +<h3 class="title 3" blk="4">Météo et phéno</h3> +<p class="paragraph" blk="5">Fermeture atteinte en tout secteur</p> +<h3 class="title 3" blk="6">Mildiou et Oïdium</h3> +<p class="paragraph" blk="7">Bilan fermeture</p> +<h3 class="title 3" blk="8">Tordeuses</h3> +<p class="paragraph" blk="9">Bilan fermeture : pontes et perforations</p> +<h3 class="title 3" blk="10">Drosophiles</h3> +<p class="paragraph" blk="11">Etat des autres filières</p> +<h3 class="title 3" blk="12">Rais'Alsace</h3> +<p class="paragraph" blk="13">Attention fin des réunions le 27/07.</p> +<h4 class="title 4" blk="14">Réunion spéciale drosophiles et flavescence en août</h4> +<p class="paragraph" blk="15">Le stade de la fermeture est atteint à présent dans toutes les parcelles. Les premières baies verrées sont attendues au plus tôt pour cette fin de semaine en secteurs précoces. Les notations et pesées pour l'estimation du volume de récolte ont été réalisées les 18 et 19 juillet. Les notations laissent apparaitre une forte disparité entre les parcelles. Le mildiou est l'élément majeur qui pèse parfois lourd dans la balance en particulier dans les pinots gris et noirs. Le rendement moyen tous cépages est estimé à 76hl/ha, voir site du CIVA. Au niveau des précipitations, les niveaux sont très hétérogènes. Les dernières pluies ont été très localisées, de quelques gouttes à plus de 60 mm. Les chaleurs des derniers jours ont occasionnées les premiers symptômes d'échaudage.</p> +<h4 class="title 4" blk="16">Prochain bulletin après le15 août</h4> +<figure><figcaption class="caption" blk="17">Echaudage au 25/07</figcaption></figure> +<h2 class="title 2" blk="18">Mildiou / Oïdium</h2> +<p class="paragraph" blk="19">Bilan fermeture de la grappe FREDON/CAA Un bilan a été réalisé entre le 22 et le 27 juillet sur 80 parcelles environ au hasard dans l'ensemble du vignoble. 20 grappes sont notées par parcelle ainsi qu'une estimation de l'attaque de mildiou sur le feuillage. Situation Le mildiou est stable depuis deux semaines maintenant. De rares parcelles présentent encore du rot gris. Le rot brun est encore bien</p> +<p blk="20"><small class="misc">Chambre d'agriculture d'Alsace 2 rue de Rome CS 30022 Schiltigheim 67013 STRASBOURG<br />CEDEX Directeur de Publication : Laurent WENDLINGER</small></p> +<p class="paragraph" blk="21">présent avec parfois des pédicelles couverts de sporulation. Toutes les parties de grappes attaquées en pré ou post floraison sèchent et tombent.</p> +<figure><figcaption class="caption" blk="22">Mildiou et oïdium sur la même grappe</figcaption></figure> +<p class="paragraph" blk="23">Dans les parcelles prises au hasard, 82,5 à 85% d'entre elles présentent des attaques sur grappes selon le département. Presque la totalité exprime des symptômes foliaires. La fréquence d'attaque sur grappe présente une amplitude maximale de 0 à 100%. Pour l'oïdium, la situation est également stable. Les parcelles qui affichent des symptômes marqués sont liées à un défaut de protection. Dans les autres parcelles où la</p> +<p blk="24"><small class="misc">Animateurs : Chambre d'agriculture d'Alsace, FREDON Alsace<br />Participants : AB2F, Alsace Appro, Ampelys, Armbruster Vigne SARL, Cave de Beblenheim, Bestheim&Châteaux, Caves de Traenheim et Turckheim, CIVA, Coop d'Appro du Piémont, Hauller, IFV, Bulletin de Santé du Végétal n°00 du 6 février 2009 Page 2/3 Labo Gresser, SRAL, Viti Best, Viti.com, Wolfberger.</small></p> +<hr /><!-- =============== Page 2 =============== --> +<p class="paragraph" blk="25">protection a été maintenue avec soin, les attaques sont limitées à quelques baies. Moins de 5% de grappes sont attaquées. Analyse du risque La sensibilité de la grappe persiste jusqu'au stade début véraison pour le mildiou, mais elle baisse progressivement. Les dernières pluies sont potentiellement contaminatrices. Le risque actuel est la contamination du feuillage par le mildiou mosaïque, en particulier sur le haut du plan de palissage. Mais les jeunes feuilles des entre-cÅ“urs peuvent aussi subir ces attaques et devront être protégées jusqu'au stade début véraison. Il faut éviter tout risque de défoliation prématurée. La sensibilité de la grappe vis-à -vis de l'oïdium s'arrête à la fermeture de la grappe si la parcelle est saine. La présence actuellement de petites baies potentiellement sensibles, conduit à maintenir la vigilance, conjointement avec le mildiou. Surveillez en particulier les muscats, sylvaner, chardonnay et auxerrois.</p> +<h2 class="title 2" blk="26">Tordeuses</h2> +<p class="paragraph" blk="27">Situation Les vols trainent encore un peu pour les eudémis. Il n'y a aucune constante dans les données de piégeage. Les captures peuvent être nulles ou à l'inverse des prises régulières se poursuivent. On tend toutefois à une fin d'activité dans les 8 jours à venir. Au niveau des observations de pontes et</p> +<p class="paragraph" blk="28">perforations, le bilan sur les parcelles au hasard, hors réseau, sont homogènes. La pression est moyenne à faible. Moins de 3% des grappes présentent un ponte, une ponte avortée ou une perforation.</p> +<figure><figcaption class="caption" blk="29">Ponte fraiche et ponte avortée</figcaption></figure> +<p class="paragraph" blk="30">Analyse du risque L'activité de ponte tend vers la fin. Les parcelles protégées ne présentent pas de risque. Pour les parcelles non protégées, ce sont les pinots bien compacts qui sont les plus sensibles aux attaques de vers et au risque de foyer de botrytis.</p> +<p class="paragraph" blk="31">sont touchées par ce ravageur. Les pièges vont être mis en place pour le vignoble au cours de la quinzaine à venir. En parallèle, des suivis de baies et grappes sont prévus à partir du début de la véraison pour évaluer le risque de dégradation de la vendange. Dès toute évolution, vous serez informés de la situation.</p> +<h2 class="title 2" blk="32">Rais'Alsace</h2> +<p class="paragraph" blk="33">Les réunions de bout de parcelles s'arrêtent pour cette fin de juillet. Une réunion " drosophiles et flavescence dorée " aura lieu au cours du mois d'août en fonction de l'actualité et sur certains sites uniquement. La date vous sera communiquée.</p> +<h2 class="title 2" blk="34">Prochain bulletin</h2> +<p class="paragraph" blk="35"> après le 15 août.</p> +<p blk="36"><small class="misc">Action pilotée par le ministère chargé de l'agriculture, avec l'appui financier de l'Office national de l'eau et des milieux aquatiques, par les crédits issus de la redevance pour pollutions diffuses attribués au financement du plan Ecophyto.</small></p> +<h2 class="title 2" blk="37">Drosophiles</h2> +<p class="paragraph" blk="38">Situation Les autres filières subissent les attaques de drosophiles asiatiques. A l'heure actuelle ce sont les myrtilles, les prunes et les mûres qui</p> +<figure><figcaption class="caption" blk="39">Bulletin de Santé du Végétal p. 2/2</figcaption></figure> +</body><!-- 873 words --> +</html> diff --git a/src/py/README.md b/src/py/README.md index 6025736eaf20016f93473b94d21bfbb64b2f4fc4..3d00255193f82b0275226866d8216c7cc93ce797 100644 --- a/src/py/README.md +++ b/src/py/README.md @@ -54,6 +54,16 @@ un fichier pdf. un dictionnaire et des outils de recherche. PyEnchant est distribué sous la licence [LGPL](http://www.gnu.org/copyleft/lesser.html). +Enfin, pour la reconnaissance de tableaux, *pdf2blocks* utilise +[camelot-py](https://pypi.org/project/camelot-py/) +([documentation](https://camelot-py.readthedocs.io/en/master/index.html)). +Camelot peut s'appuyer sur *ghostscript* ou *poppler*. Nous avons bien sûr +choisi le second, qui nécessite [pdftopng](https://pypi.org/project/pdftopng/). + +*pdftopng* est sous licence +[GPLv2](https://raw.githubusercontent.com/vinayak-mehta/pdftopng/master/LICENSE) +et *camelot* est sous licence +[MIT](https://github.com/camelot-dev/camelot/blob/master/LICENSE). #### 1.4.1 pdftotext *pdftotext* est destiné à produire une sortie en texte brut, @@ -290,7 +300,40 @@ ont la structure suivante : - **top**, **left**, **width** et **height** : Les coordonnées du segment de texte dans la page. -### 2.3 Traitements +### 2.3 Détection des tableaux + +La version précédente *pdf2blocks* avait des difficultés à reconnaître +les tableaux. Or il existe plusieurs librairies dont la reconnaissance +de tableaux (et l'extraction de données) des fichiers pdf est +l'objectif. + +Parmi celles-ci nous avons choisi *camelot*, qui peut être appelée en +python, est très configurable et est actuellement maintenue. *Camelot* +peut de plus s'appuyer sur *poppler*, ce qui est dans la continuité +des dépendances de *pdf2blocks*. + +*Camelot* est appelé avec les options par défaut. Celles-ci recherchent +les tableaux en détectant des lignes horizontales et verticales +composant des bordures. + +Les options suivantes n'ont pas été utilisées : +- l'option **split-text=True**, qui recherche les cellules multi-lignes, +interfère et donne de très mauvais résultats avec les bulletins +multi-colonnes, +- l'option **flavor='stream'** renvoie trop de faux positifs, +- enfin, le très faible nombre de tableaux basés sur une atternance +de couleurs de fond rend l'option **process_background=True** +peu utilisable. + +Les options par défaut laissent passer nombre de faux-négatifs mais +de fait interfèrent peu en dehors des tableaux. +*Camelot* présente aussi l'avantage de renvoyer des valeurs qualitatives +associées aux résultats, ce qui permet de les sélectionner. +Ainsi, seuls les tableaux ayant une valeur **accuracy** supérieure* +à 90.0 et une valeur **whitespace** inférieure à 50.0 sont retenus. +Les tableaux n'ayant qu'une seule colonne sont ignorés. + +### 2.4 Traitements À ce point, nous avons trois listes, **blocks**, **fontspec** et **segments**, dont la structure est décrite ci-dessus. @@ -308,14 +351,22 @@ un ré-ordonnancement des blocs spécifique est effectué par *pdf2blocks*. Les chapitres suivants décrivent les traitements, effectués séquentiellement, sur les données issues des deux commandes *pdftotext* et *pdftohtml*. -#### 2.3.1 Détermination de la taille de la fonte par défaut +#### 2.4.1 Marquage des tableaux +Les blocs contenus dans les tableaux détectés par *camelot* +(identifiés grâce à leurs coordonnées) sont remplacés par un unique +bloc marqué comme étant un tableau. Il n'y aura pas d'autre détection +d'élément de structure sur ces blocs (puisqu'ils sont déjà identifiés +comme tableaux), et ils sont ignorés du reste des traitements (à +l'exception de l'ordonnancement des blocs). + +#### 2.4.2 Détermination de la taille de la fonte par défaut La taille de fonte par défaut est déterminée à partir de la liste **segments** et **fontspec**, c'est à dire à partir des sorties de *pdftohtml*. La taille retenue est celle du plus grand nombre de caractères associés à chaque taille de fonte de la liste **fontspec**. -#### 2.3.2 Détection des pieds de page +#### 2.4.3 Détection des pieds de page Les "pieds de page" d’un document sont une zone de texte répétée à chaque fin de page contenant par exemple le numéro de la page @@ -336,7 +387,7 @@ on teste la ligne précédente, et ainsi de suite. Les lignes détectées comme étant des bas de page ont la valeur BL_BOTTOM_PAGE dans leur attribut class. -#### 2.3.3 Détection des en-tête +#### 2.4.4 Détection des en-tête L'algorithme est similaire à la détection des pieds de page, mais au lieu de s'appliquer aux dernières lignes de chaque page, il considère les premières @@ -352,14 +403,15 @@ Les lignes détectées comme étant des en-tête ont la valeur BL_TOP_PAGE dans leur attribut class. -#### 2.3.4 Attribution de fontes aux blocs +#### 2.4.5 Attribution de fontes aux blocs Il s'agit d'attribuer l'*id* d'une fonte de **fontspec** à chaque ligne de la liste **blocks**. L'algorithme consiste, pour chaque ligne de bloc (élément de la liste **lines**), à calculer sa [distance de Levenshtein](https://en.wikipedia.org/wiki/Levenshtein_distance) -avec chaque segment de texte (élément de la liste **segments**). +avec chaque segment de texte (élément de la liste **segments**). Ce calcul +ne se fait qu'entre blocs et segments ayant une intersection (non vide). On attribue alors à la ligne la fonte du segment ayant le meilleur score (c'est à dire la distance la plus faible). @@ -368,14 +420,17 @@ renvoyées par *pdftohtml*, qui ne correspondent pas exactement aux lignes renvoyées par *pdftotext*. Par ailleurs, l'ordre des lignes dans une même page est parfois différent entre les deux outils. -Néanmoins, lorsqu'une ligne est reconnue à l'identique, elle est marquée et +Lorsqu'une ligne est reconnue à l'identique, elle est marquée et n'est plus utilisée dans l'algorithme. -À noter que cet algorithme n'est pas du tout optimisé. Il a été écrit pour -tester le principe, qui semble satisfaisant, mais il nécessite d'être -réécrit pour que son exécution soit plus rapide. +L'implémentation du calcul de l'intersection entre blocs et segments +a été ajoutée récemment (les sorties de *pdftohtml* n'utilisent pas les +mêmes grandeurs que celles de *pdftotext*), et une étude plus poussée +sur les conditions d'intersection serait à mener et pourrait conduire +à s'affranchir de la distance de Levenstein et à une optimisation +de cet algorithme. -#### 2.3.5 Ré-ordonnancement des blocs +#### 2.4.6 Ré-ordonnancement des blocs Avant de reconstituer la structure du document, un ré-ordonnancement des blocs est effectué, en deux temps : @@ -385,16 +440,17 @@ est effectué, en deux temps : Ceci est effectué page par page. -##### 2.3.5.1 Détection de colonnes +##### 2.4.6.1 Détection de colonnes La détection de colonnes ne considère que les blocs : +- qui ne sont pas marqués comme tableaux, - de plus de 3 lignes (ce seuil pouvant être modifié dans la constante MIN_LINES_IN_COLUMN_BLOCK), - dont la taille de fonte est inférieure ou égale à la taille de fonte par défaut, - et dont la longueur de ligne est au moins égal à un seuil (MIN_CAR_IN_COLUMN_BLOCK - actuellement 20), pour les distinguer des colonnes - de tableaux. + de tableaux qui n'auraient pas été détextés par *camelot*. Sur ces blocs : @@ -451,7 +507,7 @@ quand un titre pourrait déborder sur plusieurs colonnes mais qu'il est trop court, et de fait entièrement contenu dans une seule colonne. -##### 2.3.5.2 Parcours des blocs dans le sens de lecture +##### 2.4.6.2 Parcours des blocs dans le sens de lecture L'objectif est d'ordonner les blocs d'une même page dans leur sens de lecture, c'est à dire de haut en bas et de gauche à droite. @@ -521,19 +577,21 @@ dans un ordre de lecture estimé convenable. La liste **ordered_blocks** est enrichie des blocs marqués comme pieds de page. -#### 2.3.6 Reconstitution de la structure du document +#### 2.4.7 Reconstitution de la structure du document -Les en-tête et pieds de page ont déjà été identifiés. +Les en-tête et pieds de page ont déjà été identifiés, +ainsi que les tableaux. Les lignes ne comportant aucun caractère alphanumérique sont ignorées. L'algorithme de reconstitution de la structure du document se fait en deux étapes : -- La première étape cherche à classer des blocs en fonction de - caractéristiques. Certaines caractéristiques permettent de définir - des prototypes. - C'est à dire que la classe associée à un prototype est certaine. +- La première étape cherche à classer des blocs en fonction de leurs + caractéristiques. Par exemple on écrira qu'un bloc qui possède au moins + N caractères par ligne, qui fait au moins M lignes et dont la fonte + est la fonte la plus utilisée dans le document est un paragraphe. + Certaines caractéristiques permettent ensuite de définir des prototypes. - La seconde vise à identifier les blocs restants par des recherches de similarités avec les prototypes identifiés lors de la première étape. @@ -572,11 +630,11 @@ L'alignement des blocs s'entend modulo une constante exprimée en pixels et nommée VERTICAL_ALIGMENT_THRESHOLD. -##### 2.3.6.1 Détermination des classes de blocs par des règles +##### 2.4.7.1 Détermination des classes de blocs par des règles La caractérisation d'un bloc s'appuie sur une série de règles. Lorsqu'un bloc correspond à une des règles, on lui attribue une "classe" (titre, légende, -paragraphe, tableau, ...). On dira que le bloc est "marqué". +paragraphe, …). On dira que le bloc est "marqué". Certaines règles permettent de définir des blocs prototypes. C'est à dire que les caractéristiques du bloc permettent de déterminer sa classe sans ambiguïté. @@ -597,17 +655,6 @@ Voici la liste des règles pour déterminer les blocs prototypes : - sa plus longue ligne est supérieure à un seuil (PARAGRAH_LINE_SIZE, actuellement 40 caractères), - et s'il n'est ni centré ni aligné à droite. -1. Si un bloc a une proportion de caractères non-numériques inférieure - à un seuil (NUMBERING_THRESHOLD, actuellement 0,3), une taille de fonte - ne dépassant pas celle de la police par défaut et des lignes ne dépassant - pas un seuil de caractères (TABLE_LINE_SIZE, actuellement 25), alors - ce bloc est marqué BL_TABLE. - Lorsqu'un bloc est ainsi marqué, on teste les blocs non marqués qui le - précèdent et qui le suivent, en omettant la condition sur le nombre de - caractères non-numériques et en ramenant la condition de longueur de ligne - à la longueur moyenne de ligne. Ces blocs sont aussi marqués BL_TABLE, et on - continue de tester les blocs précédents et suivants tant que la condition - est remplie. 1. Les expressions régulières contenues dans la liste CAPTION_REGEX sont testées ("crédit photo", "photo :", ...). En cas de succès le bloc est marqué BL_CAPTION (légende). @@ -615,7 +662,7 @@ Voici la liste des règles pour déterminer les blocs prototypes : Voici la liste des règles pour déterminer d'autres classes sans générer de prototypes : - Les blocs marqués FLAG_VERTICAL sont marqués BL_MISC (miscellanous). -- Les blocs ne contenant pas de caractère alphanumériques sont ignorés ; +- Les blocs ne contenant pas de caractère alphanumérique sont ignorés ; il sont marqués BL_IGNORE. - Si on ne trouve aucune légende, d'autres règles plus souples sont testées: - la fonte est plus petite que la fonte par défaut, @@ -625,15 +672,15 @@ Voici la liste des règles pour déterminer d'autres classes sans générer de p des blocs dont la fonte est au moins de la taille de la fonte par défaut sont testées. En cas de succès, le bloc est marqué BL_PARAGRAPH. Ceci est dû au fait que les liens internet sont souvent en gras, ce qui - les classe en titres. Or on peut justement supposer qu'un lien html n'est + les classe en titres. Or on peut supposer qu'un lien html n'est jamais dans un titre. -Une première série de blocs est classés à l'aide de ces règles. +Une première série de blocs est classé à l'aide de ces règles. La hiérarchie des titres est estimée par une série d'opérations décrites dans la section suivante. -##### 2.3.6.1.1 Reconnaissance des titres +##### 2.4.7.2 Reconnaissance des titres Les règles qui précèdent s'appliquent, à quelques exceptions près, à des blocs dont la taille de fonte de caractères est au plus celle de la fonte par défaut. @@ -704,7 +751,7 @@ les opérations suivantes : pour les cinq classes de titre. -##### 2.3.6.3 Classification des blocs restant +##### 2.4.7.3 Classification des blocs restants Lors de la première phase (détermination par des règles), des blocs prototypes ont été identifiés pour certaines classes. De nouvelles données sont calculées @@ -771,7 +818,7 @@ Une seconde passe de calcul de scores a ensuite lieu pour déterminer le type des blocs restants. -### Écriture des résultats +### 2.5 Écriture des résultats La sortie des résultats s'effectue dans un fichier html. @@ -794,7 +841,7 @@ classes de blocs : | BL_TITLE_3 | \<h3\> | | BL_TITLE_4 | \<h4\> | | BL_TITLE_5 | \<h5\> | -| BL_TABLE | \<table\>. Les blocs BL_TABLE successifs constituent chacun une ligne,<br>et chaque ligne de ces blocs constitue une cellule. | +| BL_TABLE | \<table\>. Le contenu de la table est renvoyé par *camelot* | | BL_CAPTION | \<figure\>\<figcaption\> | | BL_BOTTOM_PAGE | \<footer\> | | BL_TOP_PAGE | \<header\> | @@ -818,10 +865,6 @@ effectués : ##### Remarques - BL_PARAGRAPH et BL_MISC sont tous deux écrits sous la balise \<p\>. -- La reconstitution des tableaux pourrait être enrichie et améliorée. - Ceci n'a pas été fait mais peut être envisagé, les algorithmes - d'identification des lignes et colonnes étant relativement similaires - à des traitements mis en oeuvre dans le reste du document. - Pour les en-têtes et les pieds de page de plusieurs lignes, les retour à la ligne sont préservés par le biais de balises \<br\>. - Les puces ne sont pas restituées au sein de balises \<ul\> et \<li\>. diff --git a/src/py/p2b_config.py b/src/py/p2b_config.py index 9607abd979b7cd91f6add4eab500a271d94a4a95..87c91568bde5826e9f659bcf6968bbb387879db9 100644 --- a/src/py/p2b_config.py +++ b/src/py/p2b_config.py @@ -5,7 +5,7 @@ CMD_PDFTOTEXT = '/usr/sbin/pdftotext' CMD_PDFTOHTML = '/usr/sbin/pdftohtml' DEBUG_PRINT = False -PRINT_CSS = False +PRINT_CSS = True ## Dictionaire : #DICT = enchant.Dict("fr_FR") diff --git a/src/py/p2b_functions.py b/src/py/p2b_functions.py index be9db6704198a3dffcca6a5eb55227b16b7776bd..d84d576e77f6abf1776d1fd1a5fd5a9dd7e81d3c 100644 --- a/src/py/p2b_functions.py +++ b/src/py/p2b_functions.py @@ -8,6 +8,9 @@ import re import subprocess from io import StringIO +# from tabula import read_pdf # Résultats pas terribles. +import camelot + from p2b_utils import levenshtein from p2b_config import * @@ -79,10 +82,11 @@ def get_pdftotext(filename): # Parse xml code and create block table. xml = o.decode('utf8') ## Quelques cas particuliers déjà rencontrés :-( - xml = re.sub(r">[]<",'>*<', xml) + ##xml = re.sub(r">[]<",'>*<', xml) Insuffisant. + xml = re.sub(r'[\x00-\x09\x0b-\x1f]', '', xml) root = None try: - root = ET.fromstring(xml) + root = ET.fromstring(xml) except Exception as e: return [] #root = ET.fromstring(xml) @@ -97,6 +101,9 @@ def get_pdftotext(filename): for page in doc: if (page.tag.endswith('page')): page_num += 1 + pwidth = float(page.get('width')) + pheight = float(page.get('height')) # We need it because + # camelot uses y coordinate from the bottom for fl in page: if (fl.tag.endswith('flow')): flow_num += 1 @@ -108,6 +115,7 @@ def get_pdftotext(filename): 'x_max': float(bloc.get('xMax')), 'y_min': float(bloc.get('yMin')), 'y_max': float(bloc.get('yMax')), + 'pageheight' : pheight, 'pagewidth' : pwidth, } first_line = True for line in bloc: @@ -129,7 +137,7 @@ def get_pdftotext(filename): nb_words = 0 no_space_pls = False for word in line: - if (word.tag.endswith('word')): + if (word.tag.endswith('word')) and word.text is not None: hword = float(word.get('yMax')) - float(word.get('yMin')) li['words'].append({'height': hword, 'text': word.text}) @@ -139,6 +147,7 @@ def get_pdftotext(filename): else: hspaces.append(float(word.get('xMin')) - last_x) last_x = float(word.get('xMax')) + #print("#### [%s]" % word.text) l = len(re.sub(r'\W', '', word.text)) if l > 0: nb_words +=1 @@ -230,7 +239,10 @@ def get_pdftotext(filename): if last_x <= 0: last_x = float(word.get('xMax')) hs = 0 - wo = word.text.strip() + if word.text is not None: + wo = word.text.strip() + else: + wo = "" else: hs = float(word.get('xMin')) - last_x last_x = float(word.get('xMax')) @@ -238,7 +250,10 @@ def get_pdftotext(filename): ltxt = ("%s %s" % (ltxt, wo)).strip() words_list.append({'height': hword, 'text': wo.strip()}) hword = -1 - wo = word.text.strip() + if word.text is not None: + wo = word.text.strip() + else: + wo = "" else: wo = "%s%s" % (wo, word.text.strip()) ltxt = ("%s %s" % (ltxt, wo)).strip() @@ -293,6 +308,8 @@ def get_pdftohtml(filename): for page in root: if (page.tag.endswith('page')): pg = int(page.get('number')) + pheight = int(page.get('height')) + pwidth = int(page.get('width')) for tg in page: if (tg.tag.endswith('fontspec')): fontspec.append({ @@ -316,6 +333,7 @@ def get_pdftohtml(filename): segments.append({'page': pg, 'font': fnt, 'top': top, 'left': left, 'width': width, 'height': height, + 'pagewidth' : pwidth, 'pageheight': pheight, 'text': li.strip() }) # Find font in fontspec @@ -325,6 +343,54 @@ def get_pdftohtml(filename): return { 'fonts': fontspec, 'segments': segments } +# +--------------------------------------------------------------+ +# | get_pdftohtml | +# +--------------------------------------------------------------+ +def get_tables(filename, list_pages): + """ +Returns a list containing tables found by camelot. Each element +is an instance of https://camelot-py.readthedocs.io/en/master/api.html#camelot.core.TableList + +It does a selection of results (accuracy > 90.0 and whitespace < 50.0). +It also doesn't select single column tables. + +It doesn't try the option process_background=True to avoid doing +the job twice, but maybee we should do it. + +We use poppler as a backend becaus we already use pdftohtml and +pdftotext. But ghostscript is faster. + """ + ACCURACY_THR = 90.0 + WHITESPACE_THR = 50.0 + + pdf_file = '%s.pdf' % os.path.splitext(filename)[0] + tables = [] + tl = [] + try: + #tl = camelot.read_pdf(pdf_file, backend="poppler", flavor='stream', pages="all") + #tl = camelot.read_pdf(pdf_file, backend="poppler", pages="all", split_text=True) + tl = camelot.read_pdf(pdf_file, backend="poppler", pages="all") + except: + # e.g : "PyPDF2.errors.PdfReadError: Could not read malformed PDF file" + pass # tl is [] so should return [] + for t in tl: + if (t.accuracy > ACCURACY_THR) and (t.whitespace < WHITESPACE_THR) \ + and (t.shape[1] > 1): + c = [] + tt = { 'cells': t.cells, 'page': t.page, 'table': t # Maybe Not necessary + } + for l in t.cells: c+= l + tt['x_min'] = min([x.x1 for x in c]) + tt['x_max'] = max([x.x2 for x in c]) + tt['y_min'] = min([x.y1 for x in c]) + tt['y_max'] = max([x.y2 for x in c]) + tables.append(tt) + #print("<!-- ") ## &&&&&&&&&&& V1.1 + #print(t.df) ## &&&&&&&&&&& V1.1 + #print("-->") ## &&&&&&&&&&& V1.1 + return tables + + # +--------------------------------------------------------------+ # | get_default_font_size | # +--------------------------------------------------------------+ @@ -382,14 +448,15 @@ def mark_page_btotp(pages, ndx, increment, MARK): if not end: for p in k: - # if p['blocks'][ndx]['class'] == BL_UNDEF: - pages[p]['blocks'][ndx]['class'] = MARK + if pages[p]['blocks'][ndx]['class'] != BL_TABLE: + pages[p]['blocks'][ndx]['class'] = MARK if len(pages) > 2: try: - if li == "".join([re.sub(r'[^a-zA-Z]', '', l['text']) \ - for l in pages[1]['blocks'][ndx]['lines']]): - pages[1]['blocks'][ndx]['class'] = MARK + if li == "".join([re.sub(r'[^a-zA-Z]', '', l['text']) \ + for l in pages[1]['blocks'][ndx]['lines']]): + if pages[1]['blocks'][ndx]['class'] != BL_TABLE: + pages[1]['blocks'][ndx]['class'] = MARK except Exception as e: pass # <--- Booooo : VERY BAD !!! ndx += increment @@ -433,6 +500,10 @@ def get_lines(segments, fontspec): lines = [] last_line = -2 li = '' + xmin = 0 + xmax = 0 + ymin = 0 + ymax = 0 fnt = {} page_num = segments[0]['page'] for txt in segments: @@ -445,10 +516,17 @@ def get_lines(segments, fontspec): fnt_no = f lines.append({ 'text': li.strip(), 'exact_match': False, # Used by guess_fonts() + 'xmin':xmin, 'xmax':xmax, 'ymin':ymin, 'ymax':ymax, + 'pagewidth' : txt['pagewidth'], + 'pageheight' : txt['pageheight'], 'most_used_font': fnt_no, 'nb_fonts': len(fnt), 'page': page_num}) li = txt['text'].strip() + xmin = txt['left'] + xmax = xmin + txt['width'] + ymin = txt['top'] + ymax = ymin + txt['height'] last_line = txt['line'] for fi1 in fnt.keys(): for fi2 in fnt.keys(): @@ -461,6 +539,14 @@ def get_lines(segments, fontspec): fnt = {} fnt[txt['font']] = len(li.strip()) else: + if txt['left'] < xmin: + xmin = txt['left'] + if txt['left'] + txt['width'] > xmax: + xmax = txt['left'] + txt['width'] + if txt['top'] < ymin: + ymin = txt['top'] + if txt['top'] + txt['height'] > ymax: + yamx = txt['top'] + txt['height'] if (is_ind_exp(txt['text'])): li = "%s%s" % (li, txt['text'].strip()) else: @@ -501,7 +587,19 @@ def guess_fonts(blocks, segments, fontspec): font_sel = -1 line_no = -1 for i in range(ndx_lines[bl['page']-1], ndx_lines[bl['page']]): - if (not lines[i]['exact_match']) and (len(lines[i]['text']) > 0): + ll = lines[i] + llxmin = bl['pagewidth'] * float(ll['xmin']) / float(ll['pagewidth']) + llxmax = bl['pagewidth'] * float(ll['xmax']) / float(ll['pagewidth']) + llymin = bl['pageheight'] * float(ll['ymin']) / float(ll['pageheight']) + llymax = bl['pageheight'] * float(ll['ymax']) / float(ll['pageheight']) + minright = min(l['x_max'], llxmin) + maxleft = max(l['x_min'], llxmax) + minbottom = min(l['y_max'], llymin) + maxtop = max(l['y_min'], llymax) + + if (not lines[i]['exact_match']) and (len(lines[i]['text']) > 0) \ + and (minright < maxleft) and (minbottom < maxtop): + #if (not lines[i]['exact_match']) and (len(lines[i]['text']) > 0): d = levenshtein(l['text'], lines[i]['text']) if (d == 0): min_dist = 0 @@ -509,6 +607,9 @@ def guess_fonts(blocks, segments, fontspec): font_sel = lines[i]['most_used_font'] lines[i]['exact_match'] = True line_no = i + #print("=====> (%s, %s ; %s, %s) <-> (%s, %s ; %s, %s)" % ( + # l['x_min'], l['x_max'], l['y_min'], l['y_max'], + # llxmin, llxmax, llymin, llymax)) break; score = float(d) / float(max(len(l['text']), len(lines[i]['text']))) if (score <= SIMILARITY_THRESHOLD): @@ -634,222 +735,40 @@ def compute_lrud(page_blocks): # len(b['lines']), b['x_min'], b['x_max'], b['y_min'], b['y_max'])) for ob in page_blocks: # ob : other block if ob is not b: - # &&&&&&&&&&&&& Ligne suivante pour la trace : - #str = '%s [%s, %s ; %s, %s]'%(ob['lines'][0]['text'][:10], ob['x_min'], ob['x_max'], ob['y_min'], ob['y_max']) # &&&&&&&&&&&&&&&&&&&&&&&&& - if ob['y_min'] < b['y_max'] and ob['y_max'] > b['y_min']: # Vertically aligned - #str = 'V: %s' % str # &&&&&&&&&&&&&&&&&&&&&&& if ob['x_min'] >= b['x_max']: # ob on right dist = ob['x_min'] - b['x_max'] if b['right'] < 0: b['right'] = dist b['right_block'] = ob - #print(" -> %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& elif b['right'] > dist: b['right'] = dist b['right_block'] = ob - #print(" -> %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& if ob['x_max'] < b['x_min']: # ob on left dist = b['x_min'] - ob['x_max'] if b['left'] < 0: b['left'] = dist b['left_block'] = ob - #print(" <- %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& elif b['left'] > dist: b['left'] = dist b['left_block'] = ob - #print(" <- %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& if ob['x_min'] <= b['x_max'] and ob['x_max'] >= b['x_min']: # Horizontally aligned - #str = 'H: %s' % str # &&&&&&&&&&&&&&&&&&&&&&& if ob['y_min'] >= b['y_max']: # ob under b dist = ob['y_min'] - b['y_max'] if b['down'] < 0: b['down'] = dist b['down_block'] = ob - #print(" v %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& elif b['down'] > dist: b['down'] = dist b['down_block'] = ob - #print(" v %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& if ob['y_max'] < b['y_min']: # ob on top of b dist = b['y_min'] - ob['y_max'] if b['top'] < 0: b['top'] = dist b['top_block'] = ob - #print(" ^ %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& elif b['top'] > dist: b['top'] = dist b['top_block'] = ob - #print(" ^ %s (%s)" % (str, dist)) # &&&&&&&&&&&&&&&&&&&&&& - - # &&&&&&&&&&&&&&&&&&&&&&&& remove to the end of the function: printing for debug - if True: return # An easy way to have following code not executed - seen = [] - for b in page_blocks: - if b['right'] >= 0 and b['left'] < 0 and b not in seen: - bl = [b] - while bl[-1]['right'] >= 0: - bl.append(bl[-1]['right_block']) - seen.append(bl[-1]) - for i in range(max([len(bb['lines']) for bb in bl])): - txt = '|' - for bb in bl: - if i >= len(bb['lines']): - txt += ' |' - else: - txt += (bb['lines'][i]['text'] + " ")[:15]+'|' - print(txt) - print() - -# +--------------------------------------------------------------+ -# | table_detection | -# +--------------------------------------------------------------+ -# A good test : -# python -u pdf2blocks.py ../../../../CorpusTESTs/corpus-alea/bsv/BSV_GRANDES_CULTURES_2018_No38_cle0b961f.pdf - -# table_detection tries to find tables within a page. -# Uses comute_lrud()'s left/right, ... -# Will try to detect succession of blocks in the same line having -# different blocks on 'top' or 'down', making it different to shitty-justified -# text (having same top or down). But cannot detect multi-column cells. -def table_detection(page_blocks): - for b in page_blocks: - if b['class'] == BL_UNDEF and b['right'] > 0 and b['down'] > 0: - t = [[b],] - line = 0 - pb = b - ## Let's fill the 1st line - while pb['right'] > 0 : - pb = pb['right_block'] - if pb['class'] == BL_UNDEF: - t[line].append(pb) - else: - break - keep_going = len(t[0]) > 1 - - #if keep_going: # &&&&&&&&&&&&&&&&&&&&& Effacer jusqu'à "là " - # print('-----------------------------') - # l = '|' - # for i in t[0]: - # l += ("%s " % i['lines'][0]['text'])[:10]+'|' - # print(l) # &&&&&&&&&&&&&&&&& "là " - - while keep_going: - prev_line = line - bd = t[prev_line][0]['down_block'] - n = 1 - line += 1 - if bd['class'] == BL_UNDEF: - t.append([bd]) - else : - keep_going = False - while keep_going and bd['right'] > 0 and n < len(t[prev_line]): - bd = bd['right_block'] - pb = t[prev_line][n] - n += 1 - keep_going = (pb['down'] > 0 and pb['down_block'] == bd \ - and bd['class'] == BL_UNDEF) - if keep_going: - t[line].append(bd) - - #if line < len(t): # &&&&&&&&&&&&&&&&& Effacer jusqu'à "là " - # l = '|' - # for i in t[line]: - # l += ("%s " % i['lines'][0]['text'])[:10]+'|' - # if not keep_going: - # l+= 'X' - # print(l) # &&&&&&&&&&&&&&&&& "là " - - if n < len(t[prev_line]): - keep_going = False - if not keep_going: - if line < len(t): - del t[line] - #elif n < len(t[prev_line]): # &&& Pas judicieux : à supprimer... - # print("Couic : %d" % n) # &&&&&&&&&&&&&&&&&&&& - # for l in range(line): - # del t[l][n:] # ... jusqu'ici. - - if keep_going: - # We have to check that every block of the last line has - # a down_block. Otherwise, we don't go further. - for bb in t[line]: - if keep_going: - keep_going = bb['down'] > 0 - - #if not keep_going: # &&&&&&&&&&&&&&&&&&&&&& - # print() - - html = None - - if len(t) > 1 and len(t[0]) > 1: - ## We've got a table candidate here !!!! - txt = '-----------------------------\n' # &&& txt is for debug printing. Can be removed. - html = '' # obsolete: '<table>\n' - for i in t: - html += ' <tr>\n' - l = '|' - for j in i: - html += ' <td>%s</td>\n' % j['lines'][0]['text'] - l += ("%s " % j['lines'][0]['text'])[:15]+'|' - txt += l + '\n' - html += ' </tr>\n' - #html += '</table>\n' : obsolete - print(txt) # &&&&&&&&&&&&&&&&&&&&&&&&&&&&&& - print() - - if len(t) == 1 and len(t[0]) > 1: - ll = [len(tt['lines']) for tt in t[0]] - if min(ll) == max(ll) and min(ll) > 1: # <- THIS {C/SH}OULD BE A PARAMETER - ## Another Candidate here !!!! - html = '' # obsolete : '<table>\n' - txt = '===================================\n' - for i in range(len(t[0][0]['lines'])): - html += ' <tr>\n' - l = '|' - for j in t[0]: - html += ' <td>%s</td>\n' % j['lines'][i]['text'] - l += ("%s " % j['lines'][i]['text'])[:15]+'|' - html += ' </tr>\n' - txt += l + '\n' - print(txt) # &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& - print() - #html += '</table>\n' : obsolete - del ll - - # &&&&&&&&&&& TODO : - # Si html est not None, alors on a un tableau. - # Ce tableau est décrit en html dans html, on va créer une clé dans le bloc. - # On pose t[0][0] qui sera le bloc qui contient TOUT le tableau. - # DONE: Il faut faire tous les blocs de la page pour changer tous les pointeurs - # vers un des blocs du tableau vers b. - # DONE: Il faut ajuster les x_min, x_max, y_min,... - # DONE: Puis mettre en BL_IGNORE tous les blocs qui composent - # le tableau, SAUF b. - # DONE: - Et aussi : Mà J du prototype !!! - if html is not None: - all_blocks = [] - for i in t: - for j in i: - all_blocks.append(j) - j['class'] = BL_TABLE - adjust_block_prototypes(j) - j['class'] = BL_IGNORE - b['x_min'] = min([bb['x_min'] for bb in all_blocks]) - b['y_min'] = min([bb['y_min'] for bb in all_blocks]) - b['x_max'] = max([bb['x_max'] for bb in all_blocks]) - b['y_max'] = max([bb['y_max'] for bb in all_blocks]) - b['class'] = BL_TABLE - b['html'] = html - #print(html) - #print('---------------------') - - for bb in page_blocks: - if bb not in all_blocks: - for k in ['left', 'right', 'top', 'down']: - if bb[k] > 0 and bb[('%s_block'%k)] in all_blocks: - bb[('%s_block'%k)] = all_blocks[0] - # +--------------------------------------------------------------+ @@ -895,7 +814,7 @@ def sort_blocks(blocks, columns, default_font_size): b['treat'] = (b['class'] == BL_IGNORE \ or b['class'] == BL_BOTTOM_PAGE \ or b['class'] == BL_TOP_PAGE \ - or b['class'] == BL_TABLE) + or (b['class'] == BL_TABLE and len(b['lines']) == 0)) if not b['treat']: nb_blocks += 1 @@ -1177,19 +1096,67 @@ def block_contains_keywords(block, list_of_regex): # +--------------------------------------------------------------+ # | guess_structure | # +--------------------------------------------------------------+ -def guess_structure(blocks, fontspec, def_size): +def guess_structure(blocks, fontspec, tables, def_size): + word_counter = re.compile(r'[a-zA-Z_À-ÿ]+', flags=re.U) pages = {} + blocks_to_be_removed = [] + blk_cel = 0 for bl in blocks: if pages.get(bl['page']) is None: pages[bl['page']] = { 'blocks':[] } if len(bl['lines']) == 1 and \ - len(re.sub(r'\W','', bl['lines'][0]['text']).strip()) == 0: - bl['class'] = BL_IGNORE + len(re.sub(r'\W','', bl['lines'][0]['text']).strip()) == 0: + bl['class'] = BL_IGNORE + bl['nb_words'] = 0 else: - bl['class'] = BL_UNDEF + bl['class'] = BL_UNDEF + + # Is it a table ? + ta = None + for t in tables: + if (t['page'] == bl['page']) \ + and (bl['pageheight']-t['y_max']-0.01 <= bl['y_min']) \ + and (bl['pageheight']-t['y_min']+0.01 >= bl['y_max']) \ + and (t['x_min']-0.01 <= bl['x_min']) \ + and (t['x_max']+0.01 >= bl['x_max']): + ta = t + break + if ta is not None: # The block is part of a table + if ta.get('has_block') is None: # Replace current block with table + ta['has_block'] = True + bl['class'] = BL_TABLE + bl['y_min'] = bl['pageheight']-ta['y_max'] + bl['y_max'] = bl['pageheight']-ta['y_min'] + bl['x_min'] = ta['x_min'] + bl['x_max'] = ta['x_max'] + bl['lines'] = [] + bl['nb_words'] = 0 + for lines in ta['cells']: + if len(lines) > 0: + li = { 'flags': FLAG_NONE, 'words': [],'text': '<tr>\n', + 'height': lines[0].y2-lines[0].y1, 'font':bl['font'], + 'x_min': lines[0].x1, 'x_max': lines[-1].x2, + 'y_min': lines[0].y1, 'y_max': lines[0].y2} + for cel in lines: + ttxt = cel.text.strip() + bl['nb_words'] += len(word_counter.findall(ttxt)) + li['text'] += '<td blk="c-%d">%s</td>\n' % (blk_cel, ttxt) + blk_cel += 1 + li['words'].append({'height': cel.y2-cel.y1, 'text': cel.text.strip()}) + li['text'] += '</tr>' + bl['lines'].append(li) + pages.get(bl['page'])['blocks'].append(bl) + else: # mark block to be removed. + bl['class'] = BL_TABLE + bl['nb_words'] = 0 + bl['lines'] = [] # If length is 0, won't be displayed + # It's not a table : + if bl['class'] == BL_UNDEF: + bl['nb_words'] = sum([len(word_counter.findall(l['text'])) for l in bl['lines']]) pages.get(bl['page'])['blocks'].append(bl) + nb_car = max_car = 0 min_just = min_right = min_center = min_left = bl['x_max'] max_right = max_center = max_left = bl['x_min'] @@ -1254,7 +1221,7 @@ def guess_structure(blocks, fontspec, def_size): # On en fait une deuxième basée sur la hauteur de ligne, # car la reconnaissance des lignes verticales ne fonctionne pas. # Du coup on ne le fait que pour les verticales. - if (bl['flags'] & FLAG_VERTICAL) != 0: + if ((bl['flags'] & FLAG_VERTICAL) != 0) and (len(bl['lines']) > 0): bl['font_size'] = sum([l['height'] for li in bl['lines']]) / float(len(bl['lines'])) if bl['font_size'] <= def_size + FONT_THRESHOLDS[FONT_TINY]: bl['font_class'] = FONT_TINY @@ -1281,7 +1248,6 @@ def guess_structure(blocks, fontspec, def_size): elif len(bl['lines']) > 2 and \ (max_right - min_just) < VERTICAL_ALIGMENT_THRESHOLD and \ (max_left - min_left) < VERTICAL_ALIGMENT_THRESHOLD: - #print("###> %d - %d - %d - %d - %d [%s]" % (max_right, min_just, max_left, min_left, bl['nb_lines'], bl['lines'][0]['text'])) bl['alignment'] = ALIGN_JUSTIFIED elif (max_left - min_left) < VERTICAL_ALIGMENT_THRESHOLD: bl['alignment'] = ALIGN_LEFT @@ -1294,6 +1260,7 @@ def guess_structure(blocks, fontspec, def_size): # - End loop on blocks + # Count number of blocks for each font for f in fontspec: f['nb_blocks'] = len([b for b in blocks if b['font']['id'] == f['id']]) @@ -1326,16 +1293,17 @@ def guess_structure(blocks, fontspec, def_size): # Reorder blocks first_block_tagged = False for p in pages.values(): - if len(p['blocks']) > 0: - compute_lrud(p['blocks']) - expand_blocks(p['blocks'], default_font['size']) - col = get_columns(p['blocks'], default_font['size']) - col.append(max(b['x_max'] for b in p['blocks'])) - p['blocks'] = sort_blocks(p['blocks'], col, default_font['size']) - if not first_block_tagged: - bl = next(b for b in p['blocks'] if b['class'] != BL_TOP_PAGE) - bl['flags'] |= FLAG_FIRST_BLOCK - first_block_tagged = True + if len(p['blocks']) > 0: + compute_lrud(p['blocks']) + expand_blocks(p['blocks'], default_font['size']) + col = get_columns(p['blocks'], default_font['size']) + col.append(max(b['x_max'] for b in p['blocks'])) + p['blocks'] = sort_blocks(p['blocks'], col, default_font['size']) + + if not first_block_tagged: + bl = next(b for b in p['blocks'] if b['class'] != BL_TOP_PAGE) + bl['flags'] |= FLAG_FIRST_BLOCK + first_block_tagged = True # - End loop on pages # ...but sometimes it works better after blocks sorting @@ -1350,15 +1318,11 @@ def guess_structure(blocks, fontspec, def_size): # Vertical text is considered misc until its font is identificated well for p in pages.values(): for bl in p['blocks']: - if bl['flags'] & FLAG_VERTICAL != 0: + if (bl['flags'] & FLAG_VERTICAL != 0) and (bl['class'] != BL_TABLE): bl['class'] = BL_MISC # Prototypes are not adjusted, because vertical text # is too different and should need different treatments. - # Tables should be easy to detect - for p in pages.values(): - table_detection(p['blocks']) - # Ignore blocks containing no character or number for p in pages.values(): for bl in p['blocks']: @@ -1416,28 +1380,6 @@ def guess_structure(blocks, fontspec, def_size): adjust_block_prototypes(bl) nb_caption += 1 - # Tables - ## First, we look for full of numbers blocks having small columns and - ## default or small fonts, then we'll look for small columns before - ## and after these ones. - for p in pages.values(): - for i,bl in enumerate(p['blocks']): - if bl['class'] == BL_UNDEF and \ - bl['not_numbers'] <= NUMBERING_THRESHOLD and \ - bl['max_line_size'] <= TABLE_LINE_SIZE and \ - bl['font_class'] <= FONT_DEFAULT: - bl['class'] = BL_TABLE - adjust_block_prototypes(bl) - for inc in [-1, 1]: - j = i+inc - while j >= 0 and j < len(p['blocks']) and \ - p['blocks'][j]['class'] == BL_UNDEF and \ - p['blocks'][j]['font_class'] <= FONT_DEFAULT and \ - p['blocks'][j]['block_size'] / p['blocks'][j]['nb_lines'] <= TABLE_LINE_SIZE: - p['blocks'][j]['class'] = BL_TABLE - adjust_block_prototypes(p['blocks'][j]) - j += inc - # Links (or "See also") ## Just look for http(s):// ## Won't use it as a style. We just avoid it being a title. @@ -1607,24 +1549,6 @@ def guess_structure(blocks, fontspec, def_size): else: b['score'] = None - - # Some adjustments : - ## 1. Table cannot be alone, except for table having 'html' (result of - ## table_detection). So an alone table having no 'html' will be MISC. - ## 2. Titles having size > TITLE_SIZE_LIMIT are marked paragraph - prev_table = 0 - prev_block = None - for p in pages.values(): - for b in p['blocks']: - if b['class'] == BL_TABLE: - prev_table += 1 - else: - if prev_table == 1: - if not prev_block.get('html'): - prev_block['class'] = BL_MISC - prev_table = 0 - prev_block = b - return pages # +--------------------------------------------------------------+ @@ -1644,6 +1568,7 @@ def text_tr(text): # | print_html | # +--------------------------------------------------------------+ def print_html(pages, fontspec, out=sys.stdout): + nb_words = 0 blocks = [] for p in pages.values(): for b in p['blocks']: @@ -1651,7 +1576,7 @@ def print_html(pages, fontspec, out=sys.stdout): print('<!DOCTYPE html>', file=out) print('<html lang="fr">', file=out) - print('<head><meta charset="utf-8">', file=out) + print('<head><meta charset="utf-8" />', file=out) if PRINT_CSS: print('<link rel="stylesheet" href="http://ontology.inrae.fr/bsv/html/bsv.css" />', file=out) print('<link rel="stylesheet" href="bsv.css" />', file=out) @@ -1678,6 +1603,8 @@ def print_html(pages, fontspec, out=sys.stdout): if txt[-1] == ' ': txt = txt[:-1] print("%s</title>" % text_tr(txt), file=out) + else: + print(' <title></title>', file=out) print("</head>", file=out) print("<body>", file=out) @@ -1692,32 +1619,26 @@ def print_html(pages, fontspec, out=sys.stdout): last_page = 1 while i < len(blocks): if blocks[i]['page'] != last_page: - print("<hr /><!-- ------------ Page %d ------------ -->" % blocks[i]['page'], file=out) + print("<hr /><!-- =============== Page %d =============== -->" % blocks[i]['page'], file=out) last_page = blocks[i]['page'] cl = blocks[i]['class'] if cl == BL_TABLE: + if len(blocks[i]['lines']) > 0: + nb_words += blocks[i]['nb_words'] if not DEBUG_PRINT: - print('<table border="1">', file=out) + print('<table border="1" blk="%d">' % i, file=out) elif blocks[i]['score'] is None: - print('<table border="1" font="%d">' % blocks[i]['font']['id'], file=out) + print('<table border="1" font="%d" blk="%d">' % (blocks[i]['font']['id'], i), file=out) else: - print('<table border="1" font="%d" score="%f">' % ( - blocks[i]['font']['id'], blocks[i]['score']), file=out) - while i < len(blocks) and blocks[i]['class'] == BL_TABLE: - if blocks[i].get('html'): - print(blocks[i]['html'], file=out) - else: - print(' <tr>', file=out) - for l in blocks[i]['lines']: - print(" <td>%s</td>" % text_tr(l['text']), file=out) - print(' </tr>', file=out) - i += 1 + print('<table border="1" font="%d" score="%f" blk="%d">' % ( + blocks[i]['font']['id'], blocks[i]['score'], i), file=out) + for li in blocks[i]['lines']: + print(li['text'], file=out) print('</table>', file=out) - if i < len(blocks): - i -= 1 elif cl != BL_IGNORE: txt = pre = post = '' + nb_words += blocks[i]['nb_words'] if PRINT_CSS: id_cl = ' class="%s"' % BLOCKS_CLASSES[cl] else: @@ -1726,18 +1647,26 @@ def print_html(pages, fontspec, out=sys.stdout): pre = '<figure>' post = '</figure>' if cl == BL_MISC: - pre = '<p>' - post = '</p>' - if not DEBUG_PRINT: - txt = '%s%s<%s%s>' % (txt, pre, BLOCK_TAGS[cl], id_cl) - #&& txt = '%s%s<%s%s>(%s, %s)' % (txt, pre, BLOCK_TAGS[cl], id_cl, blocks[i]['x_min'],blocks[i]['x_max']) - elif blocks[i]['score'] is None: - txt = '%s%s<%s%s font="%d">' % (txt, pre, BLOCK_TAGS[cl], - id_cl, blocks[i]['font']['id']) + post = '</p>' + if not DEBUG_PRINT: + txt = '%s<p blk="%d"><%s%s>' % (txt, i, BLOCK_TAGS[cl], id_cl) + elif blocks[i]['score'] is None: + txt = '%s<p font="%d" blk="%d"><%s%s>' % (txt, + blocks[i]['font']['id'], i, BLOCK_TAGS[cl], id_cl) + else: + txt = '%s<p font="%d" score="%f" blk="%d"><%s%s>' % (txt, + blocks[i]['font']['id'], blocks[i]['score'], i, + BLOCK_TAGS[cl], id_cl) else: - txt = '%s%s<%s%s font="%d" score="%f">' % (txt, pre, + if not DEBUG_PRINT: + txt = '%s%s<%s%s blk="%d">' % (txt, pre, BLOCK_TAGS[cl], id_cl, i) + elif blocks[i]['score'] is None: + txt = '%s%s<%s%s font="%d" blk="%d">' % (txt, pre, BLOCK_TAGS[cl], + id_cl, blocks[i]['font']['id'], i) + else: + txt = '%s%s<%s%s font="%d" score="%f" blk="%d">' % (txt, pre, BLOCK_TAGS[cl], id_cl, - blocks[i]['font']['id'], blocks[i]['score']) + blocks[i]['font']['id'], blocks[i]['score'], i) for l in blocks[i]['lines']: if (l['flags'] & (HAS_BULLET | IS_DESCRIPTION)) != 0 and \ @@ -1748,7 +1677,6 @@ def print_html(pages, fontspec, out=sys.stdout): l['text'])).split(" ")[0] word_to_test = re.sub(r'[^a-zA-Z\'’à âäéèêëïîôöùûüÀÂÄÉÈÊËÃÎÔÖÙÛÜ]', '', word_to_test) if len(word_to_test) > 0: - #print(">>> -> %s [%s]" % (word_to_test, DICT.check(word_to_test))) if DICT.check(word_to_test): txt = "%s%s " % (txt.strip()[:-1], l['text']) else: @@ -1763,7 +1691,6 @@ def print_html(pages, fontspec, out=sys.stdout): l['text'])).split(" ")[0] word_to_test = re.sub(r'[^a-zA-Z\'’à âäéèêëïîôöùûüÀÂÄÉÈÊËÃÎÔÖÙÛÜ]', '', word_to_test) if len(word_to_test) > 0: - #print(">>> %s [%s]" % (word_to_test, DICT.check(word_to_test))) if DICT.check(word_to_test): txt = "%s%s " % (txt.strip()[:-1], l['text']) else: @@ -1776,26 +1703,23 @@ def print_html(pages, fontspec, out=sys.stdout): txt = "%s%s " % (txt.strip(),BLOCK_ENDLINES[cl]) if l == blocks[i]['lines'][-1]: txt = txt.strip() - # &&&&&&&&&& Tester les césures - #if l == blocks[i]['lines'][-1]: - # txt = "%s%s" % (txt, l['text']) - #elif BLOCK_ENDLINES[cl] == '': - # txt = "%s%s " % (txt, l['text']) - #else: - # txt = "%s%s%s" % (txt, l['text'], BLOCK_ENDLINES[cl]) if txt[-1] == ' ': txt = txt[:-1] print("%s</%s>%s" % (text_tr(txt), BLOCK_TAGS[cl], post), file=out) else: # cl == BL_IGNORE: - txt = "<!-- " + txt = "<div class=\"unknown\">" + txt_len = 0 for l in blocks[i]['lines']: - txt = "%s%s " % (txt, l['text']) - print("%s-->" % text_tr(txt), file=out) + txt_len += len(l['text'].strip()) + txt = "%s%s " % (txt, l['text']) + if txt_len > 0: + print("%s</div>" % text_tr(txt), file=out) i += 1 - print("</body>", file=out) + print("</body><!-- %d words -->" % nb_words, file=out) print("</html>", file=out) + return nb_words # --------------------------------------------------- # returns the result in a string @@ -1803,6 +1727,11 @@ def print_html(pages, fontspec, out=sys.stdout): def get_pdf2html(filename): blocks = get_pdftotext(filename) p2h = get_pdftohtml(filename) + page_list = [] + for bl in blocks: + if bl['page'] not in page_list: + page_list.append(bl['page']) + tableaux = get_tables(filename, page_list) if p2h is None: return ' ' fontspec = p2h['fonts'] @@ -1810,7 +1739,7 @@ def get_pdf2html(filename): default_font_size = get_default_font_size(fontspec) guess_fonts(blocks, segments, fontspec) - pages = guess_structure(blocks, fontspec, default_font_size) + pages = guess_structure(blocks, fontspec, tableaux, default_font_size) s = StringIO() - print_html(pages, fontspec, s) - return str(s.getvalue()) + nb_words = print_html(pages, fontspec, s) + return str(s.getvalue()), nb_words diff --git a/src/py/pdf2blocks.py b/src/py/pdf2blocks.py index ff23ea6b39db22df50b0f6528dda29fbddc4e969..726709ba8416cdd592ebdf8f3e108da314a370ee 100644 --- a/src/py/pdf2blocks.py +++ b/src/py/pdf2blocks.py @@ -11,5 +11,5 @@ from p2b_functions import get_pdf2html if (len(sys.argv) < 1): print("-U-> Usage : python pdf2blocks.py <fichier_pdf>") sys.exit(-1) - -print(get_pdf2html(sys.argv[1]), end='') +html, dummy = get_pdf2html(sys.argv[1]) +print(html, end='')