1. Diese Seite verwendet Cookies. Wenn du dich weiterhin auf dieser Seite aufhältst, akzeptierst du unseren Einsatz von Cookies. Weitere Informationen

XSLT nach CSV, Tags liegen aber ungeordnet vor

Dieses Thema im Forum "XML Technologien" wurde erstellt von sad3, 13. Mai 2013.

  1. sad3

    sad3 Grünschnabel

    Hallo,
    ich habe XML-Dateien, die folgendermaßen aufgebaut sind:

    Code (Text):
    1. <company id="0">
    2. <data name="a" section="1">text</data>
    3. <data name="a" section="2">text</data>
    4. <data name="b" section="1">text</data>
    5. <data name="b" section="3">text</data>
    6. ...
    7. </company>
    8. <company id="1">
    9. ...
    10. </company>
    11. ...
    Die Attribute "name" und "section" können beliebige Strings enthalten. Die Data-Tags sind allerdings nicht geordnet (Tags mit den gleichen Attributwerten kommen bei verschiedenen companies an unterschiedlichen Stellen) und es können auch Tags fehlen (z.B. hat eine Company ein '<data name="a" section="1">' -Tag, eine andere aber nicht.)

    Das Problem ist: ich möchte das ganze nun in eine CSV-Datei konvertieren (pro Company eine Zeile, in jeder Zelle der Inhalt eines Data-Tags), dabei sollten aber logischerweise in einer Spalte nur die Inhalte von Data-Tags mit gleicher name/section Kombination sein.
    So wie ich es momentan habe, werden die data-Inhalte in der Reihenfolge, in der sie im company-Tag vorkommen, in die CSV-Datei geschrieben. Das bringt mir natürlich nichts.

    Ein Versuch von mir war, die data-Tags nach den Attributen zu sortieren, aber das hilft auch nichts, wenn Tags fehlen (s.o.).

    Nun habe ich keinerlei Ansatzpunkt für dieses Problem.

    Kann mir bitte jemand helfen?

    Gruß,
    Sad3

    PS: aufrufende Technologie ist Java.
     
  2. Martin Honnen

    Martin Honnen Mitglied

    Erkläre mal genauer, wie das XML aussehen kann und welches Format du haben willst. Sind die Werte des "name"-Attributes immer die Buchstaben "a", "b", "c", ... und die Werte des "section"-Attributes immer positive, ganze Zahlen 1, 2, 3, ...? Wenn für eine "company" ein "data" mit "section" als z.b. 10 auftaucht, bedeutet das dann, dass für alle Zeilen die Spalten 1 ... 10 generiert werden müssen? Dann könnte man einfach das Maximum der "section"-Attribute bestimmen.

    Und wenn du Java benutzt, um XSLT aufzurufen, benutzt du dann Saxon 9 und XSLT 2.0? Oder nur XSLT 1.0?
     
  3. sad3

    sad3 Grünschnabel

    Danke schonmal für die Antwort.

    Wie schon gesagt, die Attribute "name" und "section" können beliebige Strings enthalten. (In der Praxis gibt es ca. 900 Kombinationen.) Man muss also nicht beim Auftauchen einer 10 auch die Spalten 1-9 generieren.

    Der Parser ist für XSLT 2.0, von Saxon habe ich keine Ahnung, kann man das irgendwo nachgucken?
     
  4. sad3

    sad3 Grünschnabel

    Das ist eine Beispiel-XML:
    Code (Text):
    1. <company id="0">
    2. <data name="a" section="q">text1</data>
    3. <data name="a" section="w">text2</data>
    4. <data name="b" section="r">text3</data>
    5. <data name="b" section="e">text4</data>
    6. <data name="a" section="e">text5</data>
    7. </company>
    8.  
    9. <company id="1">
    10. <data name="b" section="r">text6</data>
    11. <data name="a" section="q">text7</data>
    12. <data name="b" section="e">text8</data>
    13. <data name="a" section="e">text9</data>
    14. <data name="b" section="s">text0</data>
    15. </company>
    So wird es momentan ausgegeben:
    Code (Text):
    1. text1,text2,text3,text4,text5
    2. text6,text7,text8,text9,text10
    So soll es ausgegeben werden:
    Code (Text):
    1. text1,text2,text3,text4,text5,
    2. text7,,text6,text8,text9,text0

    Habe mal nachgefragt, Saxon 8.
     
  5. Martin Honnen

    Martin Honnen Mitglied

    Saxon 8 ist veraltet und existiert in zahlreichen Unterversionen, die verschiedene Stationen auf dem Weg zum XSLT 2.0 Standard implementiert haben. Die aktuelle Version von Saxon ist 9.5.

    Mit XSLT 2.0 könnte man das eventuell so lösen:

    Code (Text):
    1. <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    2.   xmlns:xs="http://www.w3.org/2001/XMLSchema"
    3.   xmlns:mf="http://example.org/mf"
    4.   exclude-result-prefixes="xs mf">
    5.  
    6. <xsl:param name="lf" as="xs:string" select="'
    7. '"/>
    8. <xsl:param name="cs" as="xs:string" select="','"/>
    9. <xsl:param name="rs" as="xs:string" select="'|'"/>
    10.  
    11. <xsl:output method="text"/>
    12.  
    13. <xsl:key name="k1" match="data" use="concat(@name, $rs, @section)"/>
    14.  
    15. <xsl:variable name="main-input" select="/"/>
    16.  
    17. <xsl:variable name="cols" as="element(data)+">
    18.   <xsl:for-each-group select="/root/company/data" group-by="concat(@name, $rs, @section)">
    19.     <!--
    20.     <xsl:sort select="@name"/>
    21.     <xsl:sort select="@section"/>
    22.     -->
    23.     <data key="{current-grouping-key()}"/>
    24.   </xsl:for-each-group>
    25. </xsl:variable>
    26.  
    27. <xsl:template match="root">
    28.   <xsl:value-of select="$cols/@key" separator="{$cs}"/>
    29.   <xsl:value-of select="$lf"/>
    30.   <xsl:apply-templates select="company"/>
    31. </xsl:template>
    32.  
    33. <xsl:template match="company">
    34.   <xsl:value-of select="for $col in $cols
    35.                         return
    36.                           (if (key('k1', $col/@key, current())) then key('k1', $col/@key, current()) else '')" separator="{$cs}"/>
    37.   <xsl:value-of select="$lf"/>
    38. </xsl:template>
    39.  
    40. </xsl:stylesheet>
    Das macht dann mit Saxon 9 aus der Eingabe
    Code (Text):
    1. <root>
    2. <company id="0">
    3. <data name="a" section="q">text1</data>
    4. <data name="a" section="w">text2</data>
    5. <data name="b" section="r">text3</data>
    6. <data name="b" section="e">text4</data>
    7. <data name="a" section="e">text5</data>
    8. </company>
    9.  
    10. <company id="1">
    11. <data name="b" section="r">text6</data>
    12. <data name="a" section="q">text7</data>
    13. <data name="b" section="e">text8</data>
    14. <data name="a" section="e">text9</data>
    15. <data name="b" section="s">text0</data>
    16. </company>
    17. </root>
    das Resultat
    Code (Text):
    1. a|q,a|w,b|r,b|e,a|e,b|s
    2. text1,text2,text3,text4,text5,
    3. text7,,text6,text8,text9,text0
    Die erste Zeile mit den Kombinationen muss man nicht ausgeben. Das Stylesheet hat drei Parameter, der Parameter "rs" sollte so gewählt werden, dass es ein Zeichen ist, das in data/@name und data/@section nicht vorkommt, damit es beim Gruppieren als Trennzeichen funktionieren kann.
     
  6. sad3

    sad3 Grünschnabel

    Hallo,
    einen ganz großes Dankeschön schonmal an dich, ich hatte nicht gedacht, dass ich das nur mit XLST noch hinbekomme.
    Ich versuche das jetzt bei mir zu übernehmen, wenn ich Probleme habe, melde ich mich nochmal.
    Viele Grüße,
    Sad3
     
  7. sad3

    sad3 Grünschnabel

    Ich muss jetzt nochmal nachfragen, ich komme einfach nicht weiter (ich bin halt auch Anfänger).

    Hier ist ein inhaltlich gekürztes, aber von der Form her vollständiges XML-Dokument:

    Code (Text):
    1. <calibrationDataFile>
    2. <account xmlns="http://#####/#####/#####/calibrationdata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" companyID="1" schemaCountry="DE" schemaAccountingType="IFRS_ISF" schemaVersion="2" accountID="51" identNumber="DE000911" status="RATING_DESIRED" certificationDate="2012-08-15T11:35:30.890000" referenceDate="2011-03-31">
    3.    <column name="generalJ" scenario="JahrGeneral">2011-03-31</column>
    4.    <column name="note11_q1" scenario="Vorjahr1Notes">2</column>
    5.    <column name="note11_q1" scenario="JahrNotes">1</column>
    6. </account>
    7. </calibrationDataFile>
    Die XSLT sieht so aus:

    Code (Text):
    1. <xsl:stylesheet version="2.0"
    2.     xmlns:bcx="http://#####/#####/#####/calibrationdata"
    3.     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    4.   xmlns:xs="http://www.w3.org/2001/XMLSchema">
    5.  
    6. <xsl:param name="lf" as="xs:string" select="'
    7. '"/>
    8. <xsl:param name="cs" as="xs:string" select="';'"/>
    9. <xsl:param name="rs" as="xs:string" select="'|'"/>
    10.  
    11. <xsl:output method="text"/>
    12.  
    13. <xsl:key name="k1" match="column" use="concat(@name, $rs, @scenario)"/>
    14.  
    15. <xsl:variable name="main-input" select="/"/>
    16.  
    17. <xsl:variable name="cols" as="element(column)+">
    18.   <xsl:for-each-group select="/calibrationDataFile/account/column" group-by="concat(@name, $rs, @scenario)">
    19.     <!--
    20.     <xsl:sort select="@name"/>
    21.     <xsl:sort select="@scenario"/>
    22.     -->
    23.     <column key="{current-grouping-key()}"/>
    24.   </xsl:for-each-group>
    25. </xsl:variable>
    26.  
    27. <xsl:template match="calibrationDataFile">
    28.   <xsl:value-of select="$cols/@key" separator="{$cs}"/>
    29.   <xsl:value-of select="$lf"/>
    30.   <xsl:apply-templates select="account"/>
    31. </xsl:template>
    32.  
    33. <xsl:template match="account">
    34.   <xsl:value-of select="for $col in $cols
    35.                         return
    36.                           (if (key('k1', $col/@key, current())) then key('k1', $col/@key, current()) else '')" separator="{$cs}"/>
    37.   <xsl:value-of select="$lf"/>
    38. </xsl:template>
    39.  
    40. </xsl:stylesheet>
    Dabei bekomme ich immer den Fehler:
    Ich denke, ich weiß, was das bedeutet, aber ich bekomme es einfach nicht hin, die XSLT so zu ändern, dass die column-Tags erkannt werden. Was geändert werden muss ist doch bestimmt das select in Zeile 18, aber ich habe einfach zu wenig Ahnung von XSLT und Namespaces. Ich habe schon alles probiert, ich bitte um Hilfe!
     
  8. Martin Honnen

    Martin Honnen Mitglied

    Ich habe das XSLT angepasst:
    Code (Text):
    1. <xsl:stylesheet version="2.0"
    2.     xmlns:bcx="http://#####/#####/#####/calibrationdata"
    3.     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    4.   xmlns:xs="http://www.w3.org/2001/XMLSchema">
    5.  
    6. <xsl:param name="lf" as="xs:string" select="'
    7. '"/>
    8. <xsl:param name="cs" as="xs:string" select="';'"/>
    9. <xsl:param name="rs" as="xs:string" select="'|'"/>
    10.  
    11. <xsl:output method="text"/>
    12.  
    13. <xsl:key name="k1" match="bcx:column" use="concat(@name, $rs, @scenario)"/>
    14.  
    15. <xsl:variable name="main-input" select="/"/>
    16.  
    17. <xsl:variable name="cols" as="element(column)+">
    18.   <xsl:for-each-group select="/calibrationDataFile/bcx:account/bcx:column" group-by="concat(@name, $rs, @scenario)">
    19.     <!--
    20.     <xsl:sort select="@name"/>
    21.     <xsl:sort select="@scenario"/>
    22.     -->
    23.     <column key="{current-grouping-key()}"/>
    24.   </xsl:for-each-group>
    25. </xsl:variable>
    26.  
    27. <xsl:template match="calibrationDataFile">
    28.   <xsl:value-of select="$cols/@key" separator="{$cs}"/>
    29.   <xsl:value-of select="$lf"/>
    30.   <xsl:apply-templates select="bcx:account/bcx:column"/>
    31. </xsl:template>
    32.  
    33. <xsl:template match="bcx:column">
    34.   <xsl:value-of select="for $col in $cols
    35.                         return
    36.                           (if (key('k1', $col/@key, current())) then key('k1', $col/@key, current()) else '')" separator="{$cs}"/>
    37.   <xsl:value-of select="$lf"/>
    38. </xsl:template>
    39.  
    40. </xsl:stylesheet>
    Mit dem Beispiel
    Code (Text):
    1. <calibrationDataFile>
    2. <account xmlns="http://#####/#####/#####/calibrationdata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" companyID="1" schemaCountry="DE" schemaAccountingType="IFRS_ISF" schemaVersion="2" accountID="51" identNumber="DE000911" status="RATING_DESIRED" certificationDate="2012-08-15T11:35:30.890000" referenceDate="2011-03-31">
    3.    <column name="generalJ" scenario="JahrGeneral">2011-03-31</column>
    4.    <column name="note11_q1" scenario="Vorjahr1Notes">2</column>
    5.    <column name="note11_q1" scenario="JahrNotes">1</column>
    6. </account>
    7. </calibrationDataFile>
    gibt Saxon 9.5 dann
    Code (Text):
    1. generalJ|JahrGeneral;note11_q1|Vorjahr1Notes;note11_q1|JahrNotes
    2. 2011-03-31;;
    3. ;2;
    4. ;;1
    aus.
     
  9. Martin Honnen

    Martin Honnen Mitglied

    Eine Anmerkung noch, das Forum gibt numerische Zeichenreferenzen in Codebeispielen nicht korrekt aus, der Parameter "lf" wird als "& # 1 0;" (nur ohne die Leerzeichen zwischen den Zeichen) definiert.
     
  10. sad3

    sad3 Grünschnabel

    Hallo,
    vielen Dank nochmal für deine Hilfe, es läuft jetzt alles super.

    Ich habe noch ein Problem, vielleicht kannst du mir auch dabei helfen. Der Inhalt der column-Tags kann Zeilenumbrüche enthalten, die aber logischerweise für eine csv-Datei nicht hilfreich sind. Wie kann man die entfernen?
    Strip-space ist nur für Whitespace-Nodes zuständig, und normalize-Space nur für Whitespaces generell.
     
  11. Martin Honnen

    Martin Honnen Mitglied

    Eventuell reicht "translate", sonst gibt es mit XSLT 2.0 auch "replace"::
    Code (Text):
    1.   <xsl:value-of select="for $col in $cols
    2.                         return
    3.                           (if (key('k1', $col/@key, current())) then replace(key('k1', $col/@key, current()), '\n', '') else '')" separator="{$cs}"/>
     
    sheel gefällt das.
  12. sad3

    sad3 Grünschnabel

    Hallo,
    vielen Dank nochmal für deine Hilfe, das Projekt ist jetzt abgeschlossen und alles läuft wunderbar.
    Viele Grüße,
    sad3
     
Die Seite wird geladen...