Ah, then I see why you experimented with the d2:condition 
The d2:condition is not fully supported in program rules unfortunately, and that is why its not documented.
One strategy I have seen employed is to make one rule per data element, and one calculated program rule variable per data element. #{DE1_TEXT}, #{DE2_TEXT}, #{DE3_TEXT} and #{DE4_TEXT} for example.
Each program rule assigns #{DEx_TEXT} only if DEx has a value, and the assigned string will be d2:concatenate(#{DEx}, ‘,’).
Then you will end up with 4 calculated program rule variables, containing either nothing, or a string and a comma. For example they might be assigned like this(in my example DE2 contains “foo” and DE4 contains “bar”;
#{DE1_TEXT}: empty
#{DE2_TEXT}: “foo,”
#{DE3_TEXT}: empty
#{DE4_TEXT}: “bar,”
Then, you can run a fifth rule to concatenate the 4 strings and assign them to the data element you wanted:
d2:concatenate(#{DE1_TEXT},#{DE2_TEXT},#{DE3_TEXT},#{DE4_TEXT})
This would of course make a comma at the end, that you might not want. Sometimes its even necessary to put an “and” in between the second to last and last value. Removing the comma can be done with a combination of d2:length and d2:left. If you need to have a better language, with an “and” between the two last DEx_TEXTS instead of a comma, this will increase the complexity a bit, but would be doable.
Make sure if you try the strategy above that the 4 rules for assigning the helper program rule variables #{DEx_TEXT} is executed before the fifth rule to concatenate everything. This is done by adding a priority to the first 4 rules, and a lower priority or no prority to the fifth rule. Rules 1-4 can all have priority 1 for example, and rule 5 might have a blank priority.
For the future we will also add this thread to the consideration for wether to build full support for d2:condition, which might have made this easier
Or even a d2:join function, allowing the user to directly join all non null values from a list, adding a delimiter between each joined element.
Markus