Source code for pyxml2pdf.core.rows

"""This module contains the base class to create table rows

Specifically it contains a class :class:`XMLCell` for unified styled cells and
a class :class:`XMLRow` for xml extracted data.
"""
from typing import cast, List, Set, Type

import defusedxml  # type: ignore
from reportlab.lib.styles import StyleSheet1  # type: ignore
from reportlab.platypus import Paragraph, Table  # type: ignore

from pyxml2pdf.input.properties import (
    columns,
    filter_xmltag,
    identifier_xmltag,
)  # type: ignore
from pyxml2pdf.styles.table_styles import XMLTableStyle
from pyxml2pdf.tables.builder import TableBuilder

# Monkeypatch standard library xml vulnerabilities.
defusedxml.defuse_stdlib()
from xml.etree.ElementTree import Element

__all__ = ["XMLCell", "XMLRow"]


[docs]class XMLCell(Paragraph): """This class represents the text of type reportlab.platypus.Paragraph in a cell It inherits from :class:`reportlab.platypus.Paragraph` and ensures the unified styling of all table cells. :py:class:`reportlab.platypus.Paragraph` is solely used with one certain style, which is supposed to be set as a class attribute during runtime. :param str text: the text to write into row """ _style: StyleSheet1 def __init__(self, text: str): super().__init__(text, self.style) @property def style(self) -> StyleSheet1: """StyleSheet1: The one for all stylesheet to style all cells.""" return self._style @style.setter def style(self, value: StyleSheet1): self._style = value
[docs]class XMLRow(Element): """A wrapper class for :py:class:`xml.etree.ElementTree.Element` :py:class:`xml.etree.ElementTree.Element` is augmented with the table row representation and the attributes and methods to manipulate everything according to the final tables needs. A :py:class:`XMLRow` can only be initialized with an object of type :py:class:`xml.etree.ElementTree.Element`. :param xml.etree.ElementTree.Element element: the element to build the instance from """ _table_builder: TableBuilder = TableBuilder() _table_style: XMLTableStyle = XMLTableStyle() _criteria: Set[str] _identifier: str _cell_styler: Type[XMLCell] = XMLCell def __init__(self, element): # Call Element constructor and extend ourselves by extending all children # tags to create an underlying copy of element. super().__init__(element.tag, element.attrib) self.extend(list(element)) # Initialize needed objects especially for table creation. self._cell_styler.style = self._table_style.custom_styles["stylesheet"][ "Normal" ] # Initialize definitely needed instance variables. self._criteria = self._init_criteria() self._identifier = self._concatenate_tags_content(identifier_xmltag) self._mandatory_columns = self._init_full_row()
[docs] def _init_criteria(self): """Initialize the list of criteria from the according xml tag's content""" criteria: str = self._concatenate_tags_content([filter_xmltag]) return set(criteria.split(", "))
[docs] def _concatenate_tags_content( self, cell_tags: List[str], separator: str = " - " ) -> str: """Form one string from the texts of a set of XML tags's to fill a cell Form a string of the content for all desired XML tags by concatenating them together with a separator. This is especially necessary, since :py:mod:`reportlab.platypus.Paragraph` cannot handle `None`s as texts but handles as well the concatenation of XML tags' content, if `cell_tags` has more than one element. So we ensure the result to be at least an empty string. :param cell_tags: list of all tags for which the descriptive texts is wanted, even if it is just one :param separator: the separator in between the concatenated texts :returns: concatenated, separated texts of all tags for the current cell """ return separator.join( [cast(str, self.findtext(tag)) for tag in cell_tags if self.findtext(tag)] )
[docs] def _init_full_row(self) -> List[XMLCell]: """Initialize the single table row containing all information from the XML input Extract interesting information from specified row tag's subtags and connect them into a nicely formatted row of a table. :return: the columns of any table representation :rtype: List[XMLCell] """ table_columns = [ self._cell_styler(self._concatenate_tags_content(column.tag)) for column in columns ] self._full_row = self._table_builder.create_fixedwidth_table([table_columns]) return table_columns
[docs] def get_full_row(self, subtable_title: str = None) -> Table: """Return a table row with all the row's information This ensures, that in subclasses we can override this function and after handing over the full information, the reduced version with a reference to the subtable containing the full version can be created via a decorator. See :class:`Event` for an example implementation of this pattern. :param subtable_title: the title of the subtable in which the row will be integrated :returns: a table row with all the event's information """ return self._full_row
@property def criteria(self) -> Set[str]: """Return the event's criteria :returns: a list of the event's criteria :rtype: Set[str] """ return self._criteria @property def identifier(self) -> str: """Return the identifier of the event :returns: identifier :rtype: str """ return self._identifier
[docs] def get_table_row(self, subtable_title: str) -> Table: """Return the table row representation of the XML tag This is the API of :py:class:`XMLRow` for getting the table row representation of the event. It allows for reacting to the distribution of the XML tags content by creating a shorter version referencing the main subtable. See :meth:`get_full_row` for details. :param str subtable_title: the title of the subtable in which the row will be integrated :returns: a table row representation of the XML tag's content :rtype: Table """ # We check if the reduced row was produced before, which means in turn, # that :meth:`get_table_row` was called at least once before. Otherwise we call # :meth:`get_full_row` which automatically triggers the creation of the # reduced row for later uses. try: return self._reduced_row except AttributeError: return self.get_full_row(subtable_title)