|
gonzo.d
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

16. Juni 2012 01:51
Hej forum, ich möchte aus einem 500 Seiten-pdf-Dokument die Tabellen extrahieren. Dazu wandle ich bisher mit | pdftotext -layout -nopgbrk -enc 'UTF-8' "quelle.pdf" "ziel.txt"
|
das pdf in txt um. Als Mensch kann ich Tabellen anhand des layouts erkennen, kann mir das auch mit sed oder awk oder was andrem gelingen? Beispiele für den Inhalt: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | Ersatzbeschaffung von Endgeräten und Druckern
54060 012 Dienstleistungen für die verfahren- 4.500 4.500 4.500 4.993,32
A09 sunabhängige IuK-Technik
1.500,0 EUR werden künftig bei 54085 nachgewiesen.
Insbesondere Ausgaben für Landeslizenzen, sonstige Software und den Internetzugang der Abteilung
Summe Maßnahmegruppe 31 8.000 8.000 8.000 7.640,01
Stand: BVV-Vorlage 08.03.2012 Seite 71 von 503
Spandau 3320
MG 32 2012/2013
Geschäftsbereich 2
Beträge in EURO
Titel Fkt Bezeichnung Ansatz Ansatz Ansatz Ist (Rest/R)
Kb
2012 2013 2011 2010
MG Ausgaben für verfahrensab-
32 hängige IuK
(neu)
54085 012 Dienstleistungen für die verfah- 1.500 1.500 1.500 1.664,43
(neu) A09 rensabhängige IuK-Technik
1.500,0 EUR wurden bislang bei 54060 nachgewiesen.
Insbesondere Ausgaben für Landeslizenzen und sonstige Software
Summe Maßnahmegruppe 32 1.500 1.500 1.500 1.664,43
Gesamtausgaben 552.900 550.400 585.800 526.180,12
Prozentuale Veränderung -5,6 % -0,5 %
Abschluss Kapitel 3320
111- Verwaltungseinnahmen, Einnah- 26.000 26.000 13.000 30.808,72
186 men aus Schuldendienst und der-
gleichen
351- Besondere Finanzierungseinnah- 1.000 1.000 1.000 1.200,00
389 men
Gesamteinnahmen 27.000 27.000 14.000 32.008,72
|
Von vorstehendem Text möchte ich z.B. die Zeilen 21-23 als Überschrift (das könnte ich weiter oben einmal extrahieren und als Spaltentitel verwenden) und darunter die Zeilen 3-4, 10, 24-26, 28-29, 35, 37-38, 44-49 behalten. Das txt-file möchte ich in calc importieren und zum Schluß soll es eine lange Tabelle mit Spaltenüberschriften sein. Nun meine Fragen:
1) Ist der Weg pfiffig oder gibt es noch einen anderen Weg (nich c&p)?
2) bekomme ich das mit sed, awk und Euch hin?
3) macht es überhaupt Sinn? cheerio g
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

16. Juni 2012 02:42
Im Grunde müsstest Du die Dokumente mit einem PDF-Parser zerlegen und dabei die richtigen Objekte (!) abgreifen. Denn wenn Du zuerst die ganzen Strukturen schlachtest, und danach aus dem Kontext versuchst, die Geschichte wieder zu rekonstruieren, dann schaffst Du Dir ja sinnlose Arbeit. Unter dem Stichwort finde ich beim ersten Blättern diesen PDF-Parser ... ich habe aber keine Ahnung, ob es da noch was besseres gibt. (wie sowas mit einem OOo-Dokument geht, das weiß ich. PDF hat ähnliche Strukturen, aber mit PDF habe noch nichts weiter gemacht) Sorry, dass ich Dir nicht mehr als diesen Hinweis geben kann. LG, track
|
|
gonzo.d
(Themenstarter)
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

16. Juni 2012 18:51
Hej track, hab mir den parser mal angesehen, sowohl aus dem gewollten Dokument als auch einem Test-Dokument mit wirklichen Tabellen in der Quelle kommt nix sinnstiftendes raus.  Aber in der tat recht sinnvoll, nicht erst zu zerschlagen, ums dann zurückzubasteln. ru g
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

16. Juni 2012 19:17
Die Tabellen müssten doch irgend ein charakteristisches Merkmal haben, oder ? (und sei es eine bestimmte Formatierung) Was kommt denn dabei heraus, wenn Du eine Probe-PDF-Tabelle nach XML konvertierst ? (das liefert ja lt. Doku die meisten Infos) Kannst Du mal einen Muster-XML einer solchen Konvertierung als Anhang posten ? (natürlich ohne sensible Daten drin ...) track
|
|
gonzo.d
(Themenstarter)
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

16. Juni 2012 20:56
Hej track, die Quelle ist das hier z.B. http://www.berlin.de/imperia/md/content/baspandau/finbuedeu/buergerbeteiligung_41/00_stand_bvv_beschluss_16_03_2012.pdf?download.html Daraus möchte ich nur den Zahlenteil haben, z.B. Seite 287 u.ä. möglicherweise sind es nur die grau hinterlegten Zeilen. Zunächst habe ich java -jar pdf2table.jar "00-Stand BVV-Vorlage 08-03-2012 komplett.pdf" tools versucht, da entsteht ein xml-Dokument, was mich nicht großartig weitergebracht hat, weil ich für OpenOffice xslt-Filter für den Import definieren müsste. zu viel zu lesen! Jetzt bringt pdftohtml -c -s "00_stand_bvv_beschluss_16_03_2012.pdf" 00_stand_bvv_beschluss_16_03_2012.html aus den poppler-utils schonmal einigermaßen ähnliches html. Kann erst später wieder dran weitermachen, aber dann könnte ich die störenden Sachen aus dem html entfernen und das in OO importieren oder so? gruß g
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

16. Juni 2012 23:01
HTML ist für die Weiterverarbeitung viel mühsamer als XML - dafür gibt's nämlich richtig gute Tools. Ich habe mal die Seite 287 herausgezogen: pdftohtml -xml -f 287 -l 287 00_stand_bvv_beschluss_16_03_2012.pdf seite_287 und bekomme damit ein durchaus handliches XML-Dokument, das sich mit xmlstarlet o.ä. wunderschön auswerten lässt. Dazu muss man natürlich die charakteristischen Merkmale finden. Hier wäre es für die grau hinterlegten Felder wahrscheinlich die Eigenschaft font="3" - und anhand dieses Merkmals lassen sich diese Teile auch sehr schön isolieren: xmlstarlet sel -I -t -c //text[@font="3"] seite_287.xml Wie solche Befehle grundsätzlich aufgebaut sind, steht in der xmlstarlet-Doku, gute Beispiele für die zugehörigen Xpath-Ausdrücke stehen hier. Die nächste Hürde ist jetzt natürlich, Felder, die mehrere Zeilen enthalten, wieder zusammen zu setzen. Aber das geht, wenn man pro Zeilenblock die Ausdrücke mit gleichem Einzug (z.B. left="111") zusammen zieht. (ja, das muss man auch erstmal programmieren, aber es ist machbar ! Xpath bietet dafür weitreichende Möglichkeiten) Auf jeden Fall bekommst Du auf diesem Wege eine klare Zuordnung und kannst die Daten, zwar mit Aufwand, aber sauber herausziehen. LG, track
|
|
gonzo.d
(Themenstarter)
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

16. Juni 2012 23:21
oha, werd ich mir noch ansehen! Derweil hab ich das http://discerning.com/hacks/docutils/pdf2xml/readme.html gefunden, was die Problematik beschreibt, daß pdf ein Haufen Daten ohne wirklich extrahierbare Struktur ist. Also über den Umweg von nah beieinanderstehenden gleich ausgezeichneten Daten, hümpf. ru g
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

16. Juni 2012 23:52
gonzo.d schrieb: Also über den Umweg von nah beieinanderstehenden gleich ausgezeichneten Daten, hümpf.
Klar ! - ist zwar ungewohnt, wenn Du eine "Tabelle" erwartest, aber anders machst Du es ja auch nicht, wenn Du "zu Fuß" eine Tabelle auswertest. (und ja, mehr Informationen gibt PDF als Layout-Format nicht her ...) Aber gut, mit etwas Logik ist es doch zu machen ... Guck Dir mal an, was es bei Xpath alles an Funktionen gibt. Aber wahrscheinlich reicht da schon ein Verweis auf den vorigen oder nächsten Node als Nebenbedingung. Da geht einiges. Und es ist auch kein Problem, mit xmlstarlet sel -t -v und -o direkt eine CSV- Ausgabe zu stricken. (wenn man die Struktur erstmal ausgepuzzelt hat) track
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

17. Juni 2012 18:10
So, ... jetzt habe ich meinen Vorschlag mal selber ausprobiert, als Proof-of-Concept. Damit bekomme ich importierbare CSV- Daten: xmlstarlet sel -t -m //text[@font="3"] -v . -i "number(following::text[@font=3]/@left) > @left" -o ";" -b -i "@left = number(following::text[@font=3]/@left)" -o "|" -b -i "@left > number(following::text[@font=3]/@left)" -n seite_287.xml | sed 's/\([[:alpha:]] \)|/\1/g; s/\([[:alpha:]]\)-|/\1/g; s/ -|/ -/; s/ ;/;/g; s/ /;;/; s/ |/;/g'
## ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
## wähle der Reihe nach aus: | wenn der "left"-Wert des nächsten text-Nodes mit font="3" wenn der "left"-Wert des nächsten text-Nodes mit font="3" wenn der "left"-Wert des nächsten text-Nodes mit font="3" Nachfilter: Füge die Folgezellen zusammen Korrektur: Leerzeichen am Zellenende,
## die text-Nodes mit font="3" | größer ist als der aktuelle Wert: hänge ein ";" an gleich ist wie der aktuelle Wert: hänge ein "|" an kleiner ist als der aktuelle Wert: Zeilenwechsel Formatierung der 1.-4. Zelle
## |
## gib den Node-Wert aus Das sieht auf den ersten Blick etwas kompliziert aus, aber mit ein paar Kommentaren wird es schnell klarer:
Alle Informationen, die nur im XML stecken, ziehe ich mit xmlstarlet heraus, die Nachbesserungen mache ich bequemlichkeitshalber mit sed Bei xmlstarlet: mit "-t -m" werden alle Textnodes mit dem Parameter font="3" der Reihe nach als aktuelle Nodes ausgewählt: Für diese wird erst der Wert (=Inhalt) ausgegeben, und dann je nach Einrückung des nächsten Tabellen-Nodes ein ";" (als Zellentrenner), ein "|" (für Folgezellen) oder ein Zeilenumbruch, wenn eine neue Zeile anfängt. Bei following:: muss man noch einmal die kompletten Bedingungen angeben, damit er tatsächlich mit dem nächsten Tabellen-Node vergleicht, und weil das Ergebnis den Typ "String" hat, ist jeweils die num.-Umwandlung nötig. Das "-b" beendet den "-i" (=if)- Block jeweils. Bei sed: der 1. Block zieht die Folgezellen zusammen (3 Fälle: Trennung im Wort, nach dem Wort und am Gedankenstrich) der 2. Block beseitigt noch hässliche Leerzeichen am Ende und korrigiert die ersten 4 Felder, damit gleiche Zellen nachher übereinander stehen.
Wie gesagt, das ist erstmal nur ein proof-of-concept. Für den Produktiveinsatz müsstest Du wahrscheinlich die einzelnen Filter noch anpassen. LG, track
|
|
gonzo.d
(Themenstarter)
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

19. Juni 2012 01:15
track schrieb: So, ... jetzt habe ich meinen Vorschlag mal selber ausprobiert, als Proof-of-Concept.
Damit bekomme ich importierbare CSV- Daten:
*beeindruckend*, ich hab mir noch Gedanken über zusammenhängende und getrennte Ausgaben dergleichen Felder gemacht, tse, tse.
Das sieht auf den ersten Blick etwas kompliziert aus, aber mit ein paar Kommentaren wird es schnell klarer:
oic!
Alle Informationen, die nur im XML stecken, ziehe ich mit xmlstarlet heraus, die Nachbesserungen mache ich bequemlichkeitshalber mit sed Bei xmlstarlet: mit "-t -m" werden alle Textnodes mit dem Parameter font="3" der Reihe nach als aktuelle Nodes ausgewählt: Für diese wird erst der Wert (=Inhalt) ausgegeben, und dann je nach Einrückung des nächsten Tabellen-Nodes ein ";" (als Zellentrenner), ein "|" (für Folgezellen) oder ein Zeilenumbruch, wenn eine neue Zeile anfängt. Bei following:: muss man noch einmal die kompletten Bedingungen angeben, damit er tatsächlich mit dem nächsten Tabellen-Node vergleicht, und weil das Ergebnis den Typ "String" hat, ist jeweils die num.-Umwandlung nötig. Das "-b" beendet den "-i" (=if)- Block jeweils. Bei sed: der 1. Block zieht die Folgezellen zusammen (3 Fälle: Trennung im Wort, nach dem Wort und am Gedankenstrich) der 2. Block beseitigt noch hässliche Leerzeichen am Ende und korrigiert die ersten 4 Felder, damit gleiche Zellen nachher übereinander stehen.
Wie gesagt, das ist erstmal nur ein proof-of-concept. Für den Produktiveinsatz müsstest Du wahrscheinlich die einzelnen Filter noch anpassen.
Ich laß das mal wirken, -t bezeichnet das nachfolgende template -m (match auf Xpath-expression font=3) wenn der node (value of xpath?) mit -v ausgegeben wird, was macht der Punkt? Das ist die Verknüpfung der Bedingungen? Ganz bin ich mit dem starlet noch nicht auf Du. Die Zeile habe ich mal auf die ganze xml-Datei (alle Seiten) losgelassen, da brichts nach der ersten Seite ab. Das wundert mich etwas, weil es a) ne Weile dauert und b) ich nicht erkennen kann, daß eine Abbruchbedingung formuliert ist. bis später gruß gonzo.d
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

19. Juni 2012 02:26
Bei großen Dokumenten ist das etwas tricky, denn xmlstarlet lädt den gesamten Kram erstmal in den Arbeitsspeicher, und hält auch sonst alles im Arbeitsspeicher. Dazu kommt, dass es auch ein paar Tücken (die ich selber noch nicht ganz durchblicke) und ein paar Bugs gibt. Man wird da etwas (oder auch etwas mehr) probieren müssen, und sich auch in den ganzen XML / XSL- Kram einarbeiten. (da bin ich selber auch erst auf dem Weg !) Vielleicht nützt es, wenn Du als Zwischenschritt mit dem schon genannten xmlstarlet sel -I -t -c //text[@font="3"] den XML erstmal etwas "abspeckst" ? Ansonsten muss man mal gucken, woran er hängt ... wie gesagt, das eine oder andere probieren. track
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

19. Juni 2012 16:23
Update: Jetzt habe ich mir das Dokument mal genauer angeguckt. Es ist etwas chaotisch formatiert, zusammengesetzt aus mehreren verschiedenen Teilen, die jeweils unterschiedlich formatiert sind. So markiert font="3" auf S. 1 z.B die Überschrift ! - Da wundert es nicht, dass ein (nicht perfekter) Filter sich dran verschluckt. Ein Versuch wäre, das Dokument seitenweise auszuwerten: for s in {27..362} ; do
pdftohtml -xml -f $s -l $s 00_stand_bvv_beschluss_16_03_2012.pdf -stdout \
| xmlstarlet sel -t -m //text[@font="3"] -v . -i "number(following::text[@font=3]/@left) > @left" -o ";" -b -i "@left = number(following::text[@font=3]/@left)" -o "|" -b -i "@left > number(following::text[@font=3]/@left)" -n \
| sed 's/\([[:alpha:]] \)|/\1/g; s/\([[:alpha:]]\)-|/\1/g; s/ -|/ -/; s/ ;/;/g; s/ /;;/; s/ |/;/g'
done > tabellen.csv... was zwar schon vernünftige Ergebnisse liefert, aber zwischendurch auch noch viel Schrott. Da wird man wohl den Filter noch verfeinern müssen, dass z.B. alle Zellen übergangen werden, die nur Leerzeichen enthalten, oder so. Wie auch immer, man muss halt etwas damit basteln und probieren. track
|
|
gonzo.d
(Themenstarter)
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

22. Juni 2012 21:30
Ja, die seitenweise Bearbeitung ist eine schöne Idee! for s in {27..362} ; do
pdftohtml -xml -f $s -l $s 00_stand_bvv_beschluss_16_03_2012.pdf -stdout \
| xmlstarlet sel -t -m //text[@font="3"] -v . -i "number(following::text[@font=3]/@left) > @left" -o ";" -b -i "@left = number(following::text[@font=3]/@left)" -o "|" -b -i "@left > number(following::text[@font=3]/@left)" -n \
| sed 's/\([[:alpha:]] \)|/\1/g; s/\([[:alpha:]]\)-|/\1/g; s/ -|/ -/; s/ ;/;/g; s/ /;;/; s/ |/;/g'\
| grep '^[0-9]\{5\}'
done > tabellen2.csvIch bekomm's grad nicht mit sed hin, Zeilen, die nicht mit 5 Ziffern anfangen sollen gelöscht werden.
sed '/^[^0-9]\{5\}/d'
/ leitet ein
^ ist der Zeilenanfang
[^0-9]\{5\} sind nicht 5 Ziffern
/ Ende
d löschenWas macht grep anders außer dasses nicht negiert ist? Damit wirds langsam übersichtlicher! Ein paar Zeilen sind noch verrutscht, weil ein Feld fehlt. Wahrscheinlich sollte ich sie durchnumerieren, falls ich sie nach Beträgen fallend sortiert wieder zurücksortieren möchte. gonzo.d
|
|
track
Anmeldungsdatum: Juni 26, 2008
Beiträge: 4746
Wohnort: Wolfen (S-A)
|

22. Juni 2012 23:51
gonzo.d schrieb: sed '/^[^0-9]\{5\}/d'
Das hat einen kleinen Denkfehler: nur die Zeilen, die wirklich volle 5 Nicht-Zahlen am Anfang haben, werden gelöscht. Was Du haben wolltest, ist: sed -n '/^[0-9]\{5\}/p' (also: nur die durchlassen, die 5 Zahlen am Anfang haben)
Damit wirds langsam übersichtlicher! Ein paar Zeilen sind noch verrutscht, weil ein Feld fehlt. ...
Die würde ich mir im Original (bzw. im XML) nochmal genauer angucken. Eventuell ist da eine kleine Anpassung bei xmlstarlet wirksamer. Wahrscheinlich sollte ich sie durchnumerieren, falls ich sie nach Beträgen fallend sortiert wieder zurücksortieren möchte.
Die Dinger haben doch ± alle ihre Kostenstellen. Sind die nicht sowieso danach sortiert ? LG, track
|
|
gonzo.d
(Themenstarter)
Anmeldungsdatum: Jan. 8, 2008
Beiträge: 147
Wohnort: Berlin
|

23. Juni 2012 01:28
track schrieb: gonzo.d schrieb: sed '/^[^0-9]\{5\}/d'
Das hat einen kleinen Denkfehler: nur die Zeilen, die wirklich volle 5 Nicht-Zahlen am Anfang haben, werden gelöscht.
langsam wird's ja. Also war ich nicht ganz weit weg.
Was Du haben wolltest, ist: sed -n '/^[0-9]\{5\}/p' (also: nur die durchlassen, die 5 Zahlen am Anfang haben)
Damit wirds langsam übersichtlicher! Ein paar Zeilen sind noch verrutscht, weil ein Feld fehlt. ...
Die würde ich mir im Original (bzw. im XML) nochmal genauer angucken. Eventuell ist da eine kleine Anpassung bei xmlstarlet wirksamer.
yep
Wahrscheinlich sollte ich sie durchnumerieren, falls ich sie nach Beträgen fallend sortiert wieder zurücksortieren möchte.
Die Dinger haben doch ± alle ihre Kostenstellen. Sind die nicht sowieso danach sortiert ?
Dachte ich auch erst, deshalb bin ich froh, daß ich schonmal auf irgendwas schauen kann. Nee, selbst die Kombinationen aus Titel und Fkt sind mehrfach vergeben! Ma kieken, vielleicht mach ich Summenzeilen wieder hinein. ru g
|