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
Tablefrom 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
ExtendedCellresides.
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(*values)¶
- 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:
Ingredientis shown as the ingredient quantity and name etc.Referenceis shown as its label (and quantity/proportion etc.)Stepis 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
SubRecipemay appear in the table for one of two purposes:For single-output sub recipes, the cell containing the
SubRecipewill 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
SubRecipewill 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.noneto 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.normalwith 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
_rootargument 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_prefixargument 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 ofRecipeblocks. 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-tableApplied to each generated
<table>.
Cell (
<td>) semantic classesrg-ingredientApplied to each cell containing an ingredient.
rg-referenceApplied to each cell containing an reference.
rg-stepApplied to each cell containing a step description.
rg-sub-recipe-headerApplied to each cell acting as a header for a sub recipe.
rg-sub-recipe-outputsApplied 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-noneIndicates 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-recipeIndicates 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-noneis 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-unitlessApplied to the
<span>surrounding the number in a unitless quantity. For example the “3” in “3 eggs”.rg-quantity-with-conversionsApplied 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-conversionsApplied 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-conversionsApplied to lists (
<ul>>) of alternative-unit quantity values.rg-proportionApplied 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-remainderApplied 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-listApplies to the list (
ul) of output names in a cell containing a sub recipe with multiple outputs.rg-scaled-valueApplies 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>');
}