Validierung, erweitert (Teil 2)

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:

  1. Der Dateityp wird ermittelt (Zeile 13) und ausgegeben.
  2. Anhand des XML-Elementtyps (audioobject, imageobject, videoobject)
    werden die verschiedenen Medientypen unterschiedlich behandelt (Zeile 15 ff.)
  3. 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.