Skip to content
Snippets Groups Projects
Commit bad0b2fa authored by Daniel Hornung's avatar Daniel Hornung
Browse files

ENH: XLSX table template generator works with multiple choice arrays

parent ba557b27
No related branches found
No related tags found
2 merge requests!107Release v0.11.0,!98Multiple choice arrays for table json converter
Pipeline #49945 passed with warnings
......@@ -42,7 +42,8 @@ class ColumnType(Enum):
SCALAR = 1
LIST = 2
FOREIGN = 3
IGNORE = 4
MULTIPLE_CHOICE = 4
IGNORE = 5
class RowType(Enum):
......@@ -189,15 +190,16 @@ class TableTemplateGenerator(ABC):
# if it is an array, value defs are in 'items'
if schema.get('type') == 'array':
if (schema['items'].get('type') == 'object'
and len(path) > 1): # list of references; special treatment
items = schema['items']
# list of references; special treatment
if (items.get('type') == 'object' and len(path) > 1):
# we add a new sheet with columns generated from the subtree of the schema
sheetname = p2s(path)
if sheetname in sheets:
raise ValueError("The schema would lead to two sheets with the same name, "
f"which is forbidden: {sheetname}")
col_def = self._treat_schema_element(
schema=schema['items'], sheets=sheets, path=path, foreign_keys=foreign_keys,
schema=items, sheets=sheets, path=path, foreign_keys=foreign_keys,
level_in_sheet_name=len(path),
array_paths=array_paths+[path] # since this level is an array extend the list
)
......@@ -223,8 +225,23 @@ class TableTemplateGenerator(ABC):
# current sheet.
return {}
# List of enums: represent as checkbox columns
if (schema.get("uniqueItems") is True and "enum" in items and len(items) == 1):
choices = items["enum"]
assert len(path) >= 1
prop_name = path[-1]
result = {}
for choice in choices:
name = f"{prop_name}.{choice}"
result[name] = (
ColumnType.MULTIPLE_CHOICE,
schema.get('description'),
path + [str(choice)],
)
return result
# it is a list of primitive types -> semicolon separated list
schema = schema['items']
schema = items
ctype = ColumnType.LIST
# This should only be the case for "new or existing reference".
......@@ -247,9 +264,8 @@ class TableTemplateGenerator(ABC):
return cols
# The schema is a leaf.
description = schema['description'] if 'description' in schema else None
# definition of a single column
default_return = {p2s(path[level_in_sheet_name:]): (ctype, description, path)}
default_return = {p2s(path[level_in_sheet_name:]): (ctype, schema.get('description'), path)}
if 'type' not in schema and 'enum' in schema:
return default_return
if 'type' not in schema and 'anyOf' in schema:
......@@ -350,12 +366,12 @@ class XLSXTemplateGenerator(TableTemplateGenerator):
ordered_cols = self._get_ordered_cols(sheetdef)
# create other columns
for index, (colname, ct, desc, path) in enumerate(ordered_cols):
ws.cell(1, 2 + index, ct.name)
for index, (colname, coltype, desc, path) in enumerate(ordered_cols):
ws.cell(1, 2 + index, coltype.name)
for path_index, el in enumerate(path):
ws.cell(2 + path_index, 2 + index, el)
ws.cell(header_index, 2 + index, colname)
if ct == ColumnType.FOREIGN:
if coltype == ColumnType.FOREIGN:
# Visual highlighting
ws.cell(header_index, 2 + index).fill = yellowfill
if desc:
......
......@@ -46,13 +46,15 @@ XLSX file. These properties have an attribute name and a value. The value can be
a. A primitive (text, number, boolean, ...)
b. A record
c. A list of primitive types
d. A list of records
d. A list of unique enums (multiple choice)
e. A list of records
In cases *a.* and *c.*, a cell is created in the column corresponding to the property in the XLSX
file. In case *b.*, columns are created for the Properties of the record, where for each of the
Properties the cases *a.* - *d.* are considered recursively.
Properties the cases *a.* - *e.* are considered recursively. Case *d.* leads to a number of
columns, one for each of the possible choices.
For case *d.* however, the two-dimensional structure of an XLSX sheet is not sufficient. Therefore,
For case *e.* however, the two-dimensional structure of an XLSX sheet is not sufficient. Therefore,
for such cases, *new* XLSX sheets/tables are created.
In these sheets/tables, the referenced records are treated as described above (new columns for the
......@@ -124,7 +126,31 @@ This entry would be represented in an XLSX sheet with the following content:
The list elements are written into the cell separated by `;` (semicolon). If the elements contain
the separator `;`, it is escaped with `\\`.
### d. Properties containing lists with references ###
### d. Multiple choice properties ###
```JSON
{
"Training": {
"date": "2024-04-17",
"skills": [
"Planning",
"Evaluation"
]
}
}
```
If the `skills` list is denoted as an `enum` array with `"uniqueItems": true` in the json schema,
this entry would be represented like this in an XLSX:
| date | skills.Planning | skills.Communication | skills.Evaluation |
|------------|-----------------|----------------------|-------------------|
| 2024-04-17 | x | | x |
Note that this example assumes that the list of possible choices, as given in the json schema, was
"Planning, Communication, Evaluation".
### e. Properties containing lists with references ###
```JSON
{
......@@ -176,7 +202,8 @@ special treatment. The following values are used:
- ``IGNORE``: This row is ignored. It can be used for explanatory texts or layout.
- ``COL_TYPE``: Typically the first row that is not `IGNORE`. It indicates the row that defines the
type of columns (`FOREIGN`, `SCALAR`, `LIST`, `IGNORE`). This row may occur only once.
type of columns (`FOREIGN`, `SCALAR`, `LIST`, `MULTIPLE_CHOICE`, `IGNORE`). This row must occur
exactly once per sheet.
- ``PATH``: Indicates that the row is used to define the path within the JSON. These rows are
typically hidden for users.
......
{
"type": "object",
"properties": {
"Training": {
"type": "object",
"required": [],
"additionalProperties": false,
"title": "Training",
"properties": {
"name": {
"type": "string",
"description": "The name of the Record to be created"
},
"date": {
"description": "The date of the training.",
"anyOf": [
{
"type": "string",
"format": "date"
},
{
"type": "string",
"format": "date-time"
}
]
},
"skills": {
"description": "Skills that are trained.",
"type": "array",
"items": {
"enum": [
"Planning",
"Communication",
"Evaluation"
]
},
"uniqueItems": true
}
}
}
},
"required": [
"Training"
],
"additionalProperties": false,
"$schema": "https://json-schema.org/draft/2020-12/schema"
}
File added
......@@ -267,6 +267,12 @@ def test_model_with_indirect_reference():
foreign_keys={"Wrapper": ["Training.name", "Training.url"]},
outfile=None)
def test_model_with_multiple_choice():
_compare_generated_to_known_good(
schema_file=rfp("data/multiple_choice_schema.json"),
known_good=rfp("data/multiple_choice_template.xlsx"),
outfile=None)
def test_exceptions():
# Foreign keys must be lists
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment