File templates in C
XML
So I had to generate a few XML files in my small C utility program.
Those had rigid structure but were also variable enough that a big static
string or copy pasting template file would not be enough. This types of
problems are common to the websites generators so there are plenty of HTML
template engines which use special characters like %%,
${} or PHP's <?= >.
Approach like that would require from me to have a separate template file and to invent (or use existing one) syntax which then I would have to parse and replace with expected values to generate final file. This is not a bad approach in general but for a small utility program of mine seemed too much.
So I've come up with a simpler solution which I would like to share.
#define _(fmt, ...)
Of course it's a macro, but wait - there's more to it. First let's take a look:
#define _(fmt, ...) (buf += sprintf(buf, fmt "\n", ##__VA_ARGS__))
char *xml = buf; // this will point to the generated file
_("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
_("<JPK xmlns:etd=\"http://crd.gov.pl/xml/schematy/dziedzinowe/mf/2022/09/13/eD/DefinicjeTypy/\">");
_(" <Naglowek>");
_(" <WariantFormularza>3</WariantFormularza>");
_(" <DataWytworzeniaJPK>%s</DataWytworzeniaJPK>", gen);
_(" <NazwaSystemu>sacc</NazwaSystemu>");
_(" <CelZlozenia poz=\"P_7\">1</CelZlozenia>");
_(" <Rok>%d</Rok>", invoices[si].sold.year);
_(" <Miesiac>%d</Miesiac>", invoices[si].sold.month);
_(" </Naglowek>");
_(" <Podmiot1 rola=\"Podatnik\">");
_(" <OsobaFizyczna>");
_(" <etd:NIP>%s</etd:NIP>", entity_row->nip);
if (jpkvat_row) {
_(" <Email>%s</Email>", jpkvat_row->email);
_(" <Telefon>%s</Telefon>", jpkvat_row->phone);
}
_(" </OsobaFizyczna>");
_(" </Podmiot1>");
_(" <Deklaracja>");
_(" <PozycjeSzczegolowe>");
_(" <P_11>%s</P_11>", roundtrim(monthnet, 0));
_(" <P_37>%s</P_37>", roundtrim(monthnet, 0));
_(" </PozycjeSzczegolowe>");
_(" <Pouczenia>1</Pouczenia>"); // has to be 1
_(" </Deklaracja>");
_(" <Ewidencja>");
for (i = si; i <= ei; i++) {
_(" <SprzedazWiersz>");
_(" <LpSprzedazy>%zu</LpSprzedazy>", i-si+1);
_(" <KodKrajuNadaniaTIN>PL</KodKrajuNadaniaTIN>");
_(" <NrKontrahenta>%s</NrKontrahenta>", invoices[i].buyer_row->nip);
_(" <NazwaKontrahenta>%s</NazwaKontrahenta>", invoices[i].buyer_row->name);
_(" <DowodSprzedazy>%s</DowodSprzedazy>", invoices[i].no);
_(" <DataWystawienia>%s</DataWystawienia>", invoices[i].sold.str);
_(" <DataSprzedazy>%s</DataSprzedazy>", invoices[i].sold.str);
_(" <K_11>%s</K_11>", roundtrim(invoices[i].net, 2));
_(" </SprzedazWiersz>");
}
_(" <SprzedazCtrl>");
_(" <LiczbaWierszySprzedazy>%zu</LiczbaWierszySprzedazy>", ei-si+1);
_(" <PodatekNalezny>0</PodatekNalezny>");
_(" </SprzedazCtrl>");
_(" <ZakupCtrl>");
_(" <LiczbaWierszyZakupow>0</LiczbaWierszyZakupow>");
_(" <PodatekNaliczony>0</PodatekNaliczony>");
_(" </ZakupCtrl>");
_(" </Ewidencja>");
_("</JPK>");
buf++; // move after the final null terminator
#undef _
As you can see macro is only part of the magic here. To complete the trick I moved the variables far away from the actual strings to make it both easy to just read the template and to see which lines have actual variable values in them.
This allows you to have very flexible templates with any kind of loops and conditions within them. You can also have comments per line.
The details
The definition of the macro is not actually important as it can be a call to
any function. The idea is to use a macro with a name that is almost invisible
and to make it so that each line has a new line appended automatically. Also
one part of this macro magic is the usage of ##__VA_ARGS__.
This relies on non standard extension which will remove the last comma if the
list of arguments is empty so that both _("str") and
_("%d", i) are valid. One can ofc workaround this issue if
they want to program in strict C89 or C99 and just define two macros: one
which accepts arguments and one which does not.
Also the buf here is assumed to be big enough. You can just
malloc(1024*1024*1024) (one gigabyte) and realloc
after your template is finished or do anything you want within your underscore
macro to manage the memory.
This way of doing the templates also workarounds the fact that there is a limit of how long can one constant string be within C source code.
#undef _
And that's it. I give you official permission to use this secret technique. Remember to tell your friends and family about it.