Unstrukturierten Text mit regulären Ausdrücken verarbeiten

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie müssen XML-Dokumente transformieren, die teilweise aus unstrukturiertem Text bestehen, der in ein korrekt ausgezeichnetes Dokument überführt werden soll.

Lösung

Es gibt drei XPath 2.0-Funktionen zum Arbeiten mit regulären Ausdrücken: match( ), replace( ) und tokenize( ). Wir haben sie unter XPath behandelt. Außerdem gibt es eine neue XSLT-Anweisung, xsl:analyze-string, die es Ihnen erlaubt, sogar aufwändigere Textverarbeitungsaufgaben auszuführen.

Die Anweisung xsl:analyze-string nimmt ein select-Attribut entgegen, um den zu verarbeitenden String festzulegen, ein regex-Attribut, um den regulären Ausdruck anzugeben, der auf den String angewandt werden soll, und ein optionales flags-Attribut, mit dem die Aktion der Regex-Engine modifiziert werden kann. Die Standard-Flags sind:

  • i Modus, in dem Groß- und Kleinschreibung nicht beachtet werden.
  • m Mehrzeiliger Modus, in dem die Metazeichen ^ und $ dem Anfang und dem Ende von Zeilen entsprechen und nicht dem Anfang und dem Ende des gesamten Strings (was die Vorgabe ist).
  • s Sorgt dafür, dass das Metazeichen . Newlines (Entity 
) filtert. Vorgabe ist es, Newlines nicht zu erfassen. Dieser Modus wird manchmal als einzeiliger Modus bezeichnet. Aus seiner Definition heraus sollte es aber klar sein, dass es sich nicht um das Gegenteil des mehrzeiligen Modus handelt. Man kann nämlich sogar die Flags s und m zusammen verwenden.
  • x Erlaubt die Verwendung von Whitespace in einem regulären Ausdruck als Trennzeichen anstatt als aussagekräftiges Zeichen.

Das Kindelement xsl:matching-substring wird verwendet, um den Teilstring zu verarbeiten, der durch den regex erfasst wird, und xsl:non-matching-substring wird verwendet, um Teilstrings zu verarbeiten, die nicht durch den regex erfasst werden. Eines von beiden darf weggelassen werden. Es ist auch möglich, mit Hilfe der Funktion regex-group innerhalb von xsl:matching-substring auf Captured Groups (Teile eines regulären Ausdrucks, die von runden Klammern eingeschlossen sind) zu verweisen:

<xsl:template match="date">
  <xsl:copy>
    <xsl:analyze-string select="normalize-space(.)" regex="(\d\d\d\d) ( / | - ) (\d\d) ( / | - ) (\d\d)" flags="x">
      <xsl:matching-substring>
        <year><xsl:value-of select="regex-group(1)"/></year>
        <month><xsl:value-of select="regex-group(3)"/></month>
        <day><xsl:value-of select="regex-group(5)"/></day>
      </xsl:matching-substring>
      <xsl:non-matching-substring>
        <error><xsl:value-of select="."/></error>
      </xsl:non-matching-substring>
    </xsl:analyze-string>
  </xsl:copy>
</xsl:template>

Ein hübsches Gegenstück zu xsl:analyze-string ist die XSLT-Funktion unparsed-text( ). Diese Funktion erlaubt es Ihnen, den Inhalt einer Textdatei als String zu lesen. Wie der Name verrät, wird die Datei nicht geparst und muss daher kein XML sein. Um ehrlich zu sein, würde man nur unter den allerungewöhnlichsten Umständen unparsed-text( ) auf XML-Inhalt anwenden.

Das folgende Stylesheet wandelt eine einfache Datei, in der Kommas als Trennzeichen verwendet werden (eine Datei ohne quotierte Strings) in XML um:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/02/xpath-functions" xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
  <xsl:param name="csv-file" select=" 'test.csv' "/>
  <xsl:template match="/">
    <converted-csv filename="{$csv-file}">
      <xsl:for-each select="tokenize(unparsed-text($csv-file, 'UTF-8'),'\n')">
        <xsl:if test="normalize-space(.)">
          <row>
            <xsl:analyze-string select="." regex="," flags="x">
              <xsl:non-matching-substring>
                <col><xsl:value-of select="normalize-space(.)"/></col>
              </xsl:non-matching-substring>
            </xsl:analyze-string>
          </row>
        </xsl:if>
      </xsl:for-each>
    </converted-csv>
  </xsl:template>
</xsl:stylesheet>

Diskussion

Die Regex-Fähigkeiten von XSLT 2.0 eröffnen zusammen mit unparsed-text( ) völlig neue Verarbeitungsmöglichkeiten in XSLT, die in XSLT 1.0 fast unmöglich waren. Dennoch wäre XSLT nicht meine erste Wahl für das Verarbeiten von Nicht-XML-Inhalten, es sei denn, ich würde in einem Kontext arbeiten, in dem eine Lösung mit mehreren Sprachen (z. B. Java und XSLT oder Perl und XLST) nicht praktikabel wäre. Falls natürlich XSLT die einzige Sprache ist, die Sie bewältigen wollen, können Sie mit den neuen Fähigkeiten völlig neue Bereiche erkunden.

Ein Teil meiner Motivation, auf den XSLT-Zug aufzuspringen, wenn es um den Bereich der Verarbeitung unstrukturierten Textes geht, sind die »fehlenden Funktionen« von xsl:analyze-string. Es wäre schön, wenn die Funktionen position( ) und last( ) innerhalb von xsl:matching-substring funktionieren würden, um Ihnen mitzuteilen, dass dies Treffer Nummer position( ) der last( ) möglichen Treffer ist. Ich verwende manchmal xsl:for-each über ein tokenize( ) anstelle von xsl:analyze-string, allerdings ist das ebenfalls unzulänglich, da damit nur nicht-zutreffende Teile zurückgeliefert werden. Außerdem fühlt man sich oft gezwungen, für ein komplexes Parsing-Problem, das viele mögliche regex-Treffer in einem regex mit Alternierung ( | ) verursacht, xsl:analyze-string zu verwenden. Es gibt jedoch keine Möglichkeit festzustellen, welches regex zugetroffen hat, ohne mit der Funktion match( ) erneut eine Filterung vorzunehmen, was nach meiner Meinung redundant und eine Zeitverschwendung ist, da die regex-Engine sicher weiß, welchen Teil sie gerade gefiltert hat:

<xsl:template match="text( )">
  <xsl:analyze-string select="." regex='[\-+]?\d\.\d+\s*[eE][\-+]?\d+ | [\-+]?\d+\.\d+ | [\-+]?\d+ | "[^"]*?" ' flags="x">
    <xsl:matching-substring>
      <xsl:choose>
        <xsl:when test="matches(.,'[\-+]?\d\.\d+\s*[eE][\-+]?\d+')">
          <scientific><xsl:value-of select="."/></scientific>
        </xsl:when>
        <xsl:when test="matches(.,'[\-+]?\d+\.\d+')">
          <decimal><xsl:value-of select="."/> </decimal>
        </xsl:when>
        <xsl:when test="matches(.,'[\-+]?\d+')">
          <integer><xsl:value-of select="."/> </integer>
        </xsl:when>
        <xsl:when test='matches(.," "" [^""]*? "" ", "x")'>
          <string><xsl:value-of select="."/></string>
        </xsl:when>
      </xsl:choose>
    </xsl:matching-substring>
  </xsl:analyze-string>
</xsl:template>

Nun ja, hinterher ist man immer klüger, und es gibt natürlich alle Arten von Implementierungsproblemen und -nachteilen, die man überwinden muss, wenn man eine Sprache weiterentwickelt. Bei allem Respekt gegenüber dem XSLT 2.0-Komitee, aber es wäre doch schöner, wenn xsl:analyze-string folgendermaßen funktionieren würde:

<!-- KEIN GÜLTIGES XSLT 2.0 - Wunschtraum des Autors -->
<xsl:template match="text( )">
  <xsl:analyze-string select="." flags="x">
    <xsl:matching-substring regex="[\-+]?\d\.\d+\s*[eE][\-+]?\d+">
      <scientific><xsl:value-of select="."/></scientific>
    </xsl:matching-substring>
    <xsl:matching-substring regex="[\-+]?\d+\.\d+'">
      <decimal><xsl:value-of select="."/> </decimal>
    </xsl:matching-substring>
    <xsl:matching-substring regex=" [\-+]?\d+')">
      <integer><xsl:value-of select="."/> </integer>
    </xsl:matching-substring>
    <xsl:matching-substring regex=' "[^"]*?" '>
      <string><xsl:value-of select="."/></string>
    </xsl:matching-substring>
    <xsl:non=matching-substring>
      <other><xsl:value-of select="."/></other>
    </xsl:non=matching-substring>
  </xsl:analyze-string>
</xsl:template>

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 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 "XSLT Kochbuch" 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, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de