XML zu Spreadsheet mappen mit mehreren gleichen Nodes

Hallo,


wir müssen für unser Projekt eine XML Struktur zu einem Spreadsheet mappen. Wir haben bereits die XML über den XMLReaderVisual reinbekommen und sind jetzt im Mapper. Unsere XML hat ca. folgende Struktur


<product>

<productidentifier>

<id_type>01</id_type>

<id_value>123ABC</id_type>

</productidentifier>

<productidentifier>

<id_type>02</id_type>

<id_value>456DEF</id_type>

</productidentifier>

</product>



Ich möchte nun eigentlich sagen: Wenn id_type='02' dann nehme den Wert vom id_value der gleichen parentnode. Allerdings werden hier im XMLReaderVisual daraus vier Spalten, wobei der zweite productidentifier dann den Suffix _01 hat (also id_type_01 und id_value_01). Damit geht so eine IF-Bedingung nicht mehr, da nicht mehrere Spalten durchsucht werden können.


Gibt es hier eine Lösung bzw. einen anderen Ansatz, mit dem das machbar wäre?

Da stößt der VisualXMLReader vermutlich an seine Grenzen.


Man könnte mal mit dem "normalen" XMLReader probieren.

Hier mal noch ein umschließendes <products> mit drum herum, damit es eine Liste von <product> wird (werden kann):


Quelle (mit Hilfe des Steps StringToFile simuliert):


<products>
<product>
 <productidentifier>
   <id_type>01</id_type>
   <id_value>123ABC</id_value>
 </productidentifier>
 <productidentifier>
   <id_type>02</id_type>
   <id_value>456DEF</id_value>
 </productidentifier>
</product>
</products>


ParsingTemplate:


<#assign row = target.addRow()>
<#list xml["products"]["product"]["productidentifier"] as p>
  <#assign row = target.addRow()>
  ${addColumns(row, p)}
<#-- or Example 2: Exclude certain fields: ${addColumns(row, p, '', {'columns':['fields', 'col2'], 'mode':'exclude'})} -->
<#-- or Example 3: Add a sub-object and prefix all columns: ${addColumns(row, p['fields'],'field_')} -->
</#list>


Ergebnis:




Mehr zu der addColumns() Funktion gibt es hier.


Hallo,


erst einmal vielen Dank für die schnelle Antwort. Das Problem was ich sehe ist, dass wir mehrere Produkte haben und in allen Produkten die o.g. Struktur 10+ mal vorkommt. Grob als Beispiel:

<products>
<product>
 <productidentifier>
   <id_type>01</id_type>
   <id_value>123ABC</id_value>
 </productidentifier>
 <productidentifier>
   <id_type>02</id_type>
   <id_value>456DEF</id_value>
 </productidentifier>
 <contributor>
   <ContributorRole>A01</ContributorRole>
   <PersonName>Max Mustermann</PersonName>
 </contributor>
 <contributor>
   <ContributorRole>B02</ContributorRole>
   <PersonName>Madeleine Musterfrau</PersonName>
 </contributor> 
 <contributor>
   <ContributorRole>B02</ContributorRole>
   <PersonName>Bart Simpson</PersonName>
 </contributor>
</product>
</products>

Im oben genannten Beispiel würde ich am Ende in der csv pro <product> eine Zeile haben mit den Spalten (unter anderem) SKU-Nr. und Autor. Sku-Nr. nimmt den Wert id_vaue wenn id_type '01' und Autor den Wert PersonName wenn ContributorRole 'A01'. Daneben kommen eben ein Dutzend weiterer Zeilen mit ähnlichen Konditionen / Konstrukten

Wenn ich jetzt nach der beschriebenen Methode im XMLReader vorgehen würde, dann würde ich ja pro <product> mehrere Zeilen bekommen, die ggf. keinen Bezug zueinander haben (zwei Zeilen für <productidentifier> Elemente und drei für <contributor>. Mir ist dann nicht klar, wie ich danach im Mapper daraus eine Zeile machen würde..? Gibt es da einen Weg im Mapper, wenn man vorher im XMLReader eine Hilfsspalte einfügt (und die z.B. für jedes <product> hochzählen lässt)? Oder muss man hier im XMLReader die ganze Logik haben und das über IFs zu lösen?

Ok, das ging nicht draus hervor. Eine Tabelle als Beispiel des gewünschten Ergebnisses zu den Quelldaten ist oft hilfreich.


Vermutlich muss man bei dieser dynamischen Struktur den komplett "manuellen" Weg gehen, da die "Magic" (addColumns oder VisualXMLReader) solche Strukturen nicht erkennt.


Quelldaten:


<products>
<product>
 <productidentifier>
   <id_type>01</id_type>
   <id_value>123ABC</id_value>
 </productidentifier>
 <productidentifier>
   <id_type>02</id_type>
   <id_value>456DEF</id_value>
 </productidentifier>
 <contributor>
   <ContributorRole>A01</ContributorRole>
   <PersonName>Max Mustermann</PersonName>
 </contributor>
 <contributor>
   <ContributorRole>B02</ContributorRole>
   <PersonName>Madeleine Musterfrau</PersonName>
 </contributor> 
 <contributor>
   <ContributorRole>B02</ContributorRole>
   <PersonName>Bart Simpson</PersonName>
 </contributor>
</product>

<product>
 <productidentifier>
   <id_type>01a</id_type>
   <id_value>123ABCa</id_value>
 </productidentifier>
 <productidentifier>
   <id_type>02a</id_type>
   <id_value>456DEFa</id_value>
 </productidentifier>
 <contributor>
   <ContributorRole>A01</ContributorRole>
   <PersonName>Max Mustermann</PersonName>
 </contributor>
 <contributor>
   <ContributorRole>B02</ContributorRole>
   <PersonName>Madeleine Musterfrau</PersonName>
 </contributor> 
 <contributor>
   <ContributorRole>B02</ContributorRole>
   <PersonName>Bart Simpson</PersonName>
 </contributor>
</product>

</products>


ParsingTemplate:


<#assign row = target.addRow()>
<#list xml["products"]["product"] as p>
  <#assign row = target.addRow()>

  ${row.addCol("id1",p["productidentifier"][0]["id_type"])}
  ${row.addCol("id1val",p["productidentifier"][0]["id_value"])}
  ${row.addCol("id2",p["productidentifier"][1]["id_type"])}
  ${row.addCol("id2val",p["productidentifier"][1]["id_type"])}

  <#list p["contributor"] as c>
    ${row.addCol("contrib"+c?index , c["ContributorRole"])}
    ${row.addCol("contrib"+c?index , c["PersonName"])}
  </#list>
</#list>



Das Beispiel zeigt 2 verschiedene Ansätze:

  • die productidentifier sind händisch per Index in eckigen Klammern angegeben. Vorteil: Man sieht genau was passiert. Nachteil: muss aber wissen wie viele identifier es geben kann.
  • die Contributors sind dynamisch mit Hilfe einer Schleife (Freemarker <#list>) erzeugt und auch über einen Zähler eindeutig gemacht (?index). Vorteil: es ist egal wie viele Contributors es gibt. Nachteil: Etwas schwerer zu verstehen auf den ersten Blick

Man kann beide Ansätze kombinieren.


Ergebnis:



Die vorher erwähnte addColumns() Funktion passt hier leider nicht.

Hallo,


danke nochmal für die detaillierten Erklärungen, das hat wieder sehr weitergeholfen. Der Punkt für uns war, den ich nicht sehr gut rübergebracht hatte, dass wir nur eine einzige ID bzw. Contributor Spalte haben wollen, die sich den Wert einer node anhand des Wertes einer anderen node nimmt. Ich glaube ich habe dafür eine (nicht sehr elegante) Lösung gefunden und würde mich über ein Feedback (bzw. gerne Verbesserungsvorschlag) freuen.


Vereinfachte Ausgangs-XML:


<Products>
  <Product>
    <ProductIdentifier>
      <ProductIDType>03</ProductIDType>
      <IDValue>ABC</IDValue>
    </ProductIdentifier>
    <ProductIdentifier>
      <ProductIDType>15</ProductIDType>
      <IDValue>DEF</IDValue>
    </ProductIdentifier>
  </Product>
  <Product>
    <ProductIdentifier>
      <ProductIDType>03</ProductIDType>
      <IDValue>123</IDValue>
    </ProductIdentifier>
    <ProductIdentifier>
      <ProductIDType>15</ProductIDType>
      <IDValue>456</IDValue>
    </ProductIdentifier>
  </Product>
  </Products>

Parsing Template:


<#assign row = target.addRow()>
<#list xml["Products"]["Product"] as p>
  <#assign row = target.addRow()>
  
    <#list p["ProductIdentifier"] as pi>
	  <#if pi["ProductIDType"] == "03">
    ${row.addCol("SKU" , pi["IDValue"])}
	</#if>
	</#list>
  </#list>

Resultat:

image



In anderen Worten, eine einzige Spalte mit den IDValues, wo ProductIDType "03" ist. Das würde dann vermutlich auch den Bedarf eines Mappers erübrigen?


Freue mich wie gesagt über Feedback!

Also die IF-Bedinungung innerhalb der Liste ist an dieser Stelle schon der korrekte Weg, wenn man sich wirklich nur einen einzigen identifier anhand der ProductIDType herauspicken möchte. Die XML-Struktur erfordert das.

Evtl. könnte man das noch mit dem ?filter Ausdruck anders schreiben (z.B. ${row.addCol("SKU" , p["ProductIdentifier"]?filter(pi -> pi["ProductIDType"] == "03")[0]) ), aber an der Stelle ist <#list> und <#if> vielleicht etwas lesbarer. Am Ende Geschmackssache.


Zur Frage, ob der Mapper notwendig ist?

Also wenn aus dem XMLReader alles schon so rauskommt, wie man es braucht, dann kann der Mapper entfallen.

Man könnte aber auch im XMLReader so wenig wie möglich Logik (if-else) reinpacken (d.h. einfach alle IDs ausgeben) und dann hinterher per Mapper, die Spalten anpassen und entfernen, was man nicht braucht.

Am Ende Geschmackssache. Ist die Frage, wer das später pflegen muss und ob man evtl. mal doch noch die anderen Identifier braucht, z.B. für einen anderen späteren Step. Mit dem Mapper geht das vermutlich einfacher von der Hand als in diesem Parsing-Script rumzuwerkeln. Wir würden an dieser Stelle aus Wartbarkeitsaspekten eher den Mapperansatz empfehlen. Auf der anderen Seite ist ihr Ansatz natürlich sparsamer, was die Menge der ausgegebenen Daten angeht.






Hallo nochmal,


auch jetzt nochmal großen Dank für die Antwort! Dann weiß ich erstmal wie wir weitermachen können.


Gegebenenfalls stolpern wir im Laufe der nächsten Tage/Wochen noch einmal über ein Hindernis, dann würde ich einen weiteren Thread im Forum aufmachen. :)