Weitere Programmbeispiele

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Hinzufügen von WIDTH- und HEIGHT-Attributen zu IMG-Tags in HTML

Dieser Abschnitt behandelt eine etwas kompliziertere Version von »Suchen und Ersetzen«. In einem HTML-Text (der als StringBuilder, StringBuffer oder in einer anderen veränderbaren CharSequence vorliegen muss) soll sichergestellt sein, dass alle Bilder (die IMG-Tags) mit den expliziten Attributen WIDTH und HEIGHT ausgestattet sind.

Wenn diese Attribute bei einer Webseite nicht angegeben sind, kann das Anzeigeprogramm (der Browser) nicht wissen, wie viel Platz für die Bilder zu reservieren ist. Der Aufbau der Seite erscheint deshalb langsam, sprunghaft oder beides, weil jedes Bild vor der Positionierung in der Webseite zuerst heruntergeladen und seine Größe bestimmt werden muss. Wenn dagegen im HTML die Bilddimensionen angegeben sind, kann der Browser den Text sofort umbrechen und die Bilder nachliefern, ohne dass das Layout verändert werden muss. Die Seite sieht so für den Benutzer von Anfang an »fertig« aus. (Anmerkung: Bei Yahoo! wurde der Satz »Bei IMG immer WIDTH und HEIGHT!« schon in der Anfangszeit des Webs mantraartig wiederholt. Auch heute findet man aber noch viele Seiten von professionellen Anbietern, bei denen die Bilddimensionen fehlen.)

Wenn ein IMG-Tag gefunden wird, sucht unser Programm innerhalb des Tags nach SRC-, WIDTH- und HEIGHT-Attributen und holt deren Werte heraus, falls angegeben. Wenn eines dieser Attribute fehlt, wird das Bild vom Server heruntergeladen und dessen Höhe und Breite bestimmt; mit diesen Daten werden dann die WIDTH- und HEIGHT-Attribute aufgebaut.

Wenn sowohl WIDTH als auch HEIGHT fehlt, werden die tatsächlichen Werte für die Bildgröße eingesetzt. Wenn nur eines der Attribute angegeben ist, wird das andere so berechnet, dass das Seitenverhältnis des Bildes gewahrt bleibt; das Bild wird skaliert. (Wenn zum Beispiel ein WIDTH-Attribut angegeben ist, das nur die Hälfte der tatsächlichen Bildbreite ausmacht, wird auch für das HEIGHT-Attribut nur die Hälfte der tatsächlichen Bildhöhe eingesetzt. So verhalten sich auch die heute üblichen Browser.)

In diesem Beispiel wird – wie in dem bei Direktes Suchen und Ersetzen – ein expliziter Zeiger auf die aktuelle Position nachgeführt. Wir verwenden Regionen (siehe Die Region des Matchers) und das Verketten von Methoden:

// Matcher zum Herauslösen von <IMG>-Tags.
Matcher mImg    = Pattern.compile("(?id)<IMG\\s+(.*?)/?>").matcher(html);

// Matcher für die SRC-, WIDTH- und HEIGHT-Attribute innerhalb eines Tags.
Matcher mSrc    = Pattern.compile("(?ix)\\bSRC   =(\\S+)").matcher(html);
Matcher mWidth  = Pattern.compile("(?ix)\\bWIDTH =(\\S+)").matcher(html);
Matcher mHeight = Pattern.compile("(?ix)\\bHEIGHT=(\\S+)").matcher(html);

// Die erste Suche beginnt am Anfang des Strings.
int imgMatchZeiger = 0;
while (mImg.find(imgMatchZeiger)) {
   imgMatchZeiger = mImg.end(); // Vom Ende des letzten <IMG>-Tags aus weitersuchen.

  // Attribute innerhalb des eben gefundenen <IMG>-Tags suchen.
   Boolean hatSrc    =    mSrc.region( mImg.start(1), mImg.end(1) ).find();
   Boolean hatHeight = mHeight.region( mImg.start(1), mImg.end(1) ).find();
   Boolean hatWidth  =  mWidth.region( mImg.start(1), mImg.end(1) ).find();

  // SRC-Attribut gefunden, aber kein WIDTH- und/oder kein HEIGHT-Attribut ...
   if (hatSrc && (! hatWidth || ! hatHeight)) {

     java.awt.image.BufferedImage i = // Bild herunterladen.
         javax.imageio.ImageIO.read(new java.net.URL(mSrc.group(1)));

     String size; // Für die fehlenden WIDTH- und/oder HEIGHT-Attribute.
      if (hatWidth)
          // Breite vorhanden, Höhe unter Einhaltung des Seitenverhältnisses berechnen.
          size = "height='" + (int)(Integer.parseInt(mWidth.group(1)) *
                                     i.getHeight() / i.getWidth())  + "' ";
      else if (hatHeight)
          // Höhe vorhanden, Breite unter Einhaltung des Seitenverhältnisses berechnen.
          size = "width='"  + (int)(Integer.parseInt(mHeight.group(1)) *
                                    i.getWidth()  / i.getHeight()) + "' ";
      else // Weder Höhe noch Breite vorhanden; die wahren Dimensionen des Bilds einsetzen.
          size = "width='"  + i.getWidth()  + "' " +
                 "height='" + i.getHeight() + "' ";

      html.insert(mImg.start(1), size); // Attribute einfügen.
      imgMatchZeiger += size.length();  // Um die Anzahl der neuen Zeichen weiterschalten.
   }
}

Aus dem Beispiel lässt sich sicher etwas lernen, aber es ist doch in einigen Aspekten ungenügend. Weil es mir vor allem um das Suchen und Ersetzen ging, habe ich in anderen Bereichen angenommen, dass der vorliegende HTML-String bestimmte Bedingungen erfüllt. Zum Beispiel erlauben die regulären Ausdrücke bei den Attributen weder Whitespace vor und nach dem Gleichheitszeichen noch Werte in doppelten Anführungszeichen. (Im Perl-Programmbeispiel von Einen HTML-Link erkennen sind reguläre Ausdrücke vorhanden, die diese Attribut-Syntax korrekt behandeln.) Das Programm kann nicht mit relativen URLs umgehen, auch nicht mit fehlerhaft formatierten URLs, und die Ausnahmen, die beim Herunterladen der Bilder auftreten können, werden nicht abgefangen.

Dennoch zeigt das Programmbeispiel ein paar wertvolle Konzepte auf.

Ein Matcher zum Prüfen von HTML mit mehreren Pattern

Dieses Programmbeispiel ist die Java-Version des Perl-Programms, das einen String auf einen Subset von HTML prüft. Es verwendet die usePattern-Methode und damit verschiedene reguläre Ausdrücke im selben Matcher. Diese regulären Ausdrücke beginnen alle mit ˹\G˼ und arbeiten als »HTML-Tag-Team« zusammen den Text durch. Im Kasten Ein Beispiel für den Gebrauch von \G in Perl wird die Funktionsweise des »Tag-Teams« erläutert.

Pattern pAmEnde   = Pattern.compile("\\G\\z");
Pattern pWort     = Pattern.compile("\\G\\w+");
Pattern pKeinHtml = Pattern.compile("\\G[^\\w<>&]+");
Pattern pImgTag   = Pattern.compile("\\G(?i)<img\\s+([^>]+)>");
Pattern pLink     = Pattern.compile("\\G(?i)<A\\s+([^>]+)>");
Pattern pLinkX    = Pattern.compile("\\G(?i)</A>");
Pattern pEntity   = Pattern.compile("\\G&(#\\d+|\\w+);");

boolean needClose = false;
Matcher m = pAmEnde.matcher(html); // Den Matcher können wir mit jedem Pattern erzeugen.

while (! m.usePattern(pAmEnde).find())
{
   if (m.usePattern(pWort).find()) {
      // Ein Wort oder eine Zahl in m.group() -- auf Obszönitäten usw. prüfen ...
   } else if (m.usePattern(pImgTag).find()) {
      // Ein IMG-Tag -- Inhalt auf Zulässigkeit prüfen ...
   } else if (! needClose && m.usePattern(pLink).find()) {
      // Ein Link -- den wir hier überprüfen können ...
      needClose = true;
   } else if (needClose && m.usePattern(pLinkX).find()) {
      System.out.println("/LINK [" + m.group() + "]");
      needClose = false;
   } else if (m.usePattern(pEntity).find()) {
      // Entities wie &gt; oder &#123; zulassen.
   } else if (m.usePattern(pKeinHtml).find()) {
      // Weder Wort noch Zahl noch HTML -- zulassen.
   } else {
      // Bis hier nichts erkannt; also ein Fehler. Wir holen ein Dutzend Zeichen aus dem
      // HTML-Text ab der aktuellen Position und geben damit eine Fehlermeldung aus.
      m.usePattern(Pattern.compile("\\G(?s).{1,12}")).find();
      System.out.println("Unerwartetes HTML vor '" + m.group() + "'");
      System.exit(1);
   }
}

if (needClose) {
   System.out.println("Kein </A> am Ende.");
   System.exit(1);
}

Wegen dem Fehler in java.util.regex, der dazu führen würde, dass der »Kein-HTML«-Match ein Zeichen zu viel vom Suchtext »fressen« würde, habe ich den Kein-HTML-Teil ans Ende verschoben. Der Fehler passiert auch dann, aber er hat nur Auswirkungen auf die Fehlermeldung, die jetzt um ein Zeichen verschoben ist. Ich habe Sun den Fehler gemeldet.

Solange dieser Fehler vorhanden ist – wie könnte man die Ein-Argument-Version von find einsetzen, um das Problem zu umgehen? Die Antwort finden Sie in Lösung 15.

Daten im CSV-Format verarbeiten

Dies ist die java.util.regex-Version des CSV-Beispiels aus Die Kunst, reguläre Ausdrücke zu schreiben (siehe Aufbrechen der Schleife bei CSV-Daten). Wir verwenden hier allerdings aus ästhetischen Gründen possessive Quantoren statt atomare Gruppen.

String regex = // Werte in Anführungszeichen landen in group(1), solche ohne in group(2).
    "  \\G(?:^|,)    \n"+
    "  (?:           \n"+
    "       # Entweder ein Feld in Anführungszeichen ...       \n"+
    "       \" # Öffnendes Anführungszeichen    \n"+
    "        (   [^\"]*+  (?: \"\" [^\"]*+ )*+  )      \n"+
    "       \" # Schließendes Anführungszeichen    \n"+
    "   |# ... oder ...          \n"+
    "       # Ein Feld ohne Kommas und Anführungszeichen.       \n"+
    "       ( [^\",]*+ ) \n"+
    "  )             \n";

// Ein Matcher für eine Zeile CSV-Daten, mit obiger Regex.
Matcher mMain = Pattern.compile(regex, Pattern.COMMENTS).matcher(zeile);

// Ein Matcher für "", der Suchtext ist irrelevant.
Matcher mQuote = Pattern.compile("\"\"").matcher("");

while (mMain.find())
{
    String feld;
    if (mMain.start(2) >= 0)
        feld = mMain.group(2); // Feld ohne Anführungszeichen, kann direkt verwendet werden.
    else
        // Feld mit Anführungszeichen; verdoppelte durch einzelne ersetzen.
        feld = mQuote.reset(mMain.group(1)).replaceAll("\"");
    // Wert aus dem Feld weiterverarbeiten.
    System.out.println("Feld [" + feld + "]");
}

Diese Version ist aus zwei Gründen effizienter als die ursprüngliche Java-Version von CSV-Dateien mit Java parsen: Die Regex an sich ist nach den in Die Kunst, reguläre Ausdrücke zu schreiben beschriebenen Methoden optimiert (siehe Aufbrechen der Schleife bei CSV-Daten), und außerdem verwenden wir nur ein einziges Matcher-Objekt und setzen es mit reset zurück, statt jedes Mal ein neues Objekt zu erzeugen.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln