Automatic template filling

The automatic template filling is the simplest way to generate a templated content. To fill the template variables and blocks with data, it is first necessary to load the whole template into the primary Block object. This can be done by setting a template string text or a text file in the Block.__init__() constructor. Alternatively, the Block.template attribute, or the Block.load_template() method can be used.

A template can then be filled using the Block.fill() method with the required data provided as an argument in a Python dictionary. The dictionary keys represent the template variable and block tags. The data dictionary needs to

Note

In reality, the automatic template filling process is, of course, not fully automatic. It is necessary to provide the data to fill the template in a correct format matching the template structure. However, the filling process is then all done by the Block.fill() method, unlike with a manual approach, where the filling script needs to call the individual Block methods to generate the required content.

Basic automatic filling

The values assigned to the data dictionary keys representing template tags can perform various operations described in the sections below depending on the data type of the dictionary value.

Setting a variable value

A variable value is set using a basic data type (i.e., int, float, str, or bool).

The example below sets the variables word1 and word2 to the string values Hello, World!:

blk = blockie.Block("<WORD1> <WORD2>")
blk.fill({"word1": "Hello", "word2": "world!"})
print(blk.content)

prints:

Hello world!

Setting a block content

The content of a block needs to be set by a dictionary, as shown in the following example setting the content of a date block, specifically setting its child day and month variable values:

blk = blockie.Block("<DATE><DAY> <MONTH></DATE>")
blk.fill({"date": {"day": 24, "month": "December"}})
print(blk.content)

prints:

24 December

Cloning a block

A block can be cloned, i.e., duplicated, using a list or a tuple of dictionaries with each dictionary corresponding to one clone of a block content. The example below shows setting of two dates in two clones of a date block:

blk = blockie.Block("<DATE><DAY> <MONTH>\n</DATE>")
blk.fill({"date": [{"day": 24, "month": 12}, {"day": 31, "month": 12}, {"day": 1, "month": "January"}]})
print(blk.content)

prints:

24 12
31 12
1 January

Important

The blocks having multiple content variations cannot be cloned directly. They can, however, be cloned indirectly by “wrapping” them in a standard parent block that can be cloned, e.g., <DATE_WRAP><DATE><DAY>.<MONTH>.<^DATE><DAY> <MONTH></DATE><DATE_WRAP>, where the DATE_WRAP block content can be cloned and each clone would contain a new DATE block having two content variations.

Note that the process of setting the selected block content variation is described later.

Example

The following filling script example shows all simple concepts described above, i.e., the template containing the basic tags and also automatic tags filled using the basic principles of automatic filling. The template is defined directly by the template string and the data to fill the template with are defined by the data dictionary.

from blockie import Block


template = """
                            SHOPPING LIST
  Items                                                         Quantity
------------------------------------------------------------------------
<ITEMS>
* <ITEM><+>                                                     <QTY>
</ITEMS>


Short list: <ITEMS><ITEM><.>, <^.></.></ITEMS>
"""

data = {
    "items": [
        {"item": "apples", "qty": "1 kg"},
        {"item": "potatoes", "qty": "2 kg"},
        {"item": "rice", "qty": "1 kg"},
        {"item": "orange juice", "qty": "1 l"},
        {"item": "cooking magazine", "qty": 1},
    ]
}

blk = Block(template)
blk.fill(data)
print(blk.content)

The script prints the following generated content:

                            SHOPPING LIST
  Items                                                         Quantity
------------------------------------------------------------------------
* apples                                                        1 kg
* potatoes                                                      2 kg
* rice                                                          1 kg
* orange juice                                                  1 l
* cooking magazine                                              1


Short list: apples, potatoes, rice, orange juice, cooking magazine

Note

Notice that the template contains two ITEMS blocks containing the variable ITEM and that both blocks are automatically filled by the same data, since they have the same name.

Advanced automatic filling

Similarly to the basic operations, the values in a data dictionary can also perform additional operations described in the following sections.

Setting a block content as is

A single block can be set to the generated content as is, i.e., without setting any of its child elements, by setting the block value to a simple non-empty value, which can be a non-empty string, zero or positive number or a boolean true. It is expected that the block content is either constant, or the variables inside have been already set. The example below shows setting a date block having a constant content into the final generated output just by setting it to boolean true:

blk = blockie.Block("<DATE>24 December</DATE>")
blk.fill({"date": True})
print(blk.content)

prints:

24 December

Setting an implicit iterator value

If a block contains just one variable, then cloning such a block and setting its single variable can be simplified using an implicit iterator tag inside the block and then filling the block by setting its value to a list or tuple of iterator values as illustrated below:

blk = blockie.Block("<LIST>- <*>\n</LIST>")
    blk.fill({"list": ["gloves", "plastic bags", "duct tape", "shovel"]})
    print(blk.content)

prints:

- gloves
- plastic bags
- duct tape
- shovel

Setting a block content variation

A specific content from a block with multiple content variations can be selected using a special vari_idx key with a numeric value defined in a dictionary corresponding to the block. The value of a number assigned to the vari_idx key defines one of the operations below:

  • A value equal to zero or higher (>=0) selects the block content variation with an index corresponding to the provided value.

  • A negative value (<0) removes the entire block from the generated content.

  • A boolean value can be used, where True has the same effect as the value zero and False has the same effect as a negative value.

Note

Note that removing a block using a vari_idx key set to a negative value is only the secondary purpose of the vari_idx key. The primary method of a block removal is described later.

The example below uses the special vari_idx key to set the first (index = 0) content variation:

blk = blockie.Block("<DATE><DAY>.<MONTH>.<^DATE><DAY> <MONTH></DATE>")
blk.fill({"date": {"vari_idx": 0, "day": 24, "month": 12}})
print(blk.content)

prints:

24.12.

A slightly more advanced example illustrates adding and setting the vari_idx key based on the data type used for setting the month key value:

date_dict = {"day": 24, "month": "December"}
date_dict["vari_idx"] = 0 if isinstance(date_dict["month"], int) else 1

blk = blockie.Block("<DATE><DAY>.<MONTH>.<^DATE><DAY> <MONTH></DATE>")
blk.fill({"date": date_dict})
print(blk.content)

prints:

24 December

Alternatively, if a block has a constant content, it is possible to select one of its constant content variations by directly setting a numeric value representing the content index to its key in a data dictionary, as shown in the second example below:

blk = blockie.Block("<DATE>24.12.<^DATE>24 December</DATE>")
blk.fill({"date": 1})
print(blk.content)

prints:

24 December

See also

See the note about cloning blocks with multiple content variations in the Block cloning section.

Setting a handler for manual filling

Note

The manual filling process is only needed in very special use cases, so in vast majority of common applications this section can be skipped.

The automatic method of filling the block template can be partially suplemented by the manual method using a special fill_hndl key with a handler function value defined in a dictionary corresponding to the block. The function assigned to the fill_hndl key defines a handler called when a block is being filled. The handler function can call the Block methods to perform special low-level operations if needed.

The handler function must use the following declaration:

func(block: Block, data: dict | object, clone_subidx: int) -> None

where:

  • block: A Block object corresponding to the template block for which the handler has been called.

  • data: The dictionary (or optionally a custom object) used for filling the content of a block.

  • clone_subidx: An index of a cloned content being filled. Only applicable if the block is being cloned during the automatic filling process.

The example below illustrates the use of a manual filling handler function format_month to make the MONTH variable value using uppercase letters if it is a string and then setting the content variation of the DATE block using the Block.set() method:

def format_month(block: blockie.Block, data: dict, _clone_subidx: int) -> None:
    if isinstance(data["month"], str):
        data["month"] = data["month"].upper()
        block.get_subblock("date").set(vari_idx=1)
    else:
        block.get_subblock("date").set(vari_idx=0)

blk = blockie.Block("<DATE><DAY>.<MONTH>.<^DATE><DAY> <MONTH></DATE>")
blk.fill({"day": 24, "month": "December", "fill_hndl": format_month})
print(blk.content)

prints:

24 DECEMBER

Removing a variable

A variable can be removed from the generated content by setting its dictionary value to an empty string or to none as shown on the example below removing the variable for a middle name.

blk = blockie.Block("<NAME> <MIDNAME> <SURNAME>")
blk.fill({"name": "Patrick", "midname": None, "surname": "Bateman"})
print(blk.content)

prints:

Patrick  Bateman

Removing a block

A block can be removed from the content by setting it to an empty value, which can be an empty dictionary, empty list or tuple, none, negative number, or boolean false. The following example uses a None object to remove the wrapper block MIDNAME_WRAP defining a content with the variable for a middle name inside:

blk = blockie.Block("<NAME> <MIDNAME_WRAP><MIDNAME> </MIDNAME_WRAP><SURNAME>")
blk.fill({"name": "Patrick", "surname": "Bateman", "midname_wrap": None})
print(blk.content)

prints:

Patrick Bateman

Note

Notice how using a wrapper block allows a better control over the parts removed from the generated content. In the example above, it it allows to remove the variable for a middle name and also the space character that would otherwise remain in the generated content.

Example

The filling script below expands the basic automatic filling concepts using the advanced operations described above. The script uses a template loaded from a text file and data to fill it are loaded from a JSON file. The generated content is then saved into the output text file.

The template input file shoplist_tmpl.txt:

                                SHOPPING LIST
  Items                                                         Quantity
------------------------------------------------------------------------
<ITEMS>
* <FLAG>IMPORTANT! <^FLAG>MAYBE? </FLAG><ITEM><+>               <QTY><UNIT> kg<^UNIT> l</UNIT>
</ITEMS>


Short list: <ITEMS><ITEM><.>, <^.></.></ITEMS>

The input data file shoplist_data.json:

{
    "items": [
        {"item": "apples", "qty": "1", "unit": 0},
        {"item": "potatoes", "qty": "2", "unit": 0},
        {"item": "rice", "qty": "1", "unit": 0},
        {"item": "orange juice", "qty": "1", "unit": 1},
        {"item": "cooking magazine", "qty": null, "unit": null}
    ]
}

The script code:

import json
from blockie import Block

important_items = ("potatoes", "rice")
maybe_items = ("cooking magazine",)

with open("shoplist_data.json", encoding="utf-8") as file:
    data = json.load(file)

    for item in data["items"]:
        item["flag"] = 0 if item["item"] in important_items else \
            1 if item["item"] in maybe_items else None

    blk = blockie.Block()
    blk.load_template("shoplist_tmpl.txt")
    blk.fill(data)
    blk.save_content("shoplist_gen.txt")

Note

Notice that the value of the FLAG variable in the template is defined by the script setting the flag key value into the input dictionary data. This is done to illustrate how to control the template filling logic within the script, since the Blockie templates are logicless.

The generated output file shoplist_gen.txt:

                            SHOPPING LIST
  Items                                                         Quantity
------------------------------------------------------------------------
* apples                                                        1 kg
* IMPORTANT! potatoes                                           2 kg
* IMPORTANT! rice                                               1 kg
* orange juice                                                  1 l
* MAYBE? cooking magazine


Short list: apples, potatoes, rice, orange juice, cooking magazine