Log in
appy.pod Writing ODT templates The « pod » function

Via the pod function, it is possible to include, in a POD result, sub-documents being themselves computed as POD results from other POD templates.

Imagine you have developed some accounting software. As a POD fan, you have defined a POD template allowing to produce the PDF version of every invoice stored in your database.

Now, what about producing a global PDF file including all the invoices of a given company, for a given period ?

You could be tempted to create a new template, insert a global section in it, repeat this section for every requested invoice, and copy/paste, in that section, the content of the POD template used to render a single invoice.

This will work, but in terms of maintainability, it is not ideal: you have 2 copies of the same template.

This is where the pod function comes into play. It allows to inject, in a main POD result, results of rendering other, so-called sub-PODs.

Warning: LibreOffice is required to run in server mode in order to use the pod function.

In the mentioned example, a single sub-POD would be executed in a for statement, as many times as there are invoices for the selected company and period. But you could also imagine to inject, at several distinct places in the main POD, the result of applying different sub-PODs.

The following example illustrates the first case. Let's start with the sub-POD: a magnificent POD template (suppose it it stored in /home/gdy999/test/Invoice.odt) whose purpose is to render an individual invoice.

Since I make the assumption you are comfortable with object-oriented programming, I guess you can understand why I may have decided to deliver the invoice in the POD context in a variable named self. It is even a convention you could adopt in your own templates. If a template concerns a given object, it can be considered a kind of "document representation" of it. Using variable self to manipulate the object inside this representation makes sense, exactly as it makes sense when manipulating it in any of the object's methods.

In individual mode, my accounting software would call this template (for a starting company), with a context like:

class Invoice:
    def __init__(self, number):
        self.number = number
self = Invoice('001')

The result would be:

Let's now illustrate how this POD template could be used at a global level, as a sub-POD within a main POD. In the main POD as shown below, my objective is to get, in a single document, one page per invoice, using Invoice.odt as described hereabove. In order to illustrate this, we first need an improved version of class Invoice, that incorporates an incredible mini-DBMS system implemented in method getAll:

from appy.pod.renderer import Renderer
 
class Invoice:
    def __init__(self, number):
        self.number = number
 
    @classmethod  
    def getAll(class_):
        return class_('001'), class_('002')

The global template would look like this.

A section is created in the sole purpose of repeating it (with the minus sign) for every requested invoice. This is implemented by the first statement. The second statement will replace the target paragraph by the result of executing the sub-POD template Invoice.odt. Here is the result.

Who said it is unreadable? How old are you? Stop complaining: you have guessed what is written down in every page. Some explanations are requested here.

  1. The absolute path to the sub-POD template is passed to the pod function in parameter at, which has exactly the same semantics as the homonym parameter for function document.
  2. By default, the context defined at the global level works as a global namespace being available to the sub-PODs as well. But in the example, the sub-POD needs to be called vith variable self and does not know about variable invoice. When calling the pod function, a specific context can be passed via parameter context. Of course, the sub-POD context can be defined based on elements from the global context. In this case, Invoice.odt requires an instance of class Invoice in variable named self. This requirement is met by defining context {'self':invoice}. Variable invoice, mapped to local variable self, successively represents the 2 invoices to produce, due to the previous for statement. Note that, when using a specific context, all the other variables from the global context will not be propagated to the sub-POD.
  3. After each invoice, in the global ODT result, it is more elegant to insert a page break, excepted for the last one. This is achieved via parameter pageBreakAfter. Expression loop.invoice.last is True if, for the loop whose iterator variable is named invoice, we are at the last iteration.

Reference

The pod function, as available in the default POD context, corresponds to method importPod defined on class Renderer.

    def importPod(self, content=None, at=None, format='odt', context=None,
                  pageBreakBefore=False, pageBreakAfter=False,
                  managePageStyles='rename', resolveFields=False,
                  forceOoCall=False):
        '''Implements the POD statement "do... from pod"'''

        # Similar to importDocument, but allows to import the result of
        # executing the POD template whose absolute path is specified in at
        # (or, but deprecated, whose binary content is passed in content, with
        # this format) and include it in the POD result.

        # Renaming page styles for the sub-POD (managePageStyles being
        # "rename") ensures there is no name clash between page styles (and tied
        # elements such as headers and footers) coming from several sub-PODs or
        # with styles defined at the master document level. This takes some
        # processing, so you can set it to None if you are sure you do not need
        # it.

        # resolveFields has the same meaning as the homonym parameter on the
        # Renderer.

        # By default, if forceOoCall is True for self, a sub-renderer ran by
        # a PodImporter will inherit from this attribute, excepted if
        # parameter forceOoCall is different from INHERIT.

Technically, implementing the pod function is achieved by creating a "sub"-instance of the Renderer class, devoted to the rendering of the sub-POD(s). Parameters resolveFields and forceOoCall allow to fine-tune the process of transmitting parameters from the main renderer to a sub-renderer. Refer to the section describing the Renderer constructor for more information about these parameters.

What about performance ?

The process of rendering sub-PODs and integrating them into a main POD result is a complex and resources-consuming task. The following optimisations have already be implemented.

  1. When rendering a series of sub-documents based on the same POD template, the Renderer instance is reused from one call to the other.
  2. When rendering a sub-POD template (or, actually, any POD template), POD (a) unzips the template (an ODF document is a zip file) in a temporary folder, (b) parses the zip's internal files with SAX parsers, for producing a sort of Abstract Syntax Tree (AST) of buffers (one for every encountered main POD statement) and (c) walks the AST, using the current POD context to produce the POD result, that is finally rezipped. When rendering a series of sub-documents based on the same POD template, step (a) is optimized and done only once, but step (b) is repeated for every sub-document. POD could go one step further and optimize steb (b) as well. That being said, it is not that simple, because, for performance reasons, the root buffer within the AST is a FileBuffer whose content is directly written to disk.