Der erste Artikel zur erweiterten Validierung beschäftigte sich hauptsächlich mit XML-basierten Mitteln zur Prüfung von Dokumenten oder Manuskripten. Die weiteren Prüfungen bin ich schuldig geblieben. Diese Schulden möchte ich dann hiermit begleichen.
Bei XML-Publikationen wird viel mit Referenzen auf externe Artefakte gearbeitet. Als Referenzen werden sie meist erst bei der Verarbeitung oder gar erst bei der Anzeige richtig angesprochen, die XML-Verarbeitungsprozesse kümmern sich meist nicht um sie. Daher können hier unangenehme Fehler entstehen, die man aber durch automatisierte Prüfungen reduzieren kann. Was man gut automatisieren kann sind die ungeliebten, etwas stumpfsinnigen Prüfungen wie
- Sind die im Text referenzierten Dateien auch vorhanden?
- Stimmen die Dateiformate? (Vektor- oder Bitmap usw.)
- Passen die Dimensionen?
- Sind Copyright-Metadaten, oder überhaupt genügend Metadaten, vorhanden?
Natürlich kann man das auch alles manuell machen, aber warum nicht automatisieren, wo das leicht möglich ist? Viele solcher Prüfungen lassen sich heute mit recht einfachen Mitteln durchführen, man darf nur keine Angst vor Code haben.
Beispiel 1: Prüfung von Bildreferenzen
Einmal angenommen, ich möchte die Referenzen auf Bilddateien in einer DocBook-Datei ermitteln. Medieneinträge in DocBook können so aussehen:
<mediaobject xml:id="m1"> <imageobject> <imagedata xml:id="img1" fileref="src/resources/images/cimg0001.jpg"/> </imageobject> <textobject> <para> m1.png stellt .... </para> </textobject> </mediaobject>
Bob Staytons DocBook XSL verweist dafür auf ein kleines XSL-Werkzeug (xmldepend), das alle Referenzen auflistet. Das ist ganz schön, sagt mir aber noch nicht, ob die Dateien auch wirklich mitgeliefert wurden. Ein kleines Ruby-Skript, das auch etwas kürzer ist, erledigt beide Aufgaben:
require 'rexml/document' include REXML def check_images(file) file.elements.each('//imagedata') {|i| fname = i.attributes['fileref'] puts "Element #{i.name}, ID #{i.attributes['id']}, Referenz #{fname}" puts "\tBildreferenz #{fname} nicht gefunden" unless File.exist?(fname) } end dbfile = Document.new(File.open('src/main/xml/bsp1.xml')) check_images(dbfile)
Das Skript sucht also alle imagedata
Elemente im Text und schaut sich deren fileref
Attribut an. Dort ist die Referenz auf die Bilddatei, die an dieser Stelle angezeigt werden soll, eingetragen. Sobald die Referenz, /”src/resources/images/cimg0001.jpg”/, ermittelt wurde, kann man prüfen (File.exist?
), ob eine Datei an diesem Ort vorhanden
ist. Ganz einfache 13 Zeilen Skript, sogar mit Leerzeilen. Die Ausgabe könnte dann so aussehen:
Element imagedata, ID img1, Referenz src/resources/images/cimg0001.jpg Bildreferenz src/resources/images/cimg0001.jpg nicht gefunden Element imagedata, ID img2, Referenz src/resources/images/cimg0002.jpg ...
Beispiel 2: Prüfung aller Referenzen
Statt die Prüfung nur auf Bildreferenzen zu machen, kann man sie auch leicht auf alle Dateireferenzen ausweiten:
require 'rexml/document' include REXML def check_filerefs(file) file.elements.each('//*[@fileref]') {|i| fname = i.attributes['fileref'] puts "Element #{i.name}, ID #{i.attributes['id']}, Referenz #{fname}" puts "\tBildreferenz #{fname} nicht gefunden" unless File.exist?(fname) } end dbfile = Document.new(File.open('src/main/xml/bsp1.xml')) check_filerefs(dbfile)
Die einzig wichtige Änderung in diesem Code-Ausschnitt ist das Suchmuster (Zeile 5). Statt Bilder (//imagadata
) suchen wir nun alle Elemente, die ein fileref
-Attribut haben (//*[@fileref]
). Damit bekommen wir auch die Audio- oder Videoreferenzen geprüft.
Beispiel 3: Analyse der referenzierten Dateien
Wenn man nun schon mal die gelieferten Mediendateien gefunden hat, kann man sie ja auch gleich etwas näher anschauen. Statt gleich Photoshop etc hochzufahren, könnte man ja schon vorher interessante Parameter ermitteln. Lohnt es sich überhaupt die Dinger anzuschauen?
require 'rubygems' require 'rexml/document' include REXML require 'mini_exiftool' require 'filemagic' def check_filerefs_plus(file) file.elements.each('//*[@fileref]') {|i| fname = i.attributes['fileref'] puts "Element #{i.name}, ID #{i.attributes['id']}" if File.exist?(fname) puts "\tDateireferenz: #{fname}" fm = FileMagic.new puts "\tTyp: #{fm.file(fname)}" if (i.name == 'imagedata') photo = MiniExiftool.new fname puts "\tDimensionen: #{photo['ImageWidth']} * #{photo['ImageHeight']}" elsif (i.name == 'audiodata') # todo elsif (i.name == 'videodata') # todo else puts "\tFehler: Nicht vorgesehener Elementtyp #{e.name} mit Dateireferenz #{fname}" end else puts "\tFehler: Dateireferenz #{fname} nicht gefunden" end } end dbfile = Document.new(File.open('src/main/xml/bsp1.xml')) check_filerefs_plus(dbfile)
Hier nehme ich dann zwei Bibliotheken hinzu:
-
filemagic
ermittelt den Typ einer Datei (nur weil ein Dateiname mit “.jpg” endet muss da noch lange kein JPEG drin sein) -
mini_exiftool
erlaubt die Nutzung von Phil Harveys ExifTool, einer Bibliothek, die mit Metadaten von Fotos umgehen kann
Das Skript enthält nun drei Änderungen zur Vorgängerversion:
- Der Dateityp wird ermittelt (Zeile 13) und ausgegeben.
- Anhand des XML-Elementtyps (audioobject, imageobject, videoobject)
werden die verschiedenen Medientypen unterschiedlich behandelt (Zeile 15 ff.) - Bei Bildern werden die Dimensionen Breite und Höhe ausgelesen, um zu schauen, ob sie in den Rahmen passen (Zeile 16-17).
Die Ausgabe:
Element imagedata, ID img1 Fehler: Dateireferenz src/resources/images/cimg0001.jpg nicht gefunden Element imagedata, ID img2 Dateireferenz: src/resources/images/cimg0002.jpg Typ: JPEG image data, EXIF standard Dimensionen: 1280 * 960
Mit 30+ Zeilen Code haben wir somit den Grundstock für weitere Tests gelegt. Im vorliegenden Code werden zwar nur Bitmap-Bilddateien behandelt und nur wenige Metadaten ausgelesen, aber die zur Erweiterung nötigen Schritte sind auch nicht aufwendiger. Die ExifTool-Seite listet all die Dateiformate und Metadaten, die man zusätzlich auswerten könnte. Für Vektorgrafiken, Audio und Video benötigt man nur andere Bibliotheken, alles kein Hexenwerk.