Zum Inhalt

Variablen-Scope in verschachtelten Generates

Wenn Du <variable>-Deklarationen von einem äußeren <generate> in ein verschachteltes <generate> verschiebst, sieht das XML oft noch fast gleich aus, aber der Auswertungskontext ändert sich. Diese Seite erklärt, was die Runtime tatsächlich macht, warum Ausdrücke wie voszData.data[index] plötzlich fehlschlagen oder den falschen Frame lesen und welche Präfixe stabil sind.

Kurzfassung

  • Jede Iteration eines verschachtelten <generate> läuft in einem eigenen Kontext-Frame.
  • In verschachtelten Frames ist this.* der stabile Weg, um Variablen aus dem aktuellen Generate zu referenzieren.

Wie DATAMIMIC Scope-Frames aufbaut

Jede Iteration eines <generate> erzeugt einen eigenen Scope-Frame mit:

  • current_variables: Werte aus <variable>
  • current_product: Werte, die bereits durch <key> oder verschachtelte Kinder geschrieben wurden
  • current_name: der aktuelle <generate name="...">

Die wichtigste Folge davon ist:

  • Das erste <generate> direkt unter <setup> wird in den Top-Level-Namespace eingefaltet.
  • Ein verschachteltes <generate> wird unter seinem eigenen name eingefügt.
  • Der aktuelle Frame ist immer über this erreichbar.
  • In verschachtelten Frames ist die zusammengeführte direkte Parent-Sicht über this.parent erreichbar.

Die Regel, über die man leicht stolpert

Innerhalb eines verschachtelten <generate> bedeutet ein unqualifizierter Name nicht zuverlässig „die Variable, die genau hier deklariert wurde“.

Häufig wird stattdessen gegen den eingefalteten äußeren Frame aufgelöst.

Deshalb kann das hier nach dem Verschieben von Variablen nach innen brechen:

1
<key name="value" script="voszData.data[index]"/>

Wenn voszData und index vorher im äußeren Frame lagen, liest dieser Ausdruck die äußeren Werte. Nach dem Verschieben in den inneren Frame kann derselbe unqualifizierte Ausdruck weiterhin auf den äußeren Frame zeigen statt auf den neuen lokalen.

Verwende stattdessen:

1
<key name="value" script="this.voszData.data[this.index]"/>

Schnelle Entscheidungshilfe

Verwende diese Kurzfassung beim Schreiben oder Prüfen von Ausdrücken:

Du willst lesen... Verwende...
Eine Variable aus dem aktuellen verschachtelten <generate> this.varName
Eine Variable aus dem direkten Parent-Generate this.parent.varName
Eine Variable über einen expliziten benannten Pfad child.grand_child.varName
Eine Variable im ersten Generate unter <setup> meistens varName

Tip

Wenn Du unsicher bist, nimm bevorzugt this.*. Das ist die klarste und sicherste Wahl für lokale verschachtelte Variablen.

Referenztabelle

Wo der Wert deklariert ist Bevorzugte Referenz Hinweis
Aktuelles verschachteltes <generate> this.varName Stabilste Form
Aktuelles verschachteltes <generate> <aktuellerGenerateName>.varName Funktioniert für verschachtelte Generates innerhalb eines anderen Generates
Direktes Parent-<generate> this.parent.varName Explizit und sicher
Eingefalteter äußerer Frame varName Kann funktionieren, ist aber leicht missverständlich
Bereits aufgebauter verschachtelter Pfad child.grand_child.varName Nützlich für expliziten Baumzugriff
Erstes <generate> direkt unter <setup> varName oder root.varName Meist nicht als root.generateName.varName verfügbar

Beispiel 1: Mem-Variablen in das innere Generate verschieben

Funktioniert, wenn die Variablen im äußeren Frame bleiben

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<setup>
    <memstore id="mem"/>

    <generate name="batch" count="1">
        <variable name="voszData" source="mem" type="vosz_seed" cyclic="True"/>
        <variable name="index" generator="IncrementGenerator"/>

        <generate name="datFileCreation" count="1">
            <key name="selected" script="voszData.data[index]"/>
        </generate>
    </generate>
</setup>

Hier wird voszData.data[index] gegen den äußeren Frame aufgelöst und funktioniert deshalb.

Bricht oder liest den falschen Frame nach dem Verschieben nach innen

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<setup>
    <memstore id="mem"/>

    <generate name="batch" count="1">
        <variable name="voszData" source="mem" type="vosz_seed" cyclic="True"/>
        <variable name="index" generator="IncrementGenerator"/>

        <generate name="datFileCreation" count="1">
            <variable name="voszData" source="mem" type="vosz_seed" cyclic="True"/>
            <variable name="index" generator="IncrementGenerator"/>

            <!-- Riskant: wird weiterhin gegen den eingefalteten äußeren Frame aufgelöst -->
            <key name="selected_wrong" script="voszData.data[index]"/>

            <!-- Korrekt: expliziter aktueller Frame -->
            <key name="selected_this" script="this.voszData.data[this.index]"/>

            <!-- Ebenfalls gültig in einem verschachtelten Generate -->
            <key name="selected_named" script="datFileCreation.voszData.data[datFileCreation.index]"/>

            <!-- Expliziter Parent-Zugriff -->
            <key name="selected_parent" script="this.parent.voszData.data[this.parent.index]"/>
        </generate>
    </generate>
</setup>

Empfehlung

  • Verwende this.*, wenn die Variable im selben verschachtelten Generate deklariert ist.
  • Verwende this.parent.*, wenn Du bewusst den äußeren Frame lesen willst.
  • Lasse den Ausdruck nach dem Verschieben von Variablen zwischen Frames nicht unqualifiziert.

Beispiel 2: Cascade Generate und this.id

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<setup>
    <generate name="cascade_generate_test" count="1">
        <variable name="id" generator="IncrementGenerator"/>
        <key name="outer_id" script="id"/>

        <generate name="first_inner_generate" count="2">
            <variable name="id" generator="IncrementGenerator"/>
            <key name="first_inner_id" script="this.id"/>

            <generate name="second_inner_generate" count="2">
                <variable name="id" generator="IncrementGenerator"/>
                <key name="second_inner_id" script="this.id"/>

                <generate name="third_inner_generate" count="2">
                    <variable name="id" generator="IncrementGenerator"/>
                    <key name="third_inner_id" script="this.id"/>
                </generate>
            </generate>
        </generate>
    </generate>
</setup>

Warum this.id hier wichtig ist:

  • Das äußere id kann unqualifiziert bleiben, weil dieser Frame eingefaltet ist.
  • Innere id-Werte sollten mit this. qualifiziert werden, um Shadowing-Verwirrung zu vermeiden.
  • Wiederverwendete Namen wie id, index oder row sind nur dann sicher, wenn Du explizit bleibst.

Beispiel 3: Benannte Pfade über verschachtelte Generates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<setup>
    <generate name="parent" count="2">
        <variable name="count_global" constant="2"/>

        <generate name="child" count="{count_global}">
            <variable name="idx" generator="IncrementGenerator"/>
            <key name="child_id" script="child.idx"/>

            <generate name="grand_child" count="{child.idx}">
                <variable name="index" generator="IncrementGenerator"/>
                <key name="grand_child_id" script="child.grand_child.index"/>
                <key name="parent_idx" script="child.idx"/>
            </generate>
        </generate>
    </generate>
</setup>

Das zeigt die aktuelle Namespace-Struktur, nicht eine generische „jeden Ancestor per Namen“-Regel:

  • child.idx funktioniert, weil child der eingefaltete Parent-Frame ist.
  • child.grand_child.index funktioniert, weil grand_child unter diesem Frame verschachtelt ist.
  • this.index bleibt trotzdem die klarste Aussage für „die Variable aus dem aktuellen Generate“.

Beispiel 4: nestedKey-Pfadlogik ist dasselbe Prinzip

nestedKey-Pfadlogik folgt derselben Denkweise: Werte werden über den aktuell verfügbaren Baum adressiert, statt anzunehmen, dass automatisch „der nächste Name gewinnt“.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<setup>
    <generate name="customer" source="data/ids.ent.csv" count="6">
        <generate name="data" source="data/notification.ent.csv" count="4">
            <variable name="info" source="data/ids.ent.csv" cyclic="True"/>
            <variable name="state" source="data/state.ent.csv" cyclic="True"/>

            <nestedKey name="send_info" type="list" count="2">
                <variable name="contact_method" source="data/notification.ent.csv" distribution="ordered"/>

                <nestedKey name="contact" type="list" count="3">
                    <key name="email" script="data.send_info.contact_method.Email"/>
                    <key name="sms" script="data.send_info.contact_method.Sms"/>
                </nestedKey>
            </nestedKey>
        </generate>
    </generate>
</setup>

Konzeptionell:

  • data ist der benannte Generate-Frame.
  • send_info ist eine darunterliegende verschachtelte Struktur.
  • contact_method.Email wird über diesen expliziten Pfad aufgelöst.

Die gleiche Empfehlung gilt für verschachtelte Generates: Bevorzuge explizite Pfade, wenn ein Wert nicht offensichtlich lokal ist.

Beispiel 5: Gemeinsam genutzte Variablen außen, lokale Variablen innen

Hier wird das Scope-Modell nützlich statt nur fehleranfällig.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<setup>
    <memstore id="mem"/>

    <generate name="batch" count="1">
        <!-- Gemeinsam genutzte Daten für mehrere verschachtelte Generates -->
        <variable name="customer" source="mem" type="customer_seed" cyclic="True"/>

        <generate name="invoice" count="1">
            <!-- Lokaler Helfer nur für die Rechnungslogik -->
            <variable name="lineIndex" generator="IncrementGenerator"/>

            <key name="customer_id" script="customer.id"/>
            <key name="invoice_line" script="this.lineIndex"/>
        </generate>

        <generate name="deliveryNote" count="1">
            <!-- Lokaler Helfer nur für den Lieferschein -->
            <variable name="lineIndex" generator="IncrementGenerator"/>

            <key name="customer_id" script="customer.id"/>
            <key name="delivery_line" script="this.lineIndex"/>
        </generate>
    </generate>
</setup>

Warum das nützlich ist:

  • customer bleibt außen, weil beide verschachtelten Generates den Wert brauchen.
  • Jedes verschachtelte Generate kann trotzdem seinen eigenen lokalen lineIndex haben.
  • this.lineIndex macht sofort klar, dass der Wert lokal zum aktuellen verschachtelten Generate gehört.
  • Derselbe Hilfsname in zwei verschachtelten Generates bleibt lesbar, wenn Du ihn qualifizierst.

Warum dieses Modell flexibel ist

Dieses Scope-Modell gibt Dir zwei nützliche Muster:

  • Gemeinsam genutzte äußere Variablen: Deklariere Lookup-Daten, memstore-basierte Werte oder gemeinsame IDs einmal im äußeren Generate und verwende sie in mehreren inneren Generates wieder.
  • Lokale innere Variablen: Halte Hilfszähler, temporäre Selektoren oder Formatierungswerte in dem verschachtelten Generate, zu dem sie gehören.

Das Ergebnis ist ein explizites Modell:

  • Gemeinsame Werte bleiben gemeinsam.
  • Lokale Werte bleiben lokal.
  • Verschachtelte Pfade bleiben lesbar.
  • Shadowing bleibt beherrschbar, wenn Du this.* konsequent verwendest.

Best Practices

  • Bevorzuge this.* für Variablen, die im aktuellen verschachtelten Generate deklariert sind.
  • Bevorzuge this.parent.*, wenn Du absichtlich aus dem direkten Parent-Frame liest.
  • Halte gemeinsam genutzte memstore-basierte Variablen im äußeren Generate, wenn mehrere innere Generates sie brauchen.
  • Wenn Variablen nur für ein verschachteltes Generate lokal sind, halte sie lokal und verwende konsequent this.*.
  • Vermeide wiederverwendete Namen wie id, index, row oder data über mehrere Ebenen hinweg, außer Du qualifizierst sie immer.
  • Betrachte unqualifizierte Namen in verschachtelten Generates als riskant, auch wenn sie im Moment funktionieren.

Troubleshooting

Symptom Wahrscheinliche Ursache Fix
variable not found Der Wert existiert nur im aktuellen verschachtelten Frame, aber der Ausdruck wird woanders aufgelöst Verwende this.varName oder <aktuellerGenerateName>.varName
Falscher Wert, aber kein Fehler Der Ausdruck wurde gegen einen eingefalteten äußeren Frame aufgelöst Qualifiziere mit this.* oder this.parent.*
ambiguous variable oder verwirrendes Shadowing Derselbe Name existiert in mehreren Frames Benenne die Variable um oder qualifiziere den Frame explizit
Ein Pfad funktioniert auf einer Ebene, aber nicht auf einer anderen Die Namespace-Struktur hat sich durch die Verschachtelung geändert Verwende this.* für aktuelle Werte und explizite benannte Pfade für Baumzugriffe
root.someGenerate.var schlägt fehl Das erste Generate unter <setup> wird eingefaltet; dieser Generate-Name existiert dann unter root oft nicht Verwende root.var oder den aktuell sichtbaren benannten Pfad

Verwandte Dokumentation