Kai: Summe von Werten, wo erst noch etwas abgeschnitten werden muss

Hallo,

so langsam weiß ich echt nicht mehr weiter. Ich möchte die Spaltenbreite einer Tabelle aus dem XML auslesen, und es mit XSL darstellen. Das Problem hierbei ist, dass die Spaltenbreiten von dem Programm, mit dem die XML erstellt werden, prozentual im Vergleich zu vorher gespeichert werden. WEnn man also eine Tabelle mit 3 Spalten hat, bekommt jede den gleichen Platz, verkleinere ich die Spalte wird zB 0.8* draus oder beim Vergrößern zB 1.3*.

Hier ein Ausschnitt der Tabelle aus dem XML:

<table><title>materials</title>  
<tgroup cols="3"><colspec colwidth="1.36*"/>  
<colspec colwidth="1.16*"/><colspec colwidth="0.50*"/>  
<thead>  
<row>  
<entry>D</entry>  
<entry>P</entry>  
<entry>M</entry>  
</row>  
</thead>  
<tbody>  
<row>  
<entry>A</entry>  
<entry>5</entry>  
<entry>1</entry>  
</row></tbody></table>

Ich dachte, dass ich die Spaltenbreite bekomme, wenn ich alle colwidth aufsummiere, anschließend 100 durch den Wert teile und diesen anschließend mit den einzelnen Werten von colwidth multipliziere um den Spaltenbreitenwert in % angeben zu können.

Ich habe gelesen dass man Variablen nicht nach der Initialisierung in XSL verändern kann. Anfangs dachte ich wenn ich eine globale Variable nehme, vielleicht kann ich sie dann selbst verändern. Denn ich wollte eine Variable anlegen, dann die for-each-schleife in der ich bei allen colwidth das * abschneide und dann addiere. Allerdings ist das Problem dass ich dann die einzelnen WErte in einer Variablen stehe habe und sum möchte ja Knoten haben. Knoten kann ich aber nicht nehmen, da das * beim Summieren stört. Dann dachte ich wenn ich die einzelnen Werte in eine Variable mit Semikolon abgetrennt schreiben lasse und diese an javascript übergebe und dort summiere, das müsste gehen. Aber dann hab ich das Problem wie kann ich mit dem Ergebnis in XSL weitermachen.

Mein Versuch bisher:
<xsl:template match="tgroup">
  <xsl:apply-templates select="*"/>
</xsl:template>

<xsl:template match="colspec">
  <col>
    <xsl:if test="@colwidth and substring(@colwidth, string-length(@colwidth), 1)='*'">
      <xsl:variable name="test">
<xsl:for-each select="..//@colwidth">
  <xsl:variable name="width"><xsl:value-of select="number(substring(..//@colwidth, 0, string-length(..//@colwidth)))"/></xsl:variable>
          <xsl:value-of select="$width"/>;
        </xsl:for-each>
      </xsl:variable>
<!--<script type="text/javascript">Javascript:var summe = parent.frames[1].addieren('<xsl:value-of select="$test"/>');</script>-->
<!--<xsl:variable name="widthALL"><xsl:value-of select="javascript:parent.frames[1].addieren('$test');"/>);</xsl:variable>-->
      <xsl:attribute name="width"><xsl:value-of select="round($test)"/></xsl:attribute>
    </xsl:if>
  </col>
</xsl:template>

Jetz werden natürlich nur die Werte hinteinander mit einem Semikolon getrennt in width hineingeschrieben, an der Stelle wo <xsl:value-of select="$width"/>; steht hatte ich mal <xsl:value-of select="sum($width)"/> stehen, aber das geht nicht, weil er Knoten erwartet.

Vielen Dank im Voraus
Viele Grüße
Kai

  1. Hallo Kai,

    Ich habe gelesen dass man Variablen nicht nach der Initialisierung in XSL verändern kann. Anfangs dachte ich wenn ich eine globale Variable nehme, vielleicht kann ich sie dann selbst verändern.

    Nein, (auch globale) Variablen lassen sich nach dem Festlegen nicht ändern, es sind quasi Konstanten. Man kann Variablen jedoch innerhalb von xsl:for-each, xsl:template usw. jeweils neu belegen.

    Dein eigentliches Summierungsproblem lässt sich mit XSLT 2.0 elegant lösen:

    <xsl:template match="table">  
      <xsl:value-of select="fn:sum(for $cw in tgroup/colspec/@colwidth return fn:number(fn:substring-before($cw, '*')))"/>  
    </xsl:template>
    

    Ergebnis: 3.02

    Mit den Mitteln von XSLT 1.0 kann das mit einem benannten Template und xsl:call-template sowie xsl:with-param und xsl:param etwas aufwändiger erreicht werden.

    Grüße,
    Thomas

    1. Hi Thomas,

      vielen lieben Dank für deine Antwort. Habe sie leider durch den Feiertag erst heute gelesen.

      Nein, (auch globale) Variablen lassen sich nach dem Festlegen nicht ändern, es sind quasi Konstanten. Man kann Variablen jedoch innerhalb von xsl:for-each, xsl:template usw. jeweils neu belegen.

      Dass man globale Variablen nicht mehr ändern kann, ist natürlich schade

      Dein eigentliches Summierungsproblem lässt sich mit XSLT 2.0 elegant lösen:

      <xsl:template match="table">

      <xsl:value-of select="fn:sum(for $cw in tgroup/colspec/@colwidth return fn:number(fn:substring-before($cw, '*')))"/>
      </xsl:template>

      Bisher verwende ich XSLT 1.0, ich denke wenn ich jetzt auf XSLT 2.0 umstelle, werde ich an vielen Stellen erstmal ein Problem bekommen oder? Habe nämlich an die 1000Zeilen in meiner XSLT-Datei, da wir verschiedene XML-Dateien (mit unterschiedener STruktur )haben. Oder unterstützt XSLT 2.0 alles von XSLT 1.0?  
        
      
      >   
      > Ergebnis: 3.02  
      >   
      > Mit den Mitteln von XSLT 1.0 kann das mit einem benannten Template und xsl:call-template sowie xsl:with-param und xsl:param etwas aufwändiger erreicht werden.  
        
      Das habe ich auch schon probiert, aber bin leider zu keinem lauffähigen XSL gekommen :( vielleicht könntest du mir einen Tipp geben was ich falsch mache:  
      <col>  
        <xsl:if test="@colwidth and substring(@colwidth, string-length(@colwidth), 1)='\*'">  
          <xsl:for-each select="..//@colwidth">  
            <xsl:variable name="width"><xsl:value-of select="number(substring(..//@colwidth, 0, string-length(..//@colwidth)))"/></xsl:variable>  
            <xsl:call-template name="bilde\_summe">  
              <xsl:with-param name="colwidth" select="$width"/>  
            </xsl:call-template>  
          </xsl:for-each>  
          <xsl:attribute name="width"><xsl:value-of select="$colwidth"/></xsl:attribute>  
        </xsl:if>	  
      </col>  
        
        <xsl:template name="bilde\_summe">  
          <xsl:param name="colwidth"/>  
      	<xsl:choose>  
            <xsl:when test="$colwidth">  
              <xsl:variable name="summe">  
                <xsl:call-template name="bilde\_summe">  
                  <xsl:with-param name="colwidth" select="$colwidth"/>  
                </xsl:call-template>  
              </xsl:variable>  
              <xsl:value-of select="$summe + $colwidth"/>  
            </xsl:when>  
            <xsl:otherwise>0</xsl:otherwise>  
          </xsl:choose>  
        </xsl:template>  
        
      Ich muss aber ehrlichgesagt sagen dass ich noch nie ein template geschrieben habe, das über with-param aufgerufen wird. Irgendwie müsste ich ja von for-each den aktuellen Wert $width übergeben und in bilde\_summe übergeben und addieren, aber das Problem ist, dass ich die Variable, in der die Summe gespeichert ist, ja nicht in sich selber aufrufen kann.  
        
      Viele Grüße  
      Kai
      
      1. Hallo Kai,

        Bisher verwende ich XSLT 1.0, ich denke wenn ich jetzt auf XSLT 2.0 umstelle, werde ich an vielen Stellen erstmal ein Problem bekommen oder? Habe nämlich an die 1000Zeilen in meiner XSLT-Datei, da wir verschiedene XML-Dateien (mit unterschiedener STruktur )haben. Oder unterstützt XSLT 2.0 alles von XSLT 1.0?

        Im Grunde sollte die Verwendung von version="2.0" funktionieren. Es kann bzgl. der Behandlung von Knotenmengen als sog. Sequenzen evtl. zu anderen Ergebnissen kommen, dann müsste man genauer nachsehen. Probiere es mal aus.

        Ich muss aber ehrlichgesagt sagen dass ich noch nie ein template geschrieben habe, das über with-param aufgerufen wird. Irgendwie müsste ich ja von for-each den aktuellen Wert $width übergeben und in bilde_summe übergeben und addieren, aber das Problem ist, dass ich die Variable, in der die Summe gespeichert ist, ja nicht in sich selber aufrufen kann.

        Mein eingangs verwendetes Beispiel habe ich mal für 1.0 umgeschrieben (Ergebnis wiederum 3.02):

        <xsl:template match="table">  
          <xsl:call-template name="cw_summe">  
            <xsl:with-param name="summe" select="0"/>  
            <xsl:with-param name="i" select="count(tgroup/colspec[@colwidth])"/>  
          </xsl:call-template>  
        </xsl:template>  
          
        <xsl:template name="cw_summe">  
          <xsl:param name="summe"/>  
          <xsl:param name="i"/>  
          
          <xsl:choose>  
            <xsl:when test="$i > 0">  
              <xsl:variable name="cw" select="number(substring-before(tgroup/colspec[@colwidth][$i]/@colwidth, '*'))"/>  
              <xsl:call-template name="cw_summe">  
                <xsl:with-param name="summe" select="$summe + $cw"/>  
                <xsl:with-param name="i" select="$i - 1"/>  
              </xsl:call-template>  
            </xsl:when>  
            <xsl:otherwise>  
               <xsl:value-of select="$summe"/>  
            </xsl:otherwise>  
          </xsl:choose>  
        </xsl:template>
        

        Grüße,
        Thomas

        1. Hi Thomas,

          Im Grunde sollte die Verwendung von version="2.0" funktionieren. Es kann bzgl. der Behandlung von Knotenmengen als sog. Sequenzen evtl. zu anderen Ergebnissen kommen, dann müsste man genauer nachsehen. Probiere es mal aus.

          Er erkennt denke ich XSLT 2.0 nicht, da er die Funktion nicht kennt bzw den Präfix fn. Es kam folgende Fehlermeldung: "Verweis auf ein nicht deklariertes Namespace-Präfix: 'fn'. "

          Mein eingangs verwendetes Beispiel habe ich mal für 1.0 umgeschrieben (Ergebnis wiederum 3.02):

          Wow vielen lieben Dank, dass du mir so super geholfen hast :)) es funktioniert, habe es unter colspec umgesetzt als Element <col> eingebaut.

          Viele Grüße
          Kai

          1. Hallo Kai,

            Er erkennt denke ich XSLT 2.0 nicht, da er die Funktion nicht kennt bzw den Präfix fn. Es kam folgende Fehlermeldung: "Verweis auf ein nicht deklariertes Namespace-Präfix: 'fn'. "

            fn referenziert den Namensraum für XPath-Funktionen: xmlns:fn="http://www.w3.org/2005/xpath-functions"

            Man kann das fn-Präfix auch weglassen und sum() statt fn:sum() schreiben. Ich schreibe es jedoch konsequent dazu, um mir immer den Unterschied zwischen XPath- und XSLT-Funktionen klar zu machen.

            Beispiel:
            fn:current-date() ist eine XPath-Funktion, aber um ein damit erhaltenes Datum zu formatieren verwendet man die XSLT-Funktion format-date().

            es funktioniert, habe es unter colspec umgesetzt als Element <col> eingebaut.

            Freut mich.

            Grüße,
            Thomas