Best practice when using Merge Conditional <mc> macros in XPP

Here is a best practice suggestion for those still using classic Merge Conditional <mc> macros in XPP.

There is a note about this subject at the end of the documentation of mc in the XyMacros manual, which says (formatted differently here):

When using an mc that generates text that may also wrap across lines, make sure the mc macro does not directly call out another macro that generates text, but rather use only the nul and skp arguments. Otherwise it can result in repeated generated text from the mc macro. For example, instead of

<mc;#13=1;xpp:ModOrder;nul><mc;#15]0;xpp:TocaseBook;nul>

where <xpp:ModOrder> and <xpp:TocaseBook> generate text and the generated text can wrap across lines, use

<mc;#13=1;nul;skp><xpp:ModOrder><mc;#15]0;nul;skp><xpp:TocaseBook>

Why?

Unfortunately, it's due to a limitation of XPP composition in how it handles "generated text". Composition was never designed to track the nesting levels of generated text, and so it can sometimes result in problems with duplicated (or repeated) generated text if things wrap across a line end. This limitation has always existed, and there's no easy solution (w/o redesigning and rewriting a whole bunch of the XPP composition code, taking a whole bunch of time and resources to do it, and then spending a lot of time fixing everything that gets broken). In any case, for the foreseeable future it is what it is.

This situation can particularly be a problem when using the mc macro. But there is a workaround (and this is one of the reasons we've never been forced to address it by fixing composition).

I'll note that there are also other cases than "generated text" where it can be important to use the "best practice" being discussed when writing mc macros. It can also apply when there is some "side effect" of what is being executed with the mc macro, such as incrementing a register (where if that happens more times that you expect, it can obviously result in wrong register values and wrong results).

So in general, unless you have a totally simplistic and "obvious" mc macro that doesn't have any "side effects" such as generating text or incrementing registers or you know that there's no possibility that what the mc macro does could wrap across a line end, then you should write your mc macros to follow the suggested best practice (aka the workaround).

I've seen cases in customer samples where the nesting of mc macros and what they call has been many levels deep (and almost impossible to tell what's actually being done and where).

So, the conclusion is that if you want to be safer then always avoid writing the following mc macros:

<mc;test;macro1(arg1,arg2,...);nul>
<mc;test;nul;macro2(arg1,arg2,...)>
<mc;test;macro1(arg1,arg2,...);macro2(arg1,arg2,...)>

And instead write these mc macros in the different way as follows:

<mc;test;nul;skp><macro1;arg1;arg2;...>
<mc;test;skp;nul><macro2;arg1;arg2;...>
<mc;test;nul;skp><macro1;arg1;arg2;...><mc;test;skp;nul><macro2;arg1;arg2;...>

Now if either macro1 or macro2 are themselves mc macros, then you have some work to do to adhere to this best practice. Those situations are best handled by breaking things into multiple macro definitions.

And this applies whether you are using <mc> or </mc>.

Jonathan Dagresta
SDL XPP Engineering

Parents
  • Jonathan,

    Interesting...

    But for me the best solution would be to switch to XyPerl whenever there is something conditional.
    As you said conditional macros quickly get very obfuscated when there is more than 1 test to do, or when they go more than 1 level deep
    And with the generated text problem now you give us yet another reason to switch the XyPerl.

    Using XyPerl your first example would become:

    sub check {
        my $X = XppCompo->new();
        my $reg13 = $X->get_reg('#13');
        my $reg15 = $X->get_reg('X15');
        $X->set_text("...ModOrder text...") if ($reg13 == 1);
        $X->set_text("...TocaseBook text..") if ($reg15 > 0);
    }
    

    Your last example would suddenly becomes a lot more readable:

    sub othercheck {
        my $X = XppCompo->new();
        if (sometest) {
            $X->xymacro('macro1', 'arg1', 'arg2', ...);
        } else {
            $X->xymacro('macro2', 'arg1', 'arg2', ...);
        }
    }

    (assuming that text generated from perl does give similar problems like the mc macro, but I do not think that is the case....)

Reply
  • Jonathan,

    Interesting...

    But for me the best solution would be to switch to XyPerl whenever there is something conditional.
    As you said conditional macros quickly get very obfuscated when there is more than 1 test to do, or when they go more than 1 level deep
    And with the generated text problem now you give us yet another reason to switch the XyPerl.

    Using XyPerl your first example would become:

    sub check {
        my $X = XppCompo->new();
        my $reg13 = $X->get_reg('#13');
        my $reg15 = $X->get_reg('X15');
        $X->set_text("...ModOrder text...") if ($reg13 == 1);
        $X->set_text("...TocaseBook text..") if ($reg15 > 0);
    }
    

    Your last example would suddenly becomes a lot more readable:

    sub othercheck {
        my $X = XppCompo->new();
        if (sometest) {
            $X->xymacro('macro1', 'arg1', 'arg2', ...);
        } else {
            $X->xymacro('macro2', 'arg1', 'arg2', ...);
        }
    }

    (assuming that text generated from perl does give similar problems like the mc macro, but I do not think that is the case....)

Children