XSLT nach CSV, Tags liegen aber ungeordnet vor

sad3

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

Code:
<company id="0">
<data name="a" section="1">text</data>
<data name="a" section="2">text</data>
<data name="b" section="1">text</data>
<data name="b" section="3">text</data>
...
</company>
<company id="1">
...
</company>
...

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.
 
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?
 
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?
 
Das ist eine Beispiel-XML:
Code:
<company id="0">
<data name="a" section="q">text1</data>
<data name="a" section="w">text2</data>
<data name="b" section="r">text3</data>
<data name="b" section="e">text4</data>
<data name="a" section="e">text5</data>
</company>

<company id="1">
<data name="b" section="r">text6</data>
<data name="a" section="q">text7</data>
<data name="b" section="e">text8</data>
<data name="a" section="e">text9</data>
<data name="b" section="s">text0</data>
</company>

So wird es momentan ausgegeben:
Code:
text1,text2,text3,text4,text5
text6,text7,text8,text9,text10

So soll es ausgegeben werden:
Code:
text1,text2,text3,text4,text5,
text7,,text6,text8,text9,text0


Habe mal nachgefragt, Saxon 8.
 
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:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:mf="http://example.org/mf"
  exclude-result-prefixes="xs mf">

<xsl:param name="lf" as="xs:string" select="'
'"/>
<xsl:param name="cs" as="xs:string" select="','"/>
<xsl:param name="rs" as="xs:string" select="'|'"/>

<xsl:output method="text"/>

<xsl:key name="k1" match="data" use="concat(@name, $rs, @section)"/>

<xsl:variable name="main-input" select="/"/>

<xsl:variable name="cols" as="element(data)+">
  <xsl:for-each-group select="/root/company/data" group-by="concat(@name, $rs, @section)">
    <!--
    <xsl:sort select="@name"/>
    <xsl:sort select="@section"/>
    -->
    <data key="{current-grouping-key()}"/>
  </xsl:for-each-group>
</xsl:variable>

<xsl:template match="root">
  <xsl:value-of select="$cols/@key" separator="{$cs}"/>
  <xsl:value-of select="$lf"/>
  <xsl:apply-templates select="company"/>
</xsl:template>

<xsl:template match="company">
  <xsl:value-of select="for $col in $cols
                        return
                          (if (key('k1', $col/@key, current())) then key('k1', $col/@key, current()) else '')" separator="{$cs}"/>
  <xsl:value-of select="$lf"/>
</xsl:template>

</xsl:stylesheet>

Das macht dann mit Saxon 9 aus der Eingabe
Code:
<root>
<company id="0">
<data name="a" section="q">text1</data>
<data name="a" section="w">text2</data>
<data name="b" section="r">text3</data>
<data name="b" section="e">text4</data>
<data name="a" section="e">text5</data>
</company>
 
<company id="1">
<data name="b" section="r">text6</data>
<data name="a" section="q">text7</data>
<data name="b" section="e">text8</data>
<data name="a" section="e">text9</data>
<data name="b" section="s">text0</data>
</company>
</root>
das Resultat
Code:
a|q,a|w,b|r,b|e,a|e,b|s
text1,text2,text3,text4,text5,
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.
 
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
 
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:
<calibrationDataFile>
<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">
   <column name="generalJ" scenario="JahrGeneral">2011-03-31</column>
   <column name="note11_q1" scenario="Vorjahr1Notes">2</column>
   <column name="note11_q1" scenario="JahrNotes">1</column>
</account>
</calibrationDataFile>

Die XSLT sieht so aus:

Code:
<xsl:stylesheet version="2.0"
	xmlns:bcx="http://#####/#####/#####/calibrationdata"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
<xsl:param name="lf" as="xs:string" select="'
'"/>
<xsl:param name="cs" as="xs:string" select="';'"/>
<xsl:param name="rs" as="xs:string" select="'|'"/>
 
<xsl:output method="text"/>
 
<xsl:key name="k1" match="column" use="concat(@name, $rs, @scenario)"/>
 
<xsl:variable name="main-input" select="/"/>
 
<xsl:variable name="cols" as="element(column)+">
  <xsl:for-each-group select="/calibrationDataFile/account/column" group-by="concat(@name, $rs, @scenario)">
    <!--
    <xsl:sort select="@name"/>
    <xsl:sort select="@scenario"/>
    -->
    <column key="{current-grouping-key()}"/>
  </xsl:for-each-group>
</xsl:variable>
 
<xsl:template match="calibrationDataFile">
  <xsl:value-of select="$cols/@key" separator="{$cs}"/>
  <xsl:value-of select="$lf"/>
  <xsl:apply-templates select="account"/>
</xsl:template>
 
<xsl:template match="account">
  <xsl:value-of select="for $col in $cols
                        return
                          (if (key('k1', $col/@key, current())) then key('k1', $col/@key, current()) else '')" separator="{$cs}"/>
  <xsl:value-of select="$lf"/>
</xsl:template>
 
</xsl:stylesheet>

Dabei bekomme ich immer den Fehler:
Error on line 16 of file:///C:/temp/KalibrierungsDatenExport/test.xslt:
XTTE0570: An empty sequence is not allowed as the value of variable $cols
; SystemID: file:///C:/temp/KalibrierungsDatenExport/test.xslt; Line#: 16; Column#: -1
net.sf.saxon.trans.DynamicError: An empty sequence is not allowed as the value of variable $cols

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!
 
Ich habe das XSLT angepasst:
Code:
<xsl:stylesheet version="2.0"
    xmlns:bcx="http://#####/#####/#####/calibrationdata"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
<xsl:param name="lf" as="xs:string" select="'
'"/>
<xsl:param name="cs" as="xs:string" select="';'"/>
<xsl:param name="rs" as="xs:string" select="'|'"/>
 
<xsl:output method="text"/>
 
<xsl:key name="k1" match="bcx:column" use="concat(@name, $rs, @scenario)"/>
 
<xsl:variable name="main-input" select="/"/>
 
<xsl:variable name="cols" as="element(column)+">
  <xsl:for-each-group select="/calibrationDataFile/bcx:account/bcx:column" group-by="concat(@name, $rs, @scenario)">
    <!--
    <xsl:sort select="@name"/>
    <xsl:sort select="@scenario"/>
    -->
    <column key="{current-grouping-key()}"/>
  </xsl:for-each-group>
</xsl:variable>
 
<xsl:template match="calibrationDataFile">
  <xsl:value-of select="$cols/@key" separator="{$cs}"/>
  <xsl:value-of select="$lf"/>
  <xsl:apply-templates select="bcx:account/bcx:column"/>
</xsl:template>
 
<xsl:template match="bcx:column">
  <xsl:value-of select="for $col in $cols
                        return
                          (if (key('k1', $col/@key, current())) then key('k1', $col/@key, current()) else '')" separator="{$cs}"/>
  <xsl:value-of select="$lf"/>
</xsl:template>
 
</xsl:stylesheet>
Mit dem Beispiel
Code:
<calibrationDataFile>
<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">
   <column name="generalJ" scenario="JahrGeneral">2011-03-31</column>
   <column name="note11_q1" scenario="Vorjahr1Notes">2</column>
   <column name="note11_q1" scenario="JahrNotes">1</column>
</account>
</calibrationDataFile>
gibt Saxon 9.5 dann
Code:
generalJ|JahrGeneral;note11_q1|Vorjahr1Notes;note11_q1|JahrNotes
2011-03-31;;
;2;
;;1
aus.
 
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.
 
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.
 
Zurück