Announcement

Collapse
No announcement yet.

Xml - xslt - csv

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Xml - xslt - csv

    Hi,

    ich möchte aus einer XML-Datei via XSLT eine CSV-Datei erzeugen.

    Ressourcen:

    In den Zeilen der CSV-Datei sollen Werte aus verschiedenen Hierarchiestufen ausgegeben werden.

    Leider matcht der hierarchisch höhergelegene Wert zu früh, so daß das von mir angestrebte Zeilenkonstrukt "zerrissen" wird.

    Wie wäre die beste herangehensweise, um ein möglichst übersichtiches XSLT zu erhalten, welches die Felder in der von mir vorgegebenen Reihenfolge ausgibt und es notfalls auch einem unbedarften Dritten erlaubt, die Felder schnell neu anzuordnen?

    Vielen Dank im voraus!

    PS: Welches ist das beste englischsprachige Forum zum Thema XML & Co?
    --
    Cheers Vince

  • #2
    Du kannst ja die Elemente selektieren, die verarbeitet werden solllen; wenn jedes "InitTAP" eine Zeile bilden soll und andere Elemente nicht interessieren, dann schreibe ein Template mit match="/", in dem ein apply-templates select="descendant::InitTAP" ausgeführt wird:

    Code:
    <xsl:stylesheet
      version="2.0"
      xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      xpath-default-namespace="https://infocentre.gsm.org/TADIG-RTDR">
    
      <xsl:output method="text" encoding="utf-8"/>
      
      <xsl:template match="/">
        <xsl:apply-templates select="descendant::InitTAP"/>
      </xsl:template>
    
      <xsl:template match="InitTAP">
        <xsl:text>BOL</xsl:text>
        <xsl:apply-templates select="VPMN" />
        <xsl:apply-templates select="TAPSeqNo" />
        <xsl:text>EOL
    </xsl:text>  
      </xsl:template>
    
      <xsl:template match="VPMN | TAPSeqNo">
        <xsl:value-of select="concat(' ', local-name(), ' [')"/>
        <xsl:value-of select="./text()" />
        <xsl:text>] ; </xsl:text>
      </xsl:template>
    
    </xsl:stylesheet>
    Ob das schon reicht, bin ich nicht sicher, dein Posting erklärt nicht genau, welche Ausgabe du erreichen willst.

    Comment


    • #3
      Vpmn

      Originally posted by Martin Honnen View Post
      Du kannst ja die Elemente selektieren, die verarbeitet werden solllen; wenn jedes "InitTAP" eine Zeile bilden soll und andere Elemente nicht interessieren, dann schreibe ein Template mit match="/", in dem ein apply-templates select="descendant::InitTAP" ausgeführt wird: ...

      Ob das schon reicht, bin ich nicht sicher, dein Posting erklärt nicht genau, welche Ausgabe du erreichen willst.
      Oh, stimmt, mein Fehler. Vielen Dank erstmal für die Antwort und die Idee!

      Das eigentliche Problem ist, daß VPMN in der Hierarchie über InitTAP steht.

      Die Zeilen sollten idealerweise so aussehen (ich lasse das Debug-Markup mal weg):

      VPMN;TAPSeqNo;...

      Mit anderen Worten: Wann immer InitTAP auftaucht, soll eine neue Zeile erzeugt werden. Diese Zeile enthält dann Werte, die über- und unterhalb der Hierarchieebene von InitTAP liegen.

      Während ich mir gut vorstellen kann, daß ich in dem XML nach unten "abtauche", habe ich keine Vorstellung wie ich den Wert von VPMN an dieser Stelle heranziehe, wenn (und nur wenn) ein neues InitTAP beginnt und entsprechend transformiert wird. Aktuell wird, da VPMN ja bereits vor dem ersten InitTAP geparst wird, es dort auch schon ausgegeben.

      Ich kann mir zwar viele Varianten vorstellen (mit Parametersn, xsl:if usw), weiß aber nicht, welche "best practice" wäre und möchte das XSL nicht zu sehr überfrachten.

      Später kann es zudem ja auch gefordert sein, die Reihenfolge der Felder zu ändern, wenn sich die Tabellenstrukturen ändern sollten - und da benötige ich eine möglichst saubere und gleichzeitig flexible Lösung, damit auch ein XML-unkundiger solche Rearrangements vornehmen könnte.
      --
      Cheers Vince

      Comment


      • #4
        Mit XPath kannst du auch zu anderen Ebenen navigieren:
        Code:
        <xsl:stylesheet
          version="2.0"
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xpath-default-namespace="https://infocentre.gsm.org/TADIG-RTDR">
        
          <xsl:param name="ls" select="'
        '"/>
          <xsl:param name="rs" select="';'"/>
          
          <xsl:output method="text" encoding="utf-8"/>
          
          <xsl:template match="/">
            <xsl:apply-templates select="descendant::InitTAP"/>
          </xsl:template>
        
          <xsl:template match="InitTAP">
            <xsl:apply-templates select="ancestor::Connection/VPMN" />
            <xsl:apply-templates select="TAPSeqNo" />
            <xsl:value-of select="$ls"/>
          </xsl:template>
        
          <xsl:template match="VPMN | TAPSeqNo">
            <xsl:value-of select="concat(., $rs)"/>
          </xsl:template>
        
        </xsl:stylesheet>

        Comment


        • #5
          Super!

          Hi,

          vielen Dank! Das ist genau der Ansatz, den ich gebraucht habe - jetzt müßte der Rest leicht von der Hand gehen ... hoffentlich.

          Danke nochmal!
          --
          Cheers Vince

          Comment


          • #6
            Eventuell, wenn wirklich nur bestimmte Elemente, getrennt durch ";" ausgegeben werden sollen, und du einen XSLT 2.0 Prozessor benutzt, könnte man das ganze auch wie folgt verkürzen:
            Code:
            <xsl:stylesheet
              version="2.0"
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
              xpath-default-namespace="https://infocentre.gsm.org/TADIG-RTDR">
            
              <xsl:param name="ls" select="'
            '"/>
              <xsl:param name="rs" select="';'"/>
              
              <xsl:output method="text" encoding="utf-8"/>
              
              <xsl:template match="/">
                <xsl:apply-templates select="descendant::InitTAP"/>
              </xsl:template>
            
              <xsl:template match="InitTAP">
                <xsl:value-of select="ancestor::Connection/VPMN,
                                      TAPSeqNo"
                              separator="{$rs}"/>
                <xsl:value-of select="$ls"/>
              </xsl:template>
            
            </xsl:stylesheet>
            Dann braucht man kein Templates mehr für die einzelnen Elemente, man zählt diese direkt im select-Attribut des value-of auf.

            Comment


            • #7
              Stimmt ...

              Hi,

              Originally posted by Martin Honnen View Post
              Eventuell, wenn wirklich nur bestimmte Elemente, getrennt durch ";" ausgegeben werden sollen, und du einen XSLT 2.0 Prozessor benutzt, könnte man das ganze auch wie folgt verkürzen: ...

              Dann braucht man kein Templates mehr für die einzelnen Elemente, man zählt diese direkt im select-Attribut des value-of auf.
              Das ist auch eine Idee ... ich muß allerdings erstmal gucken, ob einzelne Werte ggf noch weiter transformiert werden müssen ... vielleicht wird's letztlich auch ein hybrider Ansatz - ich bin selber gespannt.
              --
              Cheers Vince

              Comment


              • #8
                Weiter geht's ...

                Hi,

                soweit funktioniert das aktuelle XSLT.

                Meine weiteren Überlegungen sehen nun wie folgt aus:
                • Feldbezeichnung Ich müßte irgendwie die erste Zeile zweimal parsen, damit ich im ersten Durchlauf die Feldbezeichnungen und im zweiten dann die Werte der ersten Zeile ausgeben lassen könnte. Funktioniert sowas mit xsl:copy / xsl:copy-of?

                • Allgemeine Optionen Die Ausgabe mit Komma oder Semikolon und mit oder ohne Anführungszeichen könnte man einfach über xsl : param steuern.

                • Feldinhalte Je nach Zielsystem muß ich neben unterschiedlichen Optionen auch die Werte konvertieren (Dezimal-Punkt respektive Dezimal-Komma); am besten wäre wohl bzgl der Konvertierung die Templates nach Art des Feldinhalts zu separieren (Text, Zahl, Datum) und diese dann entsprechend aufzubereiten. Gibt es direkt eine Funktion, die einen Wert entsprechend formatiert?



                Vielen Dank für alle Tips und Hinweise im voraus!
                --
                Cheers Vince

                Comment


                • #9
                  Wenn man Knoten mehrfach verarbeiten will, macht man das per "mode" also etwa:
                  Code:
                  <xsl:template match="/">
                                  <xsl:apply-templates select="descendant::InitTAP[1]" mode="header"/>
                                  <xsl:apply-templates select="descendant::InitTAP"/>
                  </xsl:template>
                  
                  
                          <xsl:template match="InitTAP" mode="header>
                  
                                  <xsl:apply-templates select="ancestor::Connection/VPMN" mode="header"/>
                  
                                  <xsl:apply-templates select="ancestor::Connection/HPMN" mode="header"/>
                  
                                  <xsl:apply-templates select="TAPSeqNo" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TAPTxCutoffTmstp" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::InvPeriod" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TotalNoOfCalls" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TotalNetCharge" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TotalTax" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TAPCurrency" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TotalNetChargeSDR" mode="header"/>
                  
                                  <xsl:apply-templates select="descendant::TotalTaxSDR" mode="header"/>
                  
                                  <xsl:value-of select="$NewLine"/>
                  
                          </xsl:template>
                  
                  
                  
                          <xsl:template match="*" mode="header">
                  
                                  <xsl:value-of select="concat(local-name(), $FieldSeparator)"/>
                  
                          </xsl:template>
                  Zum Formatieren von Zahlen gibt es format-number: http://www.w3.org/TR/xslt20/#format-number

                  Comment


                  • #10
                    Fast perfekt ...

                    Hi,

                    Originally posted by Martin Honnen View Post
                    Wenn man Knoten mehrfach verarbeiten will, macht man das per "mode" also etwa: ...

                    Zum Formatieren von Zahlen gibt es format-number: http://www.w3.org/TR/xslt20/#format-number
                    Großartig ... das XSLT ist fast perfekt. Theoretisch würd' ich ganz gerne die Periode noch in den letzten Tag des Monats umwandeln, aber das scheint ein größerer Aufwand zu sein, den zu unternehmen es kaum lohnt (etwas soll die Datenbank ja auch noch tun :P).

                    Gibt es denn bezüglich der äußeren Form ggf noch etwas zu verbessern?
                    (Hinweis: Ich weiß nicht, ob ich einen XSLT 1.0 oder 2.0 Prozessor zur Verfügung haben werde, das Stylesheet muß so leicht abänderbar wie nur möglich sein (daher die vielen Parameter in der "General" section).)
                    --
                    Cheers Vince

                    Comment


                    • #11
                      Wenn du einen XSLT 1.0 Prozessor verwenden willst oder musst, dann solltest du im Stylesheet version="1.0" setzen und mit dem XSLT 1.0 Prozessor testen.

                      Was mit XSLT 1.0 auf keinen Fall funktioniert, ist xpath-default-namespace und format-dateTime. Statt xpath-default-namespace müsstest du dann, wie in deinem ersten Stylesheet, das du gepostet hattest, einen Präfix für den Namensraum definieren und den Präfix überall verwenden, wo du Elemente selektieren willst.

                      Für format-dateTime gibt es in XSLT 1.0 keinen Ersatz, wenn der XSLT 1.0 Prozessor nicht http://www.exslt.org/date/functions/...ate/index.html unterstützt.

                      Wenn in deinem XML z.b. '2010-09' den September des Jahres 2010 darstellt und du willst (mit XSLT 2.0!) den letzten Tag dieses Monat berechnen, dann per
                      Code:
                      <xsl:value-of select="day-from-date(xs:date(concat('2010-09', '-01')) + xs:yearMonthDuration('P1M') - xs:dayTimeDuration('P1D'))
                      Dabei wird ein Wert vom Typ xs:date konstruiert, dann wird ein Monat addiert und ein Tag subtrahiert, um ein xs:date mit dem letzten Tag des Monat zu bekommen, dann wird per day-form-date der Tag ausgegeben.

                      Comment


                      • #12
                        Fertig.

                        Hi,

                        ich denke das ist es jetzt.

                        Wenn Du noch weitere Anregungen zu Verbesserungen hast: Immer her damit - ich bin begierig mehr zu lernen ...

                        Bzgl XSLT 1.0 vs 2.0: Ich denke, ich vertraue aus Faulheit jetzt darauf, daß es ein XSLT 2.0 Prozessor sein wird. :P
                        --
                        Cheers Vince

                        Comment


                        • #13
                          Als Verbesserung zu den beiden Funktionen schlage ich vor, den Typ des Funktionsresultates per "as"-Attribut anzugeben und dann statt value-of direkt sequence zu verwenden, also etwa
                          Code:
                                  <xsl:function name="my:ultimo-day" as="xs:integer">
                          
                                          <xsl:param name="Period" as="xs:gYearMonth"/>
                          
                                          <xsl:sequence select="day-from-date(
                          
                                                  xs:date(concat($Period, '-01')) + xs:yearMonthDuration('P1M') - xs:dayTimeDuration('P1D')
                          
                                          )"/>
                          
                                  </xsl:function>
                          Das ist sauberer und auch effizienter, als den Typ nicht zu definieren und per value-of einen Textknoten zu erzeugen.

                          Comment

                          Working...
                          X