Commit 0cda4bef authored by Bernard Stephan's avatar Bernard Stephan
Browse files

Remise à la version telle que laissée par Tayeb

parent 4e2bf02f
No related merge requests found
Showing with 0 additions and 477 deletions
+0 -477
= Analyse des résultats de pdf2blocs
Stéphan Bernard <stephan.bernard@irstea.fr>
== Ordonnancement des blocs
Le premier élément qui saute aux yeux est un problème dans l'ordre des blocs
de la sortie. Par exemple, la page suivante :
image:img/01_page.png[Page de BSV]
reçoit les blocs dans l'ordre suivant (après suppression d'éléments parasites) :
image:img/02_page_blocs.png[Ordre de lecture]
[NOTE]
Lier un paragraphe à son titre devient difficile. Il n'est donc pas permis
de déduire une relation implicite d'une description qui ferait référence
à l'élément cité comme titre.
== Gestion des caractères gras et indentation
Deux erreurs sont observées sur les paragraphes suivants :
image:img/03_paragraphe.png[Ordre de lecture]
La majeure partie du texte se retrouve dans deux blocs successifs :
----
Nos stations n’ont pas enregistré de pluie mais des averses orageuses ont
localement été observées entre samedi 14 et dimanche 15/07. Elles ont pu
provoquer des contaminations mais, avec le temps sec, leur expression
devrait être limitée.
----
et
----
encore complètement atteinte.
tardives avec les pluies annoncées pour la fin de semaine.
----
[NOTE]
Les portions de phrase manquantes n'ont pas été retrouvées dans le document
(pas même faisant l'objet d'un autre bloc).
Deux choix sont probablement à l'origine de ce phénomène :
* L'absence d'espace vertical entre les deux paragraphes n'a pas permis
d'interpréter la première ligne du second paragraphe comme étant une
première ligne, et son non-alignement dû à l'indentation l'a exclue
de l'ensemble (exclusion destinée à gérer le multi-colonnes).
* La mise en gras de texte en cours de ligne («Soyez donc vigilant»)
faisant l'objet d'un changement de police de caractère a exclu cette
portion du bloc, mais la ligne suivante étant correctement alignée,
le changement de police de caractère n'est pas pris en compte.
[NOTE]
À d'autres endroits du document la mise en gras n'a pas provoqué de
sortie de bloc. Le phénomène est donc un peu plus complexe à interpréter.
Notes à propos du code de Tayeb
===============================
Stéphan Bernard <stephan.bernard@irstea.fr>
Tests
-----
Récupération d'un jeu de 195 BSVs sur la viticulture, dans
`/home/phan/Boulot/Ontology/BSV/tmp/viti`. Création d'une archive
dans le répertoire parent, pour pouvoir se permettre d'y mettre le zbeul.
Exécution du code de Tayeb :
[source,sh]
-------------------------
java -jar pdftoxml.jar ../tmp/viti/
-------------------------
Problèmes rencontrés
~~~~~~~~~~~~~~~~~~~~
Erreur d'exécution
^^^^^^^^^^^^^^^^^^
Pour le fichier `BSV_viti_bilan_2018_cle8556d4.pdf`, isolé dans un répertoire
`FaitPlanter`. Il semblerait que le xml généré par pdf2html ne soit pas
consistant. À creuser.
Problème avec les caractères gras
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Rencontré dans le fichier `20180801_LOR_BSV_Viticulture_cle857461.pdf`
en page 2 (les retours à la ligne du fichier pdf sont ici matérialisés par
un nouveau paragraphe) :
====
La coloration des baies a débuté timidement sur les parcelles du réseau. Elles se sont également ramollies et vont
maintenant entrer en maturation. Le stade moyen observé est compris entre L33 « fermeture » et *L 35 « début*
*véraison ».*
En 2017, la fermeture s’était achevée vers le 7 aout avec la véraison.
====
La partie en gras en fin de ligne a disparu, mais "véraison »", qui est en gras
est présent, sans doute parce que le mot est en début de ligne.
[source,xml]
----
<bloc page="2" top="609" left="59" height="17" width="777" font="0" lignes="4">
<text top="609" left="59" width="777" height="17" font="0">La coloration (…) vont</text>
<text top="630" left="59" width="681" height="17" font="0">maintenant (…) compris entre L33 « fermeture » et</text>
<text top="652" left="59" width="79" height="17" font="9">véraison ».</text>
<text top="673" left="59" width="466" height="17" font="0">En 2017, la fermeture (…)).</text>
</bloc>
----
Diagnostic
++++++++++
Très probablement, le changement de _font_ dû au gras (on passe de `font="0"`
à `font="9"`), avec un non-alignement (autre valeur de `left`) en est la cause.
Voici les _font_ correspondantes :
[source,xml]
----
<fontspec id="0" size="14" family="ABCDEE+Calibri" color="#000000"/>
<fontspec id="9" size="14" family="ABCDEE+Calibri,Bold" color="#000000"/>
----
Si la mise en gras n'était pas en fin de paragraphe, la suite de la ligne,
à partir du premier caractère non gras, devrait être escamoté.
Solutions
+++++++++
Compliquées. Reconnaître qu'une _font_ est juste une mise en gras est possible,
mais le fichier généré par pdf2html créant deux blocs de texte du fait du
changement de police de caractères, on se retrouve toujours avec une portion
de texte non alignée.
Intéressant :
-------------
Regarder la sortie de
[source,sh]
----
pdftotext -bbox-layout ../tmp/viti/20180801_LOR_BSV_Viticulture_cle857461.pdf
----
......@@ -31,9 +31,3 @@ La fonction qui permet de construire les blocs est la fonction bar. Elle prend e
-> src/ressources/mots_vides.txt : liste de mots vides en français ;
-> src/ressources/ponctuation.txt : liste de caractères de ponctuation.
### Idées, TODO, ...
- Plutôt que d'enlever les pied de page, les marquer.
- De même, marquer le "texte petit".
- Dans les blocs, passer de ligne à ligne à phrase par
phrase.
\ No newline at end of file
img/01_page.png

352 KB

img/02_page_blocs.png

340 KB

img/03_paragraphe.png

30 KB

# https://docs.python.org/fr/3/library/xml.etree.elementtree.html#module-xml.etree.ElementTree
#
# Lit les fichiers xml générés par pdftotext :
# pdftotext -bbox-layout ../tmp/viti/20180801_LOR_BSV_Viticulture_cle857461.pdf
# et fait une sortie destinées à être lue rapidement sur un terminal,
# avec une délimitation des blocs.
#
import xml.etree.ElementTree as ET
import os
import sys
import re
# https://unix.stackexchange.com/questions/238180/execute-shell-commands-in-python
import subprocess
### Parameters
CMD_PDFTOTEXT = '/usr/sbin/pdftotext'
CMD_PDFTOHTML = '/usr/sbin/pdftohtml'
### Entering MAIN process
#### Getting pdf filename as a parameter.
if (len(sys.argv) < 1):
print("-U-> Usage : python analyse.py <fichier_pdf>")
sys.exit(-1)
#print('Parsing %s' % sys.argv[1])
basename = os.path.splitext(sys.argv[1])[0]
#### Calling pdftotext command and getting its standard output
cmd = [CMD_PDFTOTEXT, '-bbox-layout', '-eol', 'unix', '%s.pdf' % basename, '-']
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
o, e = proc.communicate()
if (proc.returncode != 0):
print('-S-> Command pdftotext returned an error :')
print(' ' + e.decode('utf8'))
sys.exit(-2)
xml = o.decode('utf8')
root = ET.fromstring(xml)
#### Extract xml to lists and dictionaries for faster and easier access.
#
# Data format :
# flow=[{page, blocks = [{page, lines:[{height, text}]}]
# Rq : page is redundant but for now we don't know which is the best
#
page_num = 0
flow = []
for body in root:
if (body.tag.endswith('body')):
for doc in body:
if (doc.tag.endswith('doc')):
for page in doc:
if (page.tag.endswith('page')):
page_num += 1
for fl in page:
if (fl.tag.endswith('flow')):
blocks = []
for bloc in fl:
if (bloc.tag.endswith('block')):
bl = {'page':page_num, 'lines':[]}
bwords = 0
bcars = 0
for line in bloc:
if (line.tag.endswith('line')):
h = float(line.get('yMax')) - float(line.get('yMin'))
li = ''
lwords = 0
last_nbcar = 0
last_h = 0
for word in line:
if (word.tag.endswith('word')):
hword = float(word.get('yMax')) - float(word.get('yMin'))
if ((hword != last_h)
and (last_nbcar < 2)):
last_h = hword
li = "%s%s" % (li, word.text)
else:
li = "%s %s" % (li, word.text)
last_nbcar = len(word.text)
lwords += 1
bl['lines'].append({
'height':h,
'text':li.strip(),
'nb_cars': len(li.strip()),
'nb_words':lwords})
bwords += lwords
bcars += len(li.strip())
bl['nb_words'] = bwords
bl['nb_cars'] = bcars
blocks.append(bl)
flow.append({'page':page_num, 'blocks':blocks})
#### Now, calls pdftohtml to improve font attributes
cmd = [CMD_PDFTOHTML, '-xml', '-i', '-stdout', '%s.pdf' % basename]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
o, e = proc.communicate()
if (proc.returncode != 0):
print('-S-> Command pdftohtml returned an error :')
print(' ' + e.decode('utf8'))
sys.exit(-2)
xml = o.decode('utf8')
root = ET.fromstring(xml)
#### Extracts font information (id, size, family, color)
#### and the link between lines of text and their font.
fontspec = []
p2x_text = []
for page in root:
if (page.tag.endswith('page')):
pg = int(page.get('number'))
for tg in page:
if (tg.tag.endswith('fontspec')):
fontspec.append({
'id': int(tg.get('id')),
'size': int(tg.get('size')),
'family': tg.get('family'),
'color': tg.get('color')
})
elif (tg.tag.endswith('text')):
fnt = int(tg.get('font'))
while (tg.text is None) and (len(tg) > 0):
tg = tg[0] # remove html style tags (like <b>, …)
if (tg.text is not None):
li = "%s" % (tg.text)
if (len(li.strip()) > 0):
p2x_text.append({
'page': pg,
'font': fnt,
'text': li.strip()
})
#### Try to find fontspec of flow's lines
###### 1. By line recognition
for fl in flow:
for bl in fl['blocks']:
for li in bl['lines']:
nocc = 0
fo = 0
for ligne in p2x_text:
if (ligne['text'] == li['text']) and (ligne['page'] == fl['page']):
nocc += 1
if (nocc == 2) and (ligne['font'] == fo):
nocc = 1
fo = ligne['font']
if (nocc == 1):
li['font'] = fo
else:
li['font'] = None
###### 2. Block uniformization
for fl in flow:
for bl in fl['blocks']:
for li in bl['lines']:
if (li['font'] is None):
h = round(li['height'])
fnt = None
for li2 in bl['lines']:
if (fnt is None) \
and (round(li2['height']) == h) \
and (li2['font'] is not None):
fnt = li2['font']
if (fnt is not None):
li['font'] = fnt
#### Page bottom detection
pb = 'dummy'
if (flow[-1]['page'] == 1): pb = None
while (pb is not None):
pb = None
last_lines = []
last_read_page = flow[0]['page']
last_read_line = 'Foo'
for fl in flow:
if (fl['page'] != last_read_page):
last_lines.append(re.sub(r'[^a-zA-Z]', '', last_read_line))
last_read_line = fl['blocks'][-1]['lines'][-1]['text']
last_read_page = fl['page']
last_lines.append(re.sub(r'[^a-zA-Z]', '', last_read_line))
### Is last_lines filled with the same string ?
pb = last_lines[0]
for li in last_lines[1:]:
if (pb is not None) and (pb != li): pb = None
### Yes, so mark these lines to be removed
if (pb is not None):
print("#####> %s" % pb)
last_read_page = flow[0]['page']
last_read_flow = flow[0]
for fl in flow[1:]:
if (fl['page'] != last_read_page):
print(' xxx> %s' % last_read_flow['blocks'][-1]['lines'][-1]['text'])
del last_read_flow['blocks'][-1]['lines'][-1]
if not last_read_flow['blocks'][-1]['lines']: # ie it's empty
del last_read_flow['blocks'][-1]
#print('****> %d' % len(last_read_flow['blocks']))
#if last_read_page == 1: print(' **> %s' % flow)
if not last_read_flow['blocks']:
flow.remove(last_read_flow)
#if last_read_page == 1: print(' ··> %s' % flow)
last_read_flow = fl
last_read_page = fl['page']
print(' xxx> %s' % last_read_flow['blocks'][-1]['lines'][-1]['text'])
del last_read_flow['blocks'][-1]['lines'][-1]
if not last_read_flow['blocks'][-1]['lines']: # ie it's empty
del last_read_flow['blocks'][-1]
#print('****> %d' % len(last_read_flow['blocks']))
if not last_read_flow['blocks']:
flow.remove(last_read_flow)
#### Calcultate some stats
font_sizes = {}
pipe = []
bl_num = 0
fl_num = 0
for fl in flow:
for bl in fl['blocks']:
for li in bl['lines']:
h = round(li['height'])
if (pipe == []):
pipe.append(h)
else:
#if (h != pipe[-1]):
pipe.append(h)
if (font_sizes.get(h) is None):
font_sizes[h] = {'nb_lines':1, 'nb_cars':li['nb_cars'],
'nb_words':li['nb_words'], 'blocks':[bl_num],
'flows':[fl_num]}
else:
font_sizes[h]['nb_lines'] += 1
font_sizes[h]['nb_cars'] += li['nb_cars']
font_sizes[h]['nb_words'] += li['nb_words']
if (font_sizes[h]['blocks'][-1] != bl_num):
font_sizes[h]['blocks'].append(bl_num)
if (font_sizes[h]['flows'][-1] != fl_num):
font_sizes[h]['flows'].append(fl_num)
bl_num += 1
fl_num += 1
#### Choose "normal" fontsize.
print('')
normal_font = 0
nf_nword = 0
for ft in sorted(font_sizes.keys()):
f = font_sizes.get(ft)
if (f['nb_words'] > nf_nword):
normal_font = ft
nf_nword = f['nb_words']
#### Prints p2x_text content
for fnt in fontspec:
print(fnt)
#print('=============================================>')
#for li in p2x_text:
# print('[p. %d][%d] %s' % (li['page'], li['font'], li['text']))
print('<=============================================')
print('')
#### Prints font stats
print('')
for ft in sorted(font_sizes.keys()):
f = font_sizes.get(ft)
print("[%d] ====> %d flows, %d blocks, %d lines, %d words, %d cars" %(ft,
len(f['flows']), len(f['blocks']),
f['nb_lines'], f['nb_words'], f['nb_cars']))
print('')
print('---> %d' % normal_font)
print('')
print('=============================================')
#### Prints flow content, but just for normal and bigger than normal text.
#for fl in flow:
# print('')
# print('[p. %d]' % fl['page'])
# for bl in fl['blocks']:
# h = 0.0;
# for li in bl['lines']: h += li['height']
# n = len(bl['lines'])
# print(' ---------------------------------------> [%d]' % round(h/n))
# for li in bl['lines']:
# if (li['font'] is not None):
# print(' [%2d] (%d) %s' % (li['font'], round(li['height']), li['text']))
# else:
# print(' (%d) %s' % (round(li['height']), li['text']))
# print(' <---------------------------------------')
BIG_ONLY = True
print('')
nb_blocks = 0
for fl in flow:
if (nb_blocks > 0):
print('')
#print('[p. %d]' % fl['page'])
nb_blocks = 0
for bl in fl['blocks']:
h = 0.0;
for li in bl['lines']: h += li['height']
n = len(bl['lines'])
h = round(h/n)
if (h >= normal_font) or not BIG_ONLY:
nb_blocks += 1
print(' <---------------------------------------- (p. %d)' % bl['page'])
for li in bl['lines']:
if (li['font'] is not None):
print(' [%2d] (%d) %s' % (li['font'], round(li['height']), li['text']))
else:
print(' (%d) %s' % (round(li['height']), li['text']))
if (nb_blocks > 0):
print(' ---------------------------------------->')
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment