Von einer Basis in eine andere konvertieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie müssen Strings, die Zahlen in einer bestimmten Basis repräsentieren, in eine andere Basis konvertieren.

Lösung

Dieses Beispiel liefert eine allgemeine Lösung für die Konvertierung von einer beliebigen Basis zwischen 2 und 36 in eine andere Basis innerhalb dieses Bereichs. Es verwendet zwei globale Variablen, um den Wert aller Zeichen in einem System zur Basis 36 als Offsets in den String zu kodieren – die eine für die Kodierung mit Großbuchstaben und die andere für die Kodierung mit Kleinbuchstaben:

<xsl:variable name="ckbk:base-lower" select="'0123456789abcdefghijklmnopqrstuvwxyz'"/>    
<xsl:variable name="ckbk:base-upper" select="'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>    
<xsl:template name="ckbk:convert-base">
  <xsl:param name="number"/>
  <xsl:param name="from-base"/>
  <xsl:param name="to-base"/>  
  <xsl:variable name="number-base10">
    <xsl:call-template name="ckbk:convert-to-base-10">
      <xsl:with-param name="number" select="$number"/>
      <xsl:with-param name="from-base" select="$from-base"/>
    </xsl:call-template>
  </xsl:variable>
  <xsl:call-template name="ckbk:convert-from-base-10">
    <xsl:with-param name="number" select="$number-base10"/>
    <xsl:with-param name="to-base" select="$to-base"/>
  </xsl:call-template>
</xsl:template>

Dieses Template reduziert das allgemeine Problem auf die zwei Teilprobleme der Konvertierung zur und von der Basis 10. Das Durchführen von Basis-10-Konvertierungen ist einfacher, weil es sich hierbei um die den XPath-Zahlen eigene Basis handelt.

Das Template ckbk:convert-to-base-10 normalisiert die Eingabezahlen auf Kleinbuchstaben. Daher behandeln Sie beispielsweise ffff-Hex genauso wie FFFF-Hex, was völlig der Konvention entspricht. Zwei Fehlertests werden durchgeführt, um sicherzustellen, dass die Basis sich in dem Bereich befindet, mit dem Sie umgehen können, und dass die Zahl keine illegalen Zeichen enthält, die inkonsistent zur Basis sind. Der triviale Fall der Konvertierung von Basis 10 in Basis 10 wird ebenfalls abgehandelt:

<xsl:template name="ckbk:convert-to-base-10">
  <xsl:param name="number"/>
  <xsl:param name="from-base"/>  
  <xsl:variable name="num" select="translate($number,$ckbk:base-upper, $ckbk:base-lower)"/>
  <xsl:variable name="valid-in-chars" select="substring($ckbk:base-lower,1,$from-base)"/>  
  <xsl:choose>
    <xsl:when test="$from-base &lt; 2 or $from-base &gt; 36">NaN</xsl:when>
    <xsl:when test="not($num) or translate($num,$valid-in-chars,'')">NaN</xsl:when>
    <xsl:when test="$from-base = 10">
      <xsl:value-of select="$number"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:convert-to-base-10-impl">
        <xsl:with-param name="number" select="$num"/>
        <xsl:with-param name="from-base" select="$from-base"/>
        <xsl:with-param name="from-chars" select="$valid-in-chars"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Sobald Sie sich um die Fehlerprüfung gekümmert haben, können Sie die tatsächliche Konvertierung an ein anderes rekursives Template delegieren, das die Arbeit erledigt. Dieses Template ermittelt den Dezimalwert jedes einzelnen Zeichens aus seinem Index innerhalb des Strings, der durch den Aufrufer übergeben worden ist. Die Rekursion multipliziert das Ergebnis mit der Basis und addiert dazu den Wert des ersten Zeichens, bis Sie nur noch einen String der Länge 1 haben:

<xsl:template name="ckbk:convert-to-base-10-impl">
  <xsl:param name="number"/>
  <xsl:param name="from-base"/>
  <xsl:param name="from-chars"/>  
  <xsl:param name="result" select="0"/>  
  <xsl:variable name="value" select="string-length(substring-before($from-chars,substring($number,1,1)))"/>  
  <xsl:variable name="total" select="$result * $from-base + $value"/>  
  <xsl:choose>
    <xsl:when test="string-length($number) = 1">
      <xsl:value-of select="$total"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:convert-to-base-10-impl">
        <xsl:with-param name="number" select="substring($number,2)"/>
        <xsl:with-param name="from-base" select="$from-base"/>
        <xsl:with-param name="from-chars" select="$from-chars"/>
        <xsl:with-param name="result" select="$total"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Die andere Hälfte des Problems erfordert eine Konvertierung von der Basis 10 in eine andere Basis. Auch hier trennen Sie die Fehlerüberprüfung von der eigentlichen Konvertierung:

<xsl:template name="ckbk:convert-from-base-10">
  <xsl:param name="number"/>
  <xsl:param name="to-base"/>  
  <xsl:choose>
    <xsl:when test="$to-base &lt; 2 or $to-base &gt; 36">NaN</xsl:when>
    <xsl:when test="number($number) != number($number)">NaN</xsl:when>
    <xsl:when test="$to-base = 10">
      <xsl:value-of select="$number"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="ckbk:convert-from-base-10-impl">
        <xsl:with-param name="number" select="$number"/>
        <xsl:with-param name="to-base" select="$to-base"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Die tatsächliche Konvertierung ist dann einfach nur noch eine Frage des Heraussuchens der einzelnen Ziffern aus der $ckbk:base-lower-Tabelle, basierend auf dem Rest (d.h. modulo), der entsteht, wenn durch $to-base dividiert wird. Sie führen die Rekursion auf dem übrig gebliebenen Integer-Anteil durch, wobei die Ziffer vor das Ergebnis gesetzt wird. Die Rekursion endet, wenn die Zahl kleiner ist als die Basis:

<xsl:template name="ckbk:convert-from-base-10-impl">
  <xsl:param name="number"/>
  <xsl:param name="to-base"/>
  <xsl:param name="result"/>  
  <xsl:variable name="to-base-digit" select="substring($ckbk:base-lower,$number mod $to-base + 1,1)"/>
  <xsl:choose>
    <xsl:when test="$number &gt;= $to-base">
      <xsl:call-template name="ckbk:convert-from-base-10-impl">
        <xsl:with-param name="number" select="floor($number div $to-base)"/>
        <xsl:with-param name="to-base" select="$to-base"/>
        <xsl:with-param name="result" select="concat($to-base-digit,$result)"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="concat($to-base-digit,$result)"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Diskussion

Basiskonvertierungen sind eine gebräuchliche Programmieraufgabe. Die meisten Entwickler wissen, wie sie ausgeführt werden müssen. Viele Sprachen besitzen integrierte Vorrichtungen für diese Konvertierungen, XSLT allerdings nicht. Die Tatsache, dass weder XPath 1.0 noch XSLT 1.0 eine Möglichkeit bieten, um den Integer-Wert eines Unicode-Zeichens zu erhalten, macht diese Konvertierungen noch beschwerlicher. In XPath 2.0 können Sie die Funktionen string-to-codepoints und codepoints-to-string verwenden. Deswegen müssen Sie auf Tricks mit Strings zurückgreifen, die sich wie Suchtabellen verhalten. Diese Manipulationen sind ineffizient, aber für die meisten Konvertierungsansprüche ausreichend.

Der Code geht davon aus, dass bei einer Basis größer als 10 die Standardkonvention verfolgt wird, den Ziffern, die größer als 9 sind aufsteigend Buchstaben zuzuweisen. Falls Sie mit einer unkonventionellen Kodierung arbeiten, müssen Sie die Zuordnung der Strings entsprechend anpassen. Sie können diesen Code potenziell über die Basis 36 hinweg erweitern, indem Sie die Zeichen hinzufügen, die zum Kodieren von Ziffern höher als Z erforderlich sind.

  

<< 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