Skip to content

Variable Scoping in Nested Generates

When you move <variable> declarations from an outer <generate> into a nested <generate>, the XML often still looks valid, but the evaluation context changes. This page explains what the runtime actually does, why expressions like voszData.data[index] can start failing or reading the wrong frame, and which prefixes are safe.

Snap Summary

  • Each nested <generate> iteration runs in its own context frame.
  • In nested frames, this.* is the stable way to reference variables declared in the current generate.

How DATAMIMIC Builds Scope Frames

Each <generate> iteration creates its own scope frame with:

  • current_variables: values created by <variable>
  • current_product: values already written by <key> or nested children
  • current_name: the current <generate name="...">

The important consequence is:

  • The first <generate> directly below <setup> is flattened into the top-level namespace.
  • A nested <generate> is added under its own name.
  • The current frame is always available through this.
  • In nested frames, the merged direct parent view is available through this.parent.

The Rule That Trips People Up

Inside a nested <generate>, an unqualified name does not reliably mean “the variable declared right here”.

It often resolves against the flattened ancestor frame instead.

That is why this can break after moving variables inward:

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

If voszData and index used to live in the outer frame, that expression reads the outer values. After moving them into the nested frame, the unqualified expression may still resolve to the outer frame, not the new local one.

Use this instead:

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

Quick Decision Guide

Use this shortcut when writing or reviewing expressions:

You want to read... Use...
A variable declared in the current nested <generate> this.varName
A variable from the direct parent generate this.parent.varName
A variable through an explicit named path child.grand_child.varName
A variable in the first generate below <setup> usually varName

Tip

If you are unsure, prefer this.*. It is the clearest and safest choice for local nested variables.

Reference Table

Where the value is declared Preferred reference Notes
Current nested <generate> this.varName Most stable form
Current nested <generate> <currentGenerateName>.varName Works for nested generates that are already inside another generate
Direct parent <generate> this.parent.varName Explicit and safe
Flattened outer frame varName Can work, but is easy to misread
Nested path already built in the current tree child.grand_child.varName Useful for explicit tree access
First <generate> directly under <setup> varName or root.varName Usually not available as root.generateName.varName

Example 1: Moving Mem Variables Into the Inner Generate

Works when variables stay in the outer frame

 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>

Here voszData.data[index] resolves against the outer frame, so it works.

Fails or reads the wrong frame after moving the variables inward

 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"/>

            <!-- Risky: still resolves to the flattened outer frame -->
            <key name="selected_wrong" script="voszData.data[index]"/>

            <!-- Correct: explicit current frame -->
            <key name="selected_this" script="this.voszData.data[this.index]"/>

            <!-- Also valid in a nested generate -->
            <key name="selected_named" script="datFileCreation.voszData.data[datFileCreation.index]"/>

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

Guidance

  • Use this.* when the variable is declared in the same nested generate.
  • Use this.parent.* when you intentionally want the outer frame.
  • Avoid leaving the expression unqualified after moving variables across frames.

Example 2: Cascade Generate and 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>

Why this.id matters:

  • Outer id can stay unqualified because that frame is flattened.
  • Inner id values should be qualified with this. to avoid shadowing confusion.
  • Reusing names like id, index, or row across levels is safe only if you stay explicit.

Example 3: Named Path Access Across Nested 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>

This shows the current namespace tree, not a generic “any ancestor by name” rule:

  • child.idx works because child is the flattened parent frame.
  • child.grand_child.index works because grand_child is nested under that frame.
  • this.index is still the clearest way to say “the variable from the current generate”.

Example 4: nestedKey Pathing Is the Same Idea

nestedKey pathing uses the same mental model: you address values through the currently available tree instead of assuming “nearest name wins”.

 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>

Conceptually:

  • data is the named generate frame.
  • send_info is a nested structure below it.
  • contact_method.Email is resolved through that explicit path.

The same advice applies to nested generates: prefer explicit paths when a value is not obviously local.

Example 5: Shared Variables Outside, Local Variables Inside

This is where the scoping model becomes useful rather than confusing.

 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">
        <!-- Shared data used by more than one nested generate -->
        <variable name="customer" source="mem" type="customer_seed" cyclic="True"/>

        <generate name="invoice" count="1">
            <!-- Local helper only for invoice generation -->
            <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">
            <!-- Local helper only for delivery note generation -->
            <variable name="lineIndex" generator="IncrementGenerator"/>

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

Why this is useful:

  • customer stays outside because both nested generates need it.
  • Each nested generate can still have its own local lineIndex.
  • this.lineIndex makes it obvious that the value is local to the current nested generate.
  • Reusing the same helper name in two nested generates stays readable when you qualify it.

Why This Model Is Flexible

This scoping model gives you two useful patterns:

  • Shared outer variables: Declare lookup data, memstore-backed values, or common IDs once in an outer generate and reuse them in several nested generates.
  • Local inner variables: Keep helper counters, temporary selectors, or formatting values inside the nested generate where they belong.

The result is a model that stays explicit:

  • Shared values stay shared.
  • Local values stay local.
  • Nested paths stay readable.
  • Shadowing becomes manageable when you use this.* consistently.

Best Practices

  • Prefer this.* for variables declared inside the current nested generate.
  • Prefer this.parent.* when you intentionally read from the direct parent frame.
  • Keep shared memstore-backed variables in the outer generate if several nested generates need them.
  • If variables are local to one nested generate, keep them local and use this.* consistently.
  • Avoid reusing names like id, index, row, or data across levels unless you always qualify them.
  • Treat unqualified names inside nested generates as risky, even if they currently work.

Troubleshooting

Symptom Likely cause Fix
variable not found The value exists only in the current nested frame, but the expression is resolving elsewhere Use this.varName or <currentGenerateName>.varName
Wrong value but no error The expression resolved to an outer flattened frame Qualify with this.* or this.parent.*
ambiguous variable or confusing shadowing The same name exists in more than one frame Rename the variable or qualify the frame explicitly
Path works in one level but not another The namespace tree shape changed after nesting Switch to this.* for current values and explicit named paths for tree access
root.someGenerate.var fails The first generate under <setup> is flattened, so that generate name may not exist under root Use root.var or the current in-scope named path instead