recipe_grid.renderer
: Rendering recipes as tables¶
Recipes are rendered from their recipe data model descriptions
(recipe_grid.recipe
) into tabular form for display.
Table rendering is split into two parts. First an abstract tabular description is generated and then secondly this is rendered into its final form (i.e. HTML). This decomposition should make it possible for future non-HTML output formats to be supported.
The abstract table representation is defined in
recipe_grid.renderer.table
, the recipe-to-table conversion in
recipe_grid.renderer.recipe_to_table
and finally, table-to-HTML
conversion in recipe_grid.renderer.html
recipe_grid.renderer.table
: Abstract table description¶
The following output-agnostic data structure is used to represent a recipe in tabular form.
The basic data structure consists of a Table
object which contains
a set of nested lists containing a 2D array of Cell
and
ExtendedCell
objects. Each entry in the array represents a cell in
the table.
Where a cell spans multiple rows or columns, the top-left cell is defined with
a Cell
instance (with Cell.rows
and
Cell.columns
set appropriately) and the occluded cells are filled
with ExtendedCell
instances. For convenience, the
Table.from_dict()
class method is provided which can automatically fill
in ExtendedCell
instances when only :py:class`Cell`s are given.
- class recipe_grid.renderer.table.Table(cells: Sequence[Sequence[recipe_grid.renderer.table.Cell[T] | recipe_grid.renderer.table.ExtendedCell[T]]])¶
- cells: Sequence[Sequence[Cell[T] | ExtendedCell[T]]]¶
The table cells. A dense 2D array indexed as
cells[row][column]
with spaces covered by extended cells being denoted usingExtendedCell
.
- classmethod from_dict(table_dict: Mapping[Tuple[int, int], Cell[T]]) Table[T] ¶
Construct a
Table
from a dictionary mapping (row, column) toCell
.
- class recipe_grid.renderer.table.Cell(value: ~T, rows: int = 1, columns: int = 1, border_left: recipe_grid.renderer.table.BorderType = <BorderType.normal: 2>, border_right: recipe_grid.renderer.table.BorderType = <BorderType.normal: 2>, border_top: recipe_grid.renderer.table.BorderType = <BorderType.normal: 2>, border_bottom: recipe_grid.renderer.table.BorderType = <BorderType.normal: 2>)¶
- value: T¶
The value contained in this cell.
- columns: int = 1¶
The number of columns (to the right) or rows (below) over which this cell extends.
- border_left: BorderType = 2¶
- border_right: BorderType = 2¶
- border_top: BorderType = 2¶
- border_bottom: BorderType = 2¶
The border styles for this cell.
- class recipe_grid.renderer.table.ExtendedCell(cell: Cell[T], drow: int, dcolumn: int)¶
Represents parts of a table which are filled not with a new cell but with the extension of an adjacent cell. (Used as a ‘dummy’ value in
Table
.)- dcolumn: int¶
The delta in coordinate from the cell this
ExtendedCell
resides.
Cells have four borders whose display styles are dictated by the border_*
attributes of the Cell
. The border styles are:
- class recipe_grid.renderer.table.BorderType(value)¶
An enumeration.
- none = 1¶
No border. Used only for cells which are to be rendered ‘outside’ the table.
- normal = 2¶
A normal border style which will surround most cells.
- sub_recipe = 3¶
A border drawn round a sub recipe; typically thicker than the normal border style.
recipe_grid.renderer.recipe_to_table
: Recipe
to Table
transformation¶
The following routine converts a single recipe tree
(RecipeTreeNode
) into the equivalent
Table
form. It should be used to convert each recipe tree in
Recipe.recipe_trees
into its own table in the
rendered output.
- recipe_grid.renderer.recipe_to_table.recipe_tree_to_table(recipe_tree: recipe_grid.recipe.RecipeTreeNode) recipe_grid.renderer.table.Table ¶
Convert a recipe tree into tabular form.
To display the resulting
Table
, cell contents should be rendered as indicated below.Firstly, the obvious cases:
Ingredient
is shown as the ingredient quantity and name etc.Reference
is shown as its label (and quantity/proportion etc.)Step
is shown as its description. The cells immediately to the left in the generated table will contain the inputs to this step.
Finally, cells containing a
SubRecipe
may appear in the table for one of two purposes:For single-output sub recipes, the cell containing the
SubRecipe
will be located above the cells containing the body of the sub recipe. This cell should be rendered in the style of a heading and containing the output name as the text.For multiple-output sub recipes, thec cell containing the
SubRecipe
will be immediately to the right of the cells defining the sub-recipe and should be rendered as a list all of the output names for the sub recipe, for example as a bulleted list.Note
This cell will have all but its left border style set to be
BorderType.none
to give the appearance of the output list being located out to the right of the rest of the table.
Unless otherwise stated, all cells will have all borders set to
BorderType.normal
with the exception of those borders of cells near the edges of a sub recipe block. These will have a style ofBorderType.sub_recipe
.Note
The
_root
argument is for internal use and must be left unspecified when called by external users. Internally, it indicates if the node passed to this function is the root node of its recipe tree.
recipe_grid.renderer.html
: HTML Table Renderer¶
This module implements Table
to HTML
conversion in the following routine:
- recipe_grid.renderer.html.render_recipe_tree(recipe_tree: RecipeTreeNode, id_prefix: str = 'sub-recipe-') str ¶
Render a recipe tree as a HTML table.
The
id_prefix
argument may be used to specify the prefix added to all anchor IDs used in this recipe tree. The same prefix must be specified when rendering every recipe tree within a series ofRecipe
blocks. When a single HTML page may contain multiple, separate recipes, different prefixes should be used for each to ensure links to not conflict.
In the generated HTML, the following CSS class names are used which should be styled accordingly using a suitable stylesheet.
The generated table is mostly self explnatory, though ingredients and
references may contain a <ul>
(with the CSS class
rg-quantity-conversions
) listing quantities using alternative units. This
list may be styled using CSS as a mouse-over hint (for example) or hidden
entirely, as required.
CSS Classes¶
In the generated HTML, the following CSS class names are used
Top level (
<table>
) classes:rg-table
Applied to each generated
<table>
.
Cell (
<td>
) semantic classesrg-ingredient
Applied to each cell containing an ingredient.
rg-reference
Applied to each cell containing an reference.
rg-step
Applied to each cell containing a step description.
rg-sub-recipe-header
Applied to each cell acting as a header for a sub recipe.
rg-sub-recipe-outputs
Applied to the cell on the right-hand-end of a table representing a sub recipe with multiple outputs.
Cell (
<td>
) border styling classesrg-border-top-none
,rg-border-bottom-none
,rg-border-left-none
,rg-border-right-none
Indicates the respective border should be omitted.
rg-border-top-sub-recipe
,rg-border-bottom-sub-recipe
,rg-border-left-sub-recipe
,rg-border-right-sub-recipe
Indicates the respective border should be drawn with an emphasised (e.g. bold) stroke.
Where the above classes are given, they are given for both ‘sides’ of the border (e.g. when
rg-border-left-none
is used in one cell, the cell immediately to its left always has the matching classrg-border-right-none
).When not overridden by the classes above, all other cell borders should be solid (and the
<table>
should have no border at all), and inter-cell spacing should be collapsed.Inline styles
rg-quantity-unitless
Applied to the
<span>
surrounding the number in a unitless quantity. For example the “3” in “3 eggs”.rg-quantity-with-conversions
Applied to the
<span>
surrounding the number and unit and list of unit conversions in a quantity. For example the “1 tsp <ul>…</ul>” in “1 tsp <ul>…</ul> of salt”.rg-quantity-without-conversions
Applied to the
<span>
surrounding the number and unit in a quantity where no unit conversions are given. For example the “1 sack” in “1 sack of potatoes”.rg-quantity-conversions
Applied to lists (
<ul>>
) of alternative-unit quantity values.rg-proportion
Applied to the
<span>
surrounding the proportion in a reference. For example in “1/2 of the sauce”, this would surround the “1/2 of the” part.Note
The number inside will additionally be enclosed in a
<span>
with the classrg-scaled-value
.rg-proportion-remainder
Applied to the
<span>
surrounding the proportion indicating a remainder in reference. For example in “remainder of the sauce”, this would surround the “remainder of the” part.rg-sub-recipe-output-list
Applies to the list (
ul
) of output names in a cell containing a sub recipe with multiple outputs.rg-scaled-value
Applies to the
<span>
wrapping all strings containing scaled values in the recipe. For example, ingredient quantities or text wrapped in{
and}
in the recipe source.
Example CSS stylesheet:¶
A sample stylesheet (actually, the one used by the Recipe Grid static site generator) is given below:
/**
* CSS For styling recipe grid tables, and nothing else.
*/
/**
* Colour definitions.
*
* Warning: There are a couple of SVGs in data URLs below which unfortunately
* have these colours hard-coded and so need to be changed. Search for
* 'data:image/svg+xml;utf8' in your editor...
*/
:root {
--rg-table-normal-colour: var(--normal-colour, #000000);
--rg-table-accent-colour: var(--accent-colour, #FE5E41);
--rg-table-inverted-fg-colour: var(--inverted-fg-colour,#FFFFFF);
}
/**
* General table layout
*/
table.rg-table {
margin-top: 8px;
margin-bottom: 8px;
border-spacing : 0;
border-collapse : collapse;
}
table.rg-table tr td {
text-align : left;
vertical-align: middle;
margin : 0;
padding : 0;
padding-left : 10px;
padding-right : 10px;
padding-top : 3px;
padding-bottom: 3px;
border-style : solid;
border-width : 1px;
border-color : var(--rg-table-normal-colour);
}
/**
* Border specifying classes.
*/
table.rg-table tr td.rg-border-right-none { border-right-style: none; }
table.rg-table tr td.rg-border-left-none { border-left-style: none; }
table.rg-table tr td.rg-border-top-none { border-top-style: none; }
table.rg-table tr td.rg-border-bottom-none { border-bottom-style: none; }
table.rg-table tr td.rg-border-right-sub-recipe { border-right-width: 3px; }
table.rg-table tr td.rg-border-left-sub-recipe { border-left-width: 3px; }
table.rg-table tr td.rg-border-top-sub-recipe { border-top-width: 3px; }
table.rg-table tr td.rg-border-bottom-sub-recipe { border-bottom-width: 3px; }
/**
* Specialisations for cell types (e.g. ingredient or sub recipe header)
*/
table.rg-table tr td.rg-ingredient {
font-weight: 600;
}
table.rg-table tr td.rg-reference {
font-style: italic;
}
table.rg-table tr td.rg-reference a:before {
content: "\2196";
}
table.rg-table tr td.rg-sub-recipe-header {
background-color: var(--rg-table-normal-colour);
color: var(--rg-table-inverted-fg-colour);
border-color: var(--rg-table-normal-colour);
border-top: none;
}
table.rg-table tr td.rg-sub-recipe-outputs {
padding: 0;
}
table.rg-table tr td.rg-sub-recipe-outputs ul {
display: flex;
flex-direction: column;
align-items: start;
justify-content: center;
padding: 0;
margin: 0;
}
table.rg-table tr td.rg-sub-recipe-outputs ul li {
display: block;
font-weight: bold;
margin: 0;
padding-left : 32px;
padding-right : 8px;
padding-top : 3px;
padding-bottom: 3px;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
background-repeat: no-repeat;
background-position-x: left;
background-position-y: center;
/* NB: The fill colour in this image should be modified to be
* the same as --rg-table-normal-colour */
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="12" viewBox="0 0 6.35 3.175"><path d="M4.058 0v1.191H0v.794h4.058v1.19L6.35 1.588 4.058 0z" fill="%23000000"/></svg>');
}
/**
* Unit conversion popups
*/
.rg-quantity-with-conversions ul.rg-quantity-conversions {
position: absolute;
z-index: 999;
margin: -8px;
margin-top: 4px;
padding: 8px;
background-color: white;
border-style: solid;
border-width: 2px;
border-color: black;
border-radius: 4px;
filter: drop-shadow(4px 4px 8px #00000033);
list-style: none;
text-align: left;
}
/* Draw an arrow pointing up at the value. */
.rg-quantity-with-conversions ul.rg-quantity-conversions:before {
content: "";
display: block;
position: absolute;
top: -16px;
box-sizing: content-box;
width: 0;
height: 0;
border-style: solid;
border-color: black;
border-width: 8px;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
}
.rg-quantity-with-conversions ul.rg-quantity-conversions li {
padding-bottom: 8px;
}
.rg-quantity-with-conversions ul.rg-quantity-conversions li:last-child {
padding-bottom: 0;
}
/* Show on unit conversions on mouseover/focus */
.rg-quantity-with-conversions:focus {
outline: none;
}
.rg-quantity-with-conversions .rg-quantity-conversions {
visibility: hidden;
}
.rg-quantity-with-conversions:hover .rg-quantity-conversions,
.rg-quantity-with-conversions:focus-within .rg-quantity-conversions {
visibility: visible;
pointer-events: auto;
}
.rg-quantity-with-conversions:hover .rg-quantity-conversions {
z-index: 1000;
pointer-events: none;
}
/* Hide conversions in print */
@media print {
.rg-quantity-with-conversions .rg-quantity-conversions {
display: none;
}
}
/**
* Apply accent colouring when a table cell is targeted.
*/
table.rg-table:target {
border-color : var(--rg-table-accent-colour);
}
table.rg-table:target tr td.rg-border-right-sub-recipe {
border-right-color: var(--rg-table-accent-colour);
}
table.rg-table:target tr td.rg-border-left-sub-recipe {
border-left-color: var(--rg-table-accent-colour);
}
table.rg-table:target tr td.rg-border-top-sub-recipe {
border-top-color: var(--rg-table-accent-colour);
}
table.rg-table:target tr td.rg-border-bottom-sub-recipe {
border-bottom-color: var(--rg-table-accent-colour);
}
table.rg-table:target tr td.rg-sub-recipe-header {
background-color: var(--rg-table-accent-colour);
color: var(--rg-table-inverted-fg-colour);
border-color: var(--rg-table-accent-colour);
}
table.rg-table tr td.rg-sub-recipe-outputs ul li:target {
color: var(--rg-table-inverted-fg-colour);
background-color: var(--rg-table-accent-colour);
/* NB: The fill colour in this image should be modified to be
* the same as --rg-table-inverted-fg-colour */
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="12" viewBox="0 0 6.35 3.175"><path d="M4.058 0v1.191H0v.794h4.058v1.19L6.35 1.588 4.058 0z" fill="%23FFFFFF"/></svg>');
}