Verschachtelte XML -> Spreadsheet

Hallo zusammen,

ich habe eine BMEcat-XML-Datei, die ich in Excel speichern möchte. Ich brauche dafür aber etwas Unterstützung.

Für jedes Produkt in der Datei sind folgende Daten hinterlegt:

Struktur
<PRODUCT>
			<SUPPLIER_PID>12345</SUPPLIER_PID>
			<PRODUCT_DETAILS>
				<DESCRIPTION_SHORT>Kurze Beschreibung</DESCRIPTION_SHORT>
				<INTERNATIONAL_PID type="EAN">54321</INTERNATIONAL_PID>
				<ERP_GROUP_BUYER>2158</ERP_GROUP_BUYER>
			</PRODUCT_DETAILS>
			<PRODUCT_FEATURES>
				<REFERENCE_FEATURE_SYSTEM_NAME>Lieferant</REFERENCE_FEATURE_SYSTEM_NAME>
				<REFERENCE_FEATURE_GROUP_ID>2158</REFERENCE_FEATURE_GROUP_ID>
			</PRODUCT_FEATURES>
			<PRODUCT_FEATURES>
				<REFERENCE_FEATURE_SYSTEM_NAME>AllgemeineEigenschaften</REFERENCE_FEATURE_SYSTEM_NAME>
				<FEATURE>
					<FT_IDREF>1</FT_IDREF>
					<FVALUE>2500</FVALUE>
				</FEATURE>
				<FEATURE>
					<FT_IDREF>2</FT_IDREF>
					<FVALUE>2500</FVALUE>
				</FEATURE>
			</PRODUCT_FEATURES>
			<PRODUCT_FEATURES>
				<REFERENCE_FEATURE_SYSTEM_NAME>ZusatzstoffeUndAllergene</REFERENCE_FEATURE_SYSTEM_NAME>
				<FEATURE>
					<FT_IDREF>32</FT_IDREF>
					<FVALUE>true</FVALUE>
				</FEATURE>
				<FEATURE>
					<FT_IDREF>36</FT_IDREF>
					<FVALUE>true</FVALUE>
				</FEATURE>
				<FEATURE>
					<FT_IDREF>29</FT_IDREF>
					<FVALUE>true</FVALUE>
				</FEATURE>
			</PRODUCT_FEATURES>
			<PRODUCT_FEATURES>
				<REFERENCE_FEATURE_SYSTEM_NAME>Naehrstoffe</REFERENCE_FEATURE_SYSTEM_NAME>
				<FEATURE>
					<FT_IDREF>6</FT_IDREF>
					<FVALUE>2.9</FVALUE>
					<FUNIT>G</FUNIT>
				</FEATURE>
				<FEATURE>
					<FT_IDREF>3</FT_IDREF>
					<FVALUE>728</FVALUE>
					<FUNIT>KJ</FUNIT>
				</FEATURE>
				<FEATURE>
					<FT_IDREF>2</FT_IDREF>
					<FVALUE>175</FVALUE>
					<FUNIT>KK</FUNIT>
				</FEATURE>
			</PRODUCT_FEATURES>
			<PRODUCT_ORDER_DETAILS>
				<ORDER_UNIT>CT</ORDER_UNIT>
				<CONTENT_UNIT>C62</CONTENT_UNIT>
				<NO_CU_PER_OU>4</NO_CU_PER_OU>
				<QUANTITY_MIN>1.000</QUANTITY_MIN>
				<QUANTITY_INTERVAL>1.000</QUANTITY_INTERVAL>
			</PRODUCT_ORDER_DETAILS>
			<PRODUCT_PRICE_DETAILS>
				<PRODUCT_PRICE price_type="net_customer">
					<PRICE_AMOUNT>99.99</PRICE_AMOUNT>
					<PRICE_CURRENCY>EUR</PRICE_CURRENCY>
					<TAX>0.07</TAX>
					<PRICE_BASE>
						<PRICE_UNIT>CT</PRICE_UNIT>
					</PRICE_BASE>
				</PRODUCT_PRICE>
			</PRODUCT_PRICE_DETAILS>
			<USER_DEFINED_EXTENSIONS>
				<UDXZutaten>XXX.</UDXZutaten>
				<UDXProduktbeschreibung>YYY</UDXProduktbeschreibung>
				<UDXZubereitung>ZZZ</UDXZubereitung>
			</USER_DEFINED_EXTENSIONS>

		</PRODUCT>

Das Zielergebnis sollte so aussehen:

SKU code Supplier Description Price Case Pack Unit Measure Minium Order Quantity Zusatzstoff 1 Zusatzstoff 2 Nährstoff 1 Nährstoff 2
[SUPPLIER_PID] [Description Short] [PRICE_AMOUNT] 1 [NO_CU_PER_OU] [FVALUE]* [CONTENT_UNIT] QUANTITY_MIN [FVALUE]** [FVALUE]** [FVALUE] & [FUNIT]*** [FVALUE] & [FUNIT]***
12345 Kurze Beschreibung 99,99 1 4 2500 C62 1 true true 2.9 G 728 KJ

*[FVALUE]: aus

Allgemeine Eigenschaften
<REFERENCE_FEATURE_SYSTEM_NAME>AllgemeineEigenschaften</REFERENCE_FEATURE_SYSTEM_NAME>
				<FEATURE>
					<FT_IDREF>1</FT_IDREF>
					<FVALUE>2500</FVALUE>

**[FVALUE] aus

Zusatzstoffe
<REFERENCE_FEATURE_SYSTEM_NAME>ZusatzstoffeUndAllergene</REFERENCE_FEATURE_SYSTEM_NAME>
				<FEATURE>
					<FT_IDREF>32</FT_IDREF>
					<FVALUE>true</FVALUE>

***[FVALUE] & [FUNIT] aus

Naehrstoffe
<REFERENCE_FEATURE_SYSTEM_NAME>Naehrstoffe</REFERENCE_FEATURE_SYSTEM_NAME>
				<FEATURE>
					<FT_IDREF>6</FT_IDREF>
					<FVALUE>2.9</FVALUE>
					<FUNIT>G</FUNIT>

Die erste Zeile in der Tabelle zeigt die Quelle der Daten (wird im echten Export natürlich nicht drin sein). Die zweite Zeile zeigt die eigentlichen Werte von unserem Beispiel.

Meine Fragen:

  1. Gibt es einen Weg, immer auf die gleiche FVALUE zu verweisen (also immer auf die Value für FT_IDREF = 1 im ersten Fall, auf die Value für FT_IDREF = 32 im zweiten Fall usw)? Wenn ich es richtig verstehe, listet der Mapper alle FVALUEs mit einer aufsteigenden Zahl auf:
    grafik
    Nicht jedes Produkt hat aber die gleiche Anzahl an Features, deswegen bekomme ich dann gemischte Ergebnisse:
    grafik

Also anstatt dass ich immer z.B. das Nettogewicht zu bekommen, habe ich manchmal Nettogewicht, dann Allergeneninfo, dann Fettgehalt auf 100g.

  1. Kann ich irgendwie anstatt Zusatzstoff 1 usw. in der Spaltenüberschrift den FT_IDREF haben, bzw. der Wert, der ihm gegenüber steht? Z.B. ID = 32 = Fettgehalt → Spaltenüberschrift „Fettgehalt“.

  2. Kann ich die Drop-Down-Felder im Mapper irgendwie breiter machen? Oder das, was man da sieht, bereits im XMLReaderVisual bereits irgendwie abkürzen? Wie man vom Screenshot oben sieht, sind die Namen der einzelnen Kategorien ziemlich lang, weswegen ich den Regler ständig hin und her schieben muss. Das ist bei längeren Listen echt mühselig. Diese Frage hat die niedrigste Prio.

Ich freue mich auf euren Input. Sagt gerne Bescheid, wenn etwas unklar ist oder ihr zusätzliche Infos braucht.

Viele Grüße
Maya

Hallo Maya,

ich denke, dass du für deinen Anwendungsfall mit dem XMLReaderVisual nicht (oder nur über viele Umwege) zum Ziel kommen wirst. Für komplexere XMLs wirst du vermutlich den XMLReader Step benötigen. Im XMLReader Step kann man ein eigene templates in der Skriptsprache Freemarker hinterlegen und damit die XML Datei sehr individuell parsen bzw. ein Spreadsheet erstellen. Leider ist das nicht ganz so einfach wie im XMLReaderVisual.

Im Handbuch findest du unter

einige Informationen dazu.

Für dein Beispiel könnte das parsing template in etwa so aussehen (nicht vollständig, aber ich habe versucht für die unterschiedliche Fälle ein Beispiel einzubauen):

<#assign row = target.addRow()>
<#list xml['PRODUCT'] as product>
  <#assign row = target.addRow()>
  
<#-- Einzelne Werte in Spalte ausgeben -->
  ${row.addCol("SUPPLIER_PID", product['SUPPLIER_PID']!)}
  ${row.addCol("DESCRIPTION_SHORT", product['PRODUCT_DETAILS']['DESCRIPTION_SHORT']!)}
  
<#-- Hier wird der Preis mit dem Attribut = "net_customer" ausgegeben -->
  <#assign priceAmount = "">
  <#list product['PRODUCT_PRICE_DETAILS']['PRODUCT_PRICE'] as price>
    <#if attr( 'price_type', price) == "net_customer">
      <#assign priceAmount = price['PRICE_AMOUNT']! >
    </#if>
 </#list>
  ${row.addCol("PRICE_AMOUNT",  priceAmount!)}

<#-- mehrere Werte eines XML Elements ausgeben -->
${addColumns(row,product['PRODUCT_ORDER_DETAILS'])}

<#-- Hier werden alle Features als extra Spalte ausgeben (titel = "FEATURE_+ FT_IDREF") -->
  <#list product['PRODUCT_FEATURES']['FEATURE'] as feature>  
      <#if feature['FUNIT'][0]! != ""> <#-- Wenn Unit vorhanden, dann ausgeben -->
         ${row.addCol("FEATURE_" + feature['FT_IDREF'], ( feature['FVALUE'] + " " + feature['FUNIT'])  )}
      <#else>
           ${row.addCol("FEATURE_" + feature['FT_IDREF'], feature['FVALUE']!  )}
      </#if>
 </#list>
 
</#list>

Das Ergebnis sieht dann folgendermaßen aus:


Im Mapper Step kannst du dann noch die Titel ändern oder andere Anpassungen vornehmen.
Ich hoffe das hilft dir etwas weiter. Falls du Fragen hast, kannst du dich gern bei uns melden.

Viele Grüße
Torsten

Hallo Thorsten,

vielen Dank für die ausführliche Antwort. Ich versuche gerade den XMLReader Step zu verwenden, aber hab ein Problem mit der Konfiguration, sodass ich keinen Step-Vorschau bekomme.
Ich lade die Daten von einem FTP server via FTPdownload herunter. Wenn ich versuche, den Output von diesem Step mit dem XMLReader zu verbinden sieht es so aus:

Wenn ich dein Code reinkopiere und auf Vorschau gehe, sehe ich aber das:

Was mache ich hier falsch? Ich hab davor den gleichen FTPdownload Step für XMLreaderVisual genutzt und da hatte ich keine Probleme.

Hallo Maya,

das Beispiel war auf dein XML Beispiel (PRODUCT) von oben bezogen. Ich vermute mal, dass du eine BMECAT Datei vom FTP herunterlädst, die folgende Struktur hat:

image

Damit du ein Ergebnis bekommst, musst du vermutlich noch des XML Element in der <#list ... > Anweisung des Parsing Codes anpassen:

xml['PRODUCT']
zu
xml['BMECAT']['T_NEW_CATELOG']['PRODUCT'] anpassen

image

Eventuell müssen auch noch die namespaces angegeben werden. Das hängt aber von der XML Datei ab. Informationen dazu findest du unter:

Das parsing im XMLReader Step bei komplexen XML-Dateien ist leider nicht ganz so einfach. Falls du nicht weiter kommst, kannst du dich gern bei uns melden.

Viele Grüße
Torsten

Vielen Dank!

Ich konnte die XML-Datei erfolgreich auslesen und bereits mehrere Sachen ausprobiert.
Momentan habe ich ein Problem mit den Features und konkret damit, dass ich die gleiche ID in mehrere Referenzsysteme habe. Das heißt, dass eine Benennung „Feature“ + FT_ID nicht ausreichend ist. Mein Ziel sind folgende Spaltenüberschriften: Referenzsystem + ID.
Ich hab u.a. diese Scripts ausprobiert:

Feature + direkter Verweis
  <#list product['PRODUCT_FEATURES']['FEATURE'] as feature>  
      <#if feature['FUNIT'][0]! != "">
         ${row.addCol(product['PRODUCT_FEATURES']['REFERENCE_FEATURE_SYSTEM_NAME'] + feature['FT_IDREF'], ( feature['FVALUE'] + " " + feature['FUNIT'])  )}
      <#else>
           ${row.addCol(product['PRODUCT_FEATURES']['REFERENCE_FEATURE_SYSTEM_NAME'] + feature['FT_IDREF'], feature['FVALUE']!  )}
      </#if>
 </#list>

Feature + system
  <#list product['PRODUCT_FEATURES']['FEATURE'] as feature>  
    <#list product['PRODUCT_FEATURES']['REFERENCE_FEATURE_SYSTEM_NAME'] as system>  
      <#if feature['FUNIT'][0]! != "">
         ${row.addCol(system + feature['FT_IDREF'], ( feature['FVALUE'] + " " + feature['FUNIT'])  )}
      <#else>
           ${row.addCol(system + feature['FT_IDREF'], feature['FVALUE']!  )}
      </#if>
 </#list>
 </#list>

und sogar dieses hier (mit = und == nach system), obwohl diese Lösung sehr unflexibel ist:

Feature + system + if
 <#list product['PRODUCT_FEATURES']['FEATURE'] as feature>  
    <#list product['PRODUCT_FEATURES']['REFERENCE_FEATURE_SYSTEM_NAME'] as system>  
          <#if system = "ZusatzstoffeUndAllergene"> 
                    ${row.addCol("Allergene_" + feature['FT_IDREF'], feature['FVALUE']  )}
           <#elseif system = "AllgemeineEigenschaften">      
                    ${row.addCol("Allgemein_" + feature['FT_IDREF'], feature['FVALUE']  )}       
           <#elseif system = "Naehrstoffe">
                    ${row.addCol("Naehrstoffe_" + feature['FT_IDREF'], ( feature['FVALUE'] + " " + feature['FUNIT'])  )}        
            </#if>
                              
    </#list>                  

 </#list>

Bei allen Scripts bekomme ich Fehlermeldungen. Ich vermute, dass ich etwas grundsätzliches übersehe, aber ich komme alleine nicht auf die Lösung. Hast du Ideen?

Und kannst du mir sagen, wo ich nachlesen kann was das ! am Ende des Codes bedeutet bzw. wann es genutzt wird? Ich hab die Links von dir bereits gelesen, aber nichts zum Thema gesehen.


Und ein kleines Offtopic / Feedback: man sieht hier die Ergebnisse in Beispiele 2 und 3 nicht.
Und hier ist die Tabelle mit den Parametern von AddColumns sehr schwer zu lesen, da nicht alle Spalten gleichzeitig gezeigt werden.

Hallo @Entegra,

versuch es mal mit geschachtelten #list-Directiven. In der äußeren Schleife iteriest du über die <PRODUCT_FEATURES> und in der inneren über alle darin enthaltenen . So hast du immer Zugriff auf den zugehörigen <REFERENCE_FEATURE_SYSTEM_NAME>.

Das Codebeispiel ist ungetestet, hoffe es enthält keine Fehler.

Code
<#list product['PRODUCT_FEATURES'] as product_features>
    <#-- Speicher Systemname in eine Variable für einfacheren Zugriff -->
    <#assign system = product_features["REFERENCE_FEATURE_SYSTEM_NAME"]>
    <#list product_features['FEATURE'] as feature>
        ${row.addCol(system + '_' + feature['FT_IDREF'], feature['FVALUE']  )}
    </#list>
</#list>

Und das Aufrufezeichen ist der Default-Operator. In den meisten Fällen ist der irrelevant und kann einfach weggelassen werden. Ich erinnere mich nicht daran, den in den letzten 4 Jahren mit Synesty jemals tatsächlich verwendet habe.

Gruß
Gustav

3 Likes

Vielen Dank! Dein Vorschlag hat auf Anhieb funktioniert! :star_struck:

Neuer Tag und neue Fragen.
Ich habe mittlerweile mehrere BMEcats, jede mit ihren eigenen Besonderheiten.

Die Datei, mit der ich aktuell ein Problem habe, hat folgende Struktur:

Struktur
<BMECAT>
    <HEADER>
    <T_NEW_CATALOG>
         <CATALOG_GROUP_SYSTEM>
                 <CATALOG_STRUCTURE type="node">
                      <GROUP_ID>6</GROUP_ID>
                      <GROUP_NAME>GEMÜSE GESCHNITTEN</GROUP_NAME>
                      <GROUP_DESCRIPTION>GEMÜSE GESCHNITTEN</GROUP_DESCRIPTION>
                      <PARENT_ID>5</PARENT_ID>
                  </CATALOG_STRUCTURE>
                 <CATALOG_STRUCTURE type="leaf">
                      <GROUP_ID>7</GROUP_ID>
                      <GROUP_NAME>SPARGEL GESCHÄLT</GROUP_NAME>
                      <GROUP_DESCRIPTION>SPARGEL GESCHÄLT</GROUP_DESCRIPTION>
                      <PARENT_ID>6</PARENT_ID>
                  </CATALOG_STRUCTURE>
         </CATALOG_GROUP_SYSTEM>

          <ARTICLE>
              <SUPPLIER_AID>123456</SUPPLIER_AID>
          </ARTICLE>

          <ARTICLE_TO_CATALOGGROUP_MAP>
              <ART_ID>123456</ART_ID>
              <CATALOG_GROUP_ID>6</CATALOG_GROUP_ID>
          </ARTICLE_TO_CATALOGGROUP_MAP>
          <ARTICLE_TO_CATALOGGROUP_MAP>
               <ART_ID>987654</ART_ID>
               <CATALOG_GROUP_ID>7</CATALOG_GROUP_ID>
          </ARTICLE_TO_CATALOGGROUP_MAP>

Im Idealfall brauche ich einen Script, der die Informationen im Catalog group system und Article to catalog group map abgleicht und die korrekte Information im Output ausfüllt:

SUPPLIER_AID Category
123456 GEMÜSE GESCHNITTEN
987654 SPARGEL GESCHÄLT

Die ID findet man in SUPPLIER_AID und ART_ID.
Die Nummer der CATALOG_GROUP_ID entspricht der GROUP_ID in der CATALOG_STRUCTURE.

Falls das nicht möglich ist, wäre ich froh, wenn ich zumindest an einem Spreadsheet mit dem Mapping (die Daten im Abschnitt ARTICLE_TO_CATALOGGROUP_MAP) kommen kann, um mich danach über mapping sets schlau machen zu können.
Also ungefähr sowas:

SUPPLIER_AID Category
123456 6
987654 7

Am „Idealfall“ hab ich nicht mal probiert. Für letzteres habe ich bis jetzt Variationen von diesem Code probiert:

<#ftl ns_prefixes={"D":"http://www.bmecat.org/bmecat/1.2/bmecat_new_catalog"}>
<#assign row = target.addRow()>

<#list xml['BMECAT']['T_NEW_CATALOG'] as cat>
<#list cat["ARTICLE_TO_CATALOGGROUP_MAP"] as mapping>
      ${row.addCol("Supplier_AID", mapping["ART_ID"])}
      ${row.addCol("Group ID", mapping["CATALOG_GROUP_ID"])}
</#list >

</#list >

aber er schlägt fehl. Wie ich jetzt, während ich diesen Post schreibe, festgestellt habe, liegt es vermutlich daran, dass ich für jeden Artikel einen einzelnen ARTICLE_TO_CATALOGGROUP_MAP Abschnitt habe und somit mehrere Spalten mit den Namen Supplier_AID und Group ID bekommen würde.

Habt ihr Lösungsvorschläge, wie ich an den Daten komme?

Hallo @Entegra,

ich würde dir erstmal den einfachen Weg mit zwei getrennten XMLReader Steps und einem Mapping Set empfehlen. Das sollte einfacher zu erstellen und auch später leichter nachvollziehbar sein, als ein komplettes Skript mit „Mapping“ der Kategorien.

Im 1. XMLReader erstellst du dir ein Spreadsheet mit den GROUP_IDs und Namen. Das sollte in etwa so aussehen:

<#ftl ns_prefixes={"D":"http://www.bmecat.org/bmecat/1.2/bmecat_new_catalog"}>
<#assign row = target.addRow()>

<#list xml['BMECAT']['T_NEW_CATALOG']["CATALOG_GROUP_SYSTEM"]["CATALOG_STRUCTURE"] as group>
   <#assign row = target.addRow()>
   ${row.addCol("Group ID", group["GROUP_ID"])}
   ${row.addCol("Group name", group["GROUP_NAME"])}
</#list >

Im 2. XMLReader erstellst du dir dann die Zuweisung der Artikel IDs und Group IDs. Das parsingTemplate dazu sollte ungefähr so aussehen:

<#ftl ns_prefixes={"D":"http://www.bmecat.org/bmecat/1.2/bmecat_new_catalog"}>
<#assign row = target.addRow()>

<#list xml['BMECAT']['T_NEW_CATALOG']["ARTICLE_TO_CATALOGGROUP_MAP"]  as mapping>
   <#assign row = target.addRow()>
   ${row.addCol("Supplier_AID", mapping["ART_ID"])}
   ${row.addCol("Group ID", mapping["CATALOG_GROUP_ID"])}
</#list >

Aus dem 1. Spreadsheet kannst du dir ein MappingSet ( AddUpdateMappingset Step) oder ein KeyValueSpreadsheet Step erstellen und damit die GROUP_IDs → Namen mappen. Das kannst du in einem Mapper Step, der als input das Spreadsheet des 2. XMLReaders verwendet, machen.

VG Torsten