Goal: When dragging a collection choice wrapper node to a collection target field, auto-generate for-each wrapping choose/when/otherwise.
A collection choice is xs:choice with maxOccurs > 1. When the choice wrapper node (not an individual member) is dragged onto a target, the system needs to handle both the collection aspect (for-each) and the choice aspect (choose/when/otherwise).
The DnD UX on choice member nodes is addressed separately by #3167
Blocked by: #2856 (MappingPairService)
Prerequisite: verify layer icon
DocumentService.isCollectionField() already checks maxOccurs regardless of wrapperKind, so collection choice wrappers should automatically show the layer icon. Verify this works — no code change expected.
DnD scenarios (wrapper node only)
| # |
Source |
Target |
Expected Behavior |
Notes |
| S1 |
Collection choice wrapper |
Non-collection field |
choose/when/otherwise only |
Same as non-collection choice. "Only source is collection → regular mapping" rule. No change needed |
| S2 |
Collection choice wrapper |
Collection field |
for-each wrapping choose/when/otherwise |
New behavior. Both sides are collections → auto for-each. Inside the loop, choose handles type discrimination |
Target side: dropping onto a collection choice wrapper always errors out ("Cannot map to an unselected choice") — existing validation, no change needed.
Decision flow
Source collection choice wrapper → Target field
│
├─ Is target a collection field?
│ NO → choose/when/otherwise only (S1, same as today)
│ YES ↓
│
├─ canUseCopyOf(choiceField, targetField)
│ → always false (__choice__ wrapper never FQN-matches a real field)
│
└─ Create ForEachItem, then createChooseFromChoice() inside it (S2)
Expected XSLT output (S2)
<xsl:for-each select="/ns0:Source/{choice-path}">
<TargetElement>
<xsl:choose>
<xsl:when test="member1">
<xsl:value-of select="member1"/>
</xsl:when>
<xsl:when test="member2">
<xsl:value-of select="member2"/>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</TargetElement>
</xsl:for-each>
Changes
MappingActionService.engageMapping() (packages/ui/src/services/visualization/mapping-action.service.ts):
- When source is an unselected
ChoiceFieldNodeData AND isCollectionField(sourceField) AND target is a collection field (S2):
- Create
ForEachItem first
- Then generate
ChooseItem with when/otherwise inside it (reuse createChooseFromChoice() logic)
Verification
Testing
- Add test cases in
mapping-action.service.choice.test.ts for S2 scenario
- Verify existing choice mapping tests still pass (S1 regression)
- Verify XSLT serialization/deserialization for the nested structure
Goal: When dragging a collection choice wrapper node to a collection target field, auto-generate
for-eachwrappingchoose/when/otherwise.A collection choice is
xs:choicewithmaxOccurs > 1. When the choice wrapper node (not an individual member) is dragged onto a target, the system needs to handle both the collection aspect (for-each) and the choice aspect (choose/when/otherwise).The DnD UX on choice member nodes is addressed separately by #3167
Blocked by: #2856 (MappingPairService)
Prerequisite: verify layer icon
DocumentService.isCollectionField()already checksmaxOccursregardless ofwrapperKind, so collection choice wrappers should automatically show the layer icon. Verify this works — no code change expected.DnD scenarios (wrapper node only)
choose/when/otherwiseonlyfor-eachwrappingchoose/when/otherwisefor-each. Inside the loop, choose handles type discriminationTarget side: dropping onto a collection choice wrapper always errors out ("Cannot map to an unselected choice") — existing validation, no change needed.
Decision flow
Expected XSLT output (S2)
Changes
MappingActionService.engageMapping()(packages/ui/src/services/visualization/mapping-action.service.ts):ChoiceFieldNodeDataANDisCollectionField(sourceField)AND target is a collection field (S2):ForEachItemfirstChooseItemwithwhen/otherwiseinside it (reusecreateChooseFromChoice()logic)Verification
for-eachwrappingchoose/when/otherwisechoose/when/otherwise(no regression)for-each→chooseTesting
mapping-action.service.choice.test.tsfor S2 scenario