def check_type(value, oktypes):
    # This allows 'Space' instead of 'Space()'
    if callable(value):
        value = value()

    if isinstance(value, oktypes):
        return value

    # Invalid type
    caller = get_caller_name()
    tag = type(value).__name__
    msg = '\n\nElement "{}" received "{}" but expected {}\n'.format(caller, tag, oktypes)
    raise TypeError(msg) def check_group(value, group):
    if value not in group:
        tag = type(value).__name__
        msg = 'element {} not in group {}'.format(tag, repr(group))
        raise TypeError(msg)
    else:
        return value def encode_dict(tag, content):
    return {
        "t": tag,
        "c": content,
    } def decode_ica(lst):
    return {'identifier': lst[0],
            'classes': lst[1],
            'attributes': lst[2]} def debug(*args, **kwargs):
    """
    Same as print, but prints to ``stderr``
    (which is not intercepted by Pandoc).
    """
    print(file=sys.stderr, *args, **kwargs) def load_pandoc_version():
    """
    Retrieve Pandoc version tuple from the environment
    """
    try:
        return tuple(int(i) for i in os.environ['PANDOC_VERSION'].split('.'))
    except KeyError:
        pass
    except (AttributeError, ValueError):
        debug(f'Environment variable PANDOC_VERSION is malformed, ignoring...') def load_pandoc_reader_options():
    """
    Retrieve Pandoc Reader options from the environment
    """
    try:
        # TODO? make option names pythonic ('readerIndentedCodeClasses' -> 'indented_code_classes')
        # TODO? replace list with set (readerAbbreviations)
        options = json.loads(os.environ['PANDOC_READER_OPTIONS'])
        return options
    except KeyError:
        pass
    except json.JSONDecodeError:
        debug(f'Environment variable PANDOC_READER_OPTIONS is malformed, ignoring...')
    return dict() class ListContainer(MutableSequence):
    """
    Wrapper around a list, to track the elements' parents.
    **This class shouldn't be instantiated directly by users,
    but by the elements that contain it**.

    :param args: elements contained in the list--like object
    :param oktypes: type or tuple of types that are allowed as items
    :type oktypes: ``type`` | ``tuple``
    :param parent: the parent element
    :type parent: ``Element``
    :param container: None, unless the element is not part of its .parent.content (this is the case for table headers for instance, which are not retrieved with table.content but with table.header)
    :type container: ``str`` | None
    """
    # Based on http://stackoverflow.com/a/3488283
    # See also https://docs.python.org/3/library/collections.abc.html

    __slots__ = ['list', 'oktypes', 'parent', 'location']

    def __init__(self, *args, oktypes=object, parent=None):
        self.oktypes = oktypes
        self.parent = parent
        self.location = None  # Cannot be set through __init__

        self.list = []
        self.extend(args)  # self.oktypes must be set first

    def __contains__(self, item):
        return item in self.list

    def __len__(self):
        return len(self.list)

    def __getitem__(self, i):
        if isinstance(i, int):
            return attach(self.list[i], self.parent, self.location)
        else:
            newlist = self.list.__getitem__(i)
            obj = ListContainer(*newlist,
                                oktypes=self.oktypes, parent=self.parent)
            obj.location = self.location
            return obj

    def __delitem__(self, i):
        del self.list[i]

    def __setitem__(self, i, v):
        if isinstance(i, slice):
            v = (check_type(x, self.oktypes) for x in v)
        else:
            v = check_type(v, self.oktypes)
        self.list[i] = v

    def insert(self, i, v):
        v = check_type(v, self.oktypes)
        self.list.insert(i, v)

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return 'ListContainer({})'.format(' '.join(repr(x) for x in self.list))

    def to_json(self):
        return [to_json_wrapper(item) for item in self.list] class DictContainer(MutableMapping):
    """
    Wrapper around a dict, to track the elements' parents.
    **This class shouldn't be instantiated directly by users,
    but by the elements that contain it**.

    :param args: elements contained in the dict--like object
    :param oktypes: type or tuple of types that are allowed as items
    :type oktypes: ``type`` | ``tuple``
    :param parent: the parent element
    :type parent: ``Element``
    """

    __slots__ = ['dict', 'oktypes', 'parent', 'location']

    def __init__(self, *args, oktypes=object, parent=None, **kwargs):
        self.oktypes = oktypes
        self.parent = parent
        self.location = None

        self.dict = dict()
        self.update(args)  # Must be a sequence of tuples
        self.update(kwargs)  # Order of kwargs is not preserved

    def __contains__(self, item):
        return item in self.dict

    def __len__(self):
        return len(self.dict)

    def __getitem__(self, k):
        return attach(self.dict[k], self.parent, self.location)

    def __delitem__(self, k):
        del self.dict[k]

    def __setitem__(self, k, v):
        v = check_type(v, self.oktypes)
        self.dict[k] = v

    def __str__(self):
        return self.__repr__()

    def __repr__(self):
        return 'DictContainer({})'.format(' '.join(repr(x) for x in self.dict))

    def __iter__(self):
        return self.dict.__iter__()

    def to_json(self):
        return {k: to_json_wrapper(v) for k, v in self.dict.items()} class Element(object):
    """
    Base class of all Pandoc elements
    """
    __slots__ = ['parent', 'location']
    _children = []

    def __new__(cls, *args, **kwargs):
        # This is just to initialize self.parent to None
        element = object.__new__(cls)
        element.parent = None
        element.location = None
        return element

    @property
    def tag(self):
        tag = type(self).__name__
        return tag

    # ---------------------------
    # Base methods
    # ---------------------------
    # Should be overridden except for trivial elements (Space, Null, etc.)

    def __repr__(self):
        # This is just a convenience method
        # Override it for more complex elements

        extra = []
        for key in self.__slots__:
            if not key.startswith('_') and key != 'text':
                val = getattr(self, key)
                if val not in ([], dict(), ''):
                    extra.append([key, val])

        if extra:
            extra = ('{}={}'.format(k, repr(v)) for k, v in extra)
            extra = '; ' + ', '.join(x for x in extra)
        else:
            extra = ''

        if '_content' in self.__slots__:
            content = ' '.join(repr(x) for x in self.content)
            return '{}({}{})'.format(self.tag, content, extra)
        elif 'text' in self.__slots__:
            return '{}({}{})'.format(self.tag, self.text, extra)
        else:
            return self.tag

    def to_json(self):
        return encode_dict(self.tag, self._slots_to_json())

    def _slots_to_json(self):
        # Default when the element contains nothing
        return []

    # ---------------------------
    # .identifier .classes .attributes
    # ---------------------------

    def _set_ica(self, identifier, classes, attributes):
        self.identifier = check_type(identifier, str)
        self.classes = [check_type(cl, str) for cl in classes]
        self.attributes = dict(attributes)

    def _ica_to_json(self):
        return [self.identifier, self.classes, list(self.attributes.items())]

    # ---------------------------
    # .content (setter and getter)
    # ---------------------------

    @property
    def content(self):
        """
        Sequence of :class:`Element` objects (usually either :class:`Block`
        or :class:`Inline`) that are "children" of the current element.

        Only available for elements that accept ``*args``.

        Note: some elements have children in attributes other than ``content``
        (such as :class:`.Table` that has children in the header and
        caption attributes).
        """
        return self._content

    @content.setter
    def content(self, value):
        oktypes = self._content.oktypes
        value = value.list if isinstance(value, ListContainer) else list(value)
        self._content = ListContainer(*value, oktypes=oktypes, parent=self)

    def _set_content(self, value, oktypes):
        """
        Similar to content.setter but when there are no existing oktypes
        """
        if value is None:
            value = []
        self._content = ListContainer(*value, oktypes=oktypes, parent=self)

    # ---------------------------
    # Navigation
    # ---------------------------

    @property
    def index(self):
        """
        Return position of element inside the parent.

        :rtype: ``int`` | ``None``
        """
        container = self.container
        if container is not None:
            return container.index(self)

    @property
    def container(self):
        """
        Rarely used attribute that returns the ``ListContainer`` or
        ``DictContainer`` that contains the element
        (or returns None if no such container exist)

        :rtype: ``ListContainer`` | ``DictContainer`` | ``None``
        """
        if self.parent is None:
            return None
        elif self.location is None:
            return self.parent.content
        else:
            container = getattr(self.parent, self.location)
            if isinstance(container, (ListContainer, DictContainer)):
                return container
            else:
                assert self is container  # id(self) == id(container)

    def offset(self, n):
        """
        Return a sibling element offset by n

        :rtype: :class:`Element` | ``None``
        """

        idx = self.index
        if idx is not None:
            sibling = idx + n
            container = self.container
            if 0 <= sibling < len(container):
                return container[sibling]

    @property
    def next(self):
        """
        Return the next sibling.
        Note that ``elem.offset(1) == elem.next``

        :rtype: :class:`Element` | ``None``

        """
        return self.offset(1)

    @property
    def prev(self):
        """
        Return the previous sibling.
        Note that ``elem.offset(-1) == elem.prev``

        :rtype: :class:`Element` | ``None``
        """
        return self.offset(-1)

    def ancestor(self, n):
        """
        Return the n-th ancestor.
        Note that ``elem.ancestor(1) == elem.parent``

        :rtype: :class:`Element` | ``None``
        """
        if not isinstance(n, int) or n < 1:
            raise TypeError('Ancestor needs to be positive, received', n)

        if n == 1 or self.parent is None:
            return self.parent
        else:
            return self.parent.ancestor(n-1)

    # ---------------------------
    # Walking
    # ---------------------------

    @property
    def doc(self):
        """
        Return the root Doc element (if there is one)
        """
        guess = self
        while guess is not None and guess.tag != 'Doc':
            guess = guess.parent  # If no parent, this will be None
        return guess  # Returns either Doc or None

    def walk(self, action, doc=None, walk_inlines=True):
        """
        Walk through the element and all its children (sub-elements),
        applying the provided function ``action``.

        A trivial example would be:

        .. code-block:: python

            from panflute import *

            def no_action(elem, doc):
                pass

            doc = Doc(Para(Str('a')))
            altered = doc.walk(no_action)


        :param action: function that takes (element, doc) as arguments.
        :type action: :class:`function`
        :param doc: root document; used to access metadata,
            the output format (in ``.format``, other elements, and
            other variables). Only use this variable if for some reason
            you don't want to use the current document of an element.
        :type doc: :class:`.Doc`
        :rtype: :class:`Element` | ``[]`` | ``None``
        """

        # Infer the document thanks to .parent magic
        if doc is None:
            doc = self.doc

        # First iterate over children
        for child in self._children:
            obj = getattr(self, child)

            if isinstance(obj, Element):
                obj = obj.walk(action, doc)
            elif isinstance(obj, ListContainer):
                #if not walk_inlines and isinstance(obj.oktypes, Block):
                #    continue
                obj = (item.walk(action, doc) for item in obj)
                # We need to convert single elements to iterables, so that they
                # can be flattened later
                obj = ((item,) if type(item) is not list else item for item in obj)
                # Flatten the list, by expanding any sublists
                obj = list(chain.from_iterable(obj))
            elif isinstance(obj, DictContainer):
                obj = [(k, v.walk(action, doc)) for k, v in obj.items()]
                obj = [(k, v) for k, v in obj if v != []]
            elif obj is None:
                obj = None  # Empty table headers or captions
            else:
                raise TypeError(type(obj))
            setattr(self, child, obj)

        # Then apply the action to the root element
        altered = action(self, doc)
        return self if altered is None else altered class Block(Element):
    """
    Base class of all block elements
    """
    __slots__ = [] class Inline(Element):
    """
    Base class of all inline elements
    """
    __slots__ = [] class MetaValue(Element):
    """
    Base class of all metadata elements
    """
    __slots__ = [] class Table(Block):
    """Table, composed of a table head, one or more table bodies, and a
    a table foot. You can also specify captions, short captions, column alignments,
    and column widths.

    Example:

        >>> x = [Para(Str('Something')), Para(Space, Str('else'))]
        >>> c1 = TableCell(*x)
        >>> c2 = TableCell(Header(Str('Title')))
        >>> row = TableRow(c1, c2)
        >>>
        >>> body = TableBody(row)
        >>> head = TableHead(row)
        >>> caption = Caption(Para(Str('Title')))
        >>> table = Table(body, head=head, caption=caption)

    TODO: UPDATE EXAMPLE
    TODO: OFFER A SIMPLE WAY TO BUILD A TABLE, with e.g. .alignments and .widths

    :param args: Table bodies
    :type args: :class:`TableBody`
    :param head: Table head
    :type head: :class:`TableHead`
    :param foot: Table foot
    :type foot: :class:`TableFoot`
    :param caption: The caption of the table (with optional short caption)
    :type caption: :class:`Caption`
    :param colspec: list of (alignment, colwidth) tuples; one for each column
    :type colspec: :class:`list` of (:class:`Alignment`, :class:`ColWidth`)
    :param identifier: element identifier (usually unique)
    :type identifier: :class:`str`
    :param classes: class names of the element
    :type classes: :class:`list` of :class:`str`
    :param attributes: additional attributes
    :type attributes: :class:`dict`
    :Base: :class:`Block`

    :param alignment: List of row alignments
        (either 'AlignLeft', 'AlignRight', 'AlignCenter' or 'AlignDefault').
    :type alignment: [:class:`str`]

    :param colwidth: Fractional column widths
    :type colwidth: [:class:`float` | "ColWidthDefault"]
     """

    __slots__ = ['_content', '_head', '_foot', '_caption', 'colspec',
                 'identifier', 'classes', 'attributes', 'cols']
    _children = ['head', 'content', 'foot', 'caption']

    def __init__(self, *args, head=None, foot=None, caption=None,
                 colspec=None, identifier='', classes=[], attributes={}):

        self._set_ica(identifier, classes, attributes)
        self._set_content(args, TableBody)
        self.caption = caption

        self._set_table_width()  # also fills in colspec if it's empty
        self.head = head
        self.foot = foot
        # Colspec is a list of (alignment, width) tuples
        # TODO: add validation to colspec
        self.colspec = [(check_group(a, TABLE_ALIGNMENT),
                         check_type_or_value(w, (float, int), 'ColWidthDefault'))
                        for (a, w) in colspec] if colspec else [('AlignDefault', 'ColWidthDefault')] * self.cols
        self._validate_colspec()

    def _set_table_width(self):
        self.cols = 0
        if self.content and self.content[0].content:
            self.cols = count_columns_in_row(self.content[0].content[0].content)  # Table -> First TableBody -> IntermediateBody -> First Row

    def _validate_cols(self, block):
        if not len(block.content):
            return

        block_cols = count_columns_in_row(block.content[0].content)

        if not self.cols:
            self.cols = block_cols
        elif self.cols != block_cols:
            msg = f'\n\nInvalid number of columns in table {block.location}.'
            msg += f'Expected {self.cols} but received {block_cols}\n'
            raise IndexError(msg)

    def _validate_colspec(self):
        if self.cols != len(self.colspec):
            msg = '\n\nInvalid number of colspec tuples.'
            msg += 'Expected {} but received {}\n'.format(self.cols, len(self.colspec))
            raise IndexError(msg)

    @property
    def head(self):
        return self._head

    @head.setter
    def head(self, value):
        self._head = check_type(value, TableHead) if value else TableHead()
        self._head.parent = self
        self._head.location = 'head'
        self._validate_cols(self.head)

    @property
    def foot(self):
        return self._foot

    @foot.setter
    def foot(self, value):
        self._foot = check_type(value, TableFoot) if value else TableFoot()
        self._foot.parent = self
        self._foot.location = 'foot'
        self._validate_cols(self.foot)

    @property
    def caption(self):
        return self._caption

    @caption.setter
    def caption(self, value):
        self._caption = check_type(value, Caption)
        self._caption.parent = self
        self._caption.location = 'caption'

    def _slots_to_json(self):
        ica = self._ica_to_json()
        caption = self.caption.to_json()
        colspec = [[{'t': a}, colspec_to_json(c)] for a, c in self.colspec]
        head = self.head.to_json()
        bodies = [body._slots_to_json() for body in self.content]
        foot = self.foot.to_json()
        return [ica, caption, colspec, head, bodies, foot] class TableHead(Block):
    """
    The head of a table, containing a one or more head rows, plus optional attributes

    :param row: head rows
    :type row: :class:`str`
    :param identifier: element identifier (usually unique)
    :type identifier: :class:`str`
    :param classes: class names of the element
    :type classes: :class:`list` of :class:`str`
    :param attributes: additional attributes
    :type attributes: :class:`dict`
    :Base: :class:`Block`
     """

    __slots__ = ['_content', 'identifier', 'classes', 'attributes']
    _children = ['content']

    def __init__(self, *args, identifier='', classes=[], attributes={}):
        self._set_ica(identifier, classes, attributes)
        self._set_content(args, TableRow)

    def to_json(self):
        return [self._ica_to_json(), self.content.to_json()] class TableFoot(Block):
    """
    The foot of a table, containing a one or more foot rows, plus optional attributes

    :param row: foot rows
    :type row: :class:`str`
    :param identifier: element identifier (usually unique)
    :type identifier: :class:`str`
    :param classes: class names of the element
    :type classes: :class:`list` of :class:`str`
    :param attributes: additional attributes
    :type attributes: :class:`dict`
    :Base: :class:`Block`
     """

    __slots__ = ['_content', 'identifier', 'classes', 'attributes']
    _children = ['content']

    def __init__(self, *args, identifier='', classes=[], attributes={}):
        self._set_ica(identifier, classes, attributes)
        self._set_content(args, TableRow)

    def to_json(self):
        return [self._ica_to_json(), self.content.to_json()] class TableBody(Block):
    """
    Body of a table, containing a list of intermediate head rows, a list of table body rows, row_head_columns, plus optional attributes

    :param row: head rows
    :type row: :class:`str`
    :param head: Intermediate head (list of table rows)
    :type head: :class:`list` of :class:`TableRow`
    :param row_head_columns: number of columns on the left that are considered column headers (default: 0)
    :type row_head_columns: class:`int`
    :param identifier: element identifier (usually unique)
    :type identifier: :class:`str`
    :param classes: class names of the element
    :type classes: :class:`list` of :class:`str`
    :param attributes: additional attributes
    :type attributes: :class:`dict`
    :Base: :class:`Block`
     """

    __slots__ = ['_content', '_head', 'row_head_columns', 'identifier', 'classes', 'attributes']
    _children = ['content', 'head']

    def __init__(self, *args, head=None, row_head_columns=0,
                 identifier='', classes=[], attributes={}):
        self._set_ica(identifier, classes, attributes)
        self._set_content(args, TableRow)
        self.head = head
        self.row_head_columns = check_type(row_head_columns, int)

    @property
    def head(self):
        return self._head

    @head.setter
    def head(self, value):
        if value:
            value = value.list if isinstance(value, ListContainer) else list(value)
        else:
            value = []
        self._head = ListContainer(*value, oktypes=TableRow, parent=self)
        self._head.location = 'head'

    def _slots_to_json(self):
        return [self._ica_to_json(), self.row_head_columns,
                self.head.to_json(), self.content.to_json()] class TableRow(Element):
    """
    Table Row

    :param args: cells
    :type args: :class:`TableCell`
    :param identifier: element identifier (usually unique)
    :type identifier: :class:`str`
    :param classes: class names of the element
    :type classes: :class:`list` of :class:`str`
    :param attributes: additional attributes
    :type attributes: :class:`dict`
    :Base: :class:`Element`
     """
    __slots__ = ['_content', 'identifier', 'classes', 'attributes']
    _children = ['content']

    def __init__(self, *args, identifier='', classes=[], attributes={}):
        self._set_ica(identifier, classes, attributes)
        self._set_content(args, TableCell)

    def to_json(self):
        return [self._ica_to_json(), self.content.to_json()] class TableCell(Element):
    """
    Table Cell

    :param args: elements
    :type args: :class:`Block`
    :param alignment: row alignment
        (either 'AlignLeft', 'AlignRight', 'AlignCenter' or 'AlignDefault').
    :type alignment: :class:`str`
    :param rowspan: number of rows occupied by a cell (height of a cell)
    :type rowspan: :class:`int`
    :param colspan: number of columns occupied by a cell (width of a cell)
    :type colspan: :class:`int`
    :param identifier: element identifier (usually unique)
    :type identifier: :class:`str`
    :param classes: class names of the element
    :type classes: :class:`list` of :class:`str`
    :param attributes: additional attributes
    :type attributes: :class:`dict`
    :Base: :class:`Element`
     """
    __slots__ = ['_content', 'alignment', 'rowspan', 'colspan',
                 'identifier', 'classes', 'attributes']
    _children = ['content']

    def __init__(self, *args, alignment='AlignDefault', rowspan=1, colspan=1,
                 identifier='', classes=[], attributes={}):

        self._set_ica(identifier, classes, attributes)
        self._set_content(args, Block)
        self.alignment = check_group(alignment, TABLE_ALIGNMENT)
        self.rowspan = rowspan
        self.colspan = colspan
        if (self.rowspan <= 0):
            raise TypeError('Cell rowspan must be positive')
        if (self.colspan <= 0):
            raise TypeError('Cell colspan must be positive')

    def to_json(self):
        return [self._ica_to_json(), {'t': self.alignment}, self.rowspan,
                self.colspan, self.content.to_json()] class Caption(Element):
    """
    Table caption with optional short caption

    :param args: caption
    :type args: :class:`Block`
    :param short_caption: Short caption
    :type short_caption: :class:`list` of :class:`Inline`
    :param identifier: element identifier (usually unique)
    :Base: :class:`Element`
     """

    __slots__ = ['_content', '_short_caption']
    _children = ['content', 'short_caption']

    def __init__(self, *args, short_caption=None):
        self._set_content(args, Block)
        self.short_caption = short_caption

    def to_json(self):
        short_caption = None if self.short_caption is None else self.short_caption.to_json()
        return [short_caption, self.content.to_json()]

    @property
    def short_caption(self):
        return self._short_caption

    @short_caption.setter
    def short_caption(self, value):
        if value:
            value = value.list if isinstance(value, ListContainer) else list(value)
            self._short_caption = ListContainer(*value, oktypes=Inline, parent=self)
            self._short_caption.location = 'short_caption'
        else:
            self._short_caption = None TABLE_ALIGNMENT = {'AlignLeft', 'AlignRight', 'AlignCenter', 'AlignDefault'} TABLE_WIDTH = {'ColWidthDefault'} def table_from_json(c):
    # Attr Caption [ColSpec] TableHead [TableBody] TableFoot
    ica = decode_ica(c[0])
    caption = Caption(*c[1][1], short_caption=c[1][0])
    colspec = c[2]
    head = TableHead(*map(row_from_json, c[3][1]), **decode_ica(c[3][0]))
    bodies = map(body_from_json, c[4])
    foot = TableFoot(*map(row_from_json, c[5][1]), **decode_ica(c[5][0]))
    return Table(*bodies, head=head, foot=foot, caption=caption, colspec=colspec, **ica)
from .utils import check_type, check_group, encode_dict, decode_ica, debug
from .utils import load_pandoc_version, load_pandoc_reader_options
from .containers import ListContainer, DictContainer
from .base import Element, Block, Inline, MetaValue
from .table_elements import (
    Table, TableHead, TableFoot, TableBody, TableRow, TableCell, Caption,
    TABLE_ALIGNMENT, TABLE_WIDTH, table_from_json)
"""
Classes corresponding to Pandoc elements

Notation:
- "ica" is shorthand for "identifier, classes, attributes"
"""

# ---------------------------
# Imports
# ---------------------------



# ---------------------------
# Special Root Class
# ---------------------------

# RUN ALL WITH PYCCO

class Doc(Element):
    """
    Pandoc document container.

    Besides the document, it includes the frontpage metadata and the
    desired output format. Filter functions can also add properties to it
    as means of global variables that can later be read by different calls.

    :param args: top--level documents contained in the document
    :type args: :class:`Block` sequence
    :param metadata: the frontpage metadata
    :type metadata: ``dict``
    :param format: output format, such as 'markdown', 'latex' and 'html'
    :type format: ``str``
    :param api_version: A tuple of three ints of the form (1, 18, 0)
    :type api_version: ``tuple``
    :return: Document with base class :class:`Element`
    :Base: :class:`Element`

    :Example:

        >>> meta = {'author':'John Doe'}
        >>> content = [Header(Str('Title')), Para(Str('Hello!'))]
        >>> doc = Doc(*content, metadata=meta, format='pdf')
        >>> doc.figure_count = 0 #  You can add attributes freely
    """

    _children = ['metadata', 'content']

    def __init__(self, *args, metadata={}, format='html', api_version=(1, 22)):
        self._set_content(args, Block)
        self.metadata = metadata
        self.format = format  # Output format

        try:
            self.api_version = tuple(check_type(v, int) for v in api_version)
        except TypeError:
            raise TypeError("invalid api version; using an older version of Pandoc?")
        if len(self.api_version) > 4 or self.api_version[:2] < (1, 22):
            raise TypeError("invalid api version", api_version)


        self.pandoc_version = load_pandoc_version()