From e8e05e4d7fcbf8830ae5ef71c3c148fc13c01485 Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum <39048939+jnussbaum@users.noreply.github.com> Date: Wed, 9 Nov 2022 09:13:59 +0100 Subject: [PATCH] feat: add command excel2json to create JSON project file from folder with Excel files (DEV-960) (#248) --- Makefile | 6 +- .../lists/de.xlsx | Bin .../lists/en.xlsx | Bin .../onto_name (onto_label)/properties.xlsx} | Bin .../onto_name (onto_label)/resources.xlsx} | Bin docs/dsp-tools-create.md | 3 +- ...tools-excel.md => dsp-tools-excel2json.md} | 96 +- docs/dsp-tools-excel2xml.md | 12 +- docs/dsp-tools-usage.md | 99 +- docs/index.md | 2 + knora/dsp_tools.py | 35 +- knora/dsplib/utils/excel_to_json_lists.py | 19 +- knora/dsplib/utils/excel_to_json_project.py | 111 ++ .../dsplib/utils/excel_to_json_properties.py | 18 +- knora/dsplib/utils/excel_to_json_resources.py | 14 +- mkdocs.yml | 4 +- test/e2e/test_tools.py | 29 +- .../test_excel_to_json_properties.py | 9 +- .../unittests/test_excel_to_json_resources.py | 8 +- testdata/Properties.xlsx | Bin 13688 -> 0 bytes testdata/Resources.xlsx | Bin 23090 -> 0 bytes testdata/excel2json-expected-output.json | 1078 +++++++++++++++++ testdata/excel2json_files/lists/de.xlsx | Bin 0 -> 9679 bytes testdata/excel2json_files/lists/en.xlsx | Bin 0 -> 9552 bytes testdata/excel2json_files/lists/fr.xlsx | Bin 0 -> 9554 bytes .../test-name (test_label)/properties.xlsx | Bin 0 -> 13662 bytes .../test-name (test_label)/resources.xlsx | Bin 0 -> 23082 bytes 27 files changed, 1403 insertions(+), 140 deletions(-) rename docs/assets/{templates => data_model_templates}/lists/de.xlsx (100%) rename docs/assets/{templates => data_model_templates}/lists/en.xlsx (100%) rename docs/assets/{templates/properties_template.xlsx => data_model_templates/onto_name (onto_label)/properties.xlsx} (100%) rename docs/assets/{templates/resources_template.xlsx => data_model_templates/onto_name (onto_label)/resources.xlsx} (100%) rename docs/{dsp-tools-excel.md => dsp-tools-excel2json.md} (75%) create mode 100644 knora/dsplib/utils/excel_to_json_project.py delete mode 100644 testdata/Properties.xlsx delete mode 100644 testdata/Resources.xlsx create mode 100644 testdata/excel2json-expected-output.json create mode 100644 testdata/excel2json_files/lists/de.xlsx create mode 100644 testdata/excel2json_files/lists/en.xlsx create mode 100644 testdata/excel2json_files/lists/fr.xlsx create mode 100644 testdata/excel2json_files/test-name (test_label)/properties.xlsx create mode 100644 testdata/excel2json_files/test-name (test_label)/resources.xlsx diff --git a/Makefile b/Makefile index 6eaca66be..6a7f5f829 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ CURRENT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) .PHONY: dsp-stack dsp-stack: ## clone the dsp-api git repository and run the dsp-stack @mkdir -p .tmp - @git clone --branch main --single-branch --depth 1 https://github.com/dasch-swiss/dsp-api.git .tmp/dsp-stack + @git clone --branch v24.0.8 --single-branch https://github.com/dasch-swiss/dsp-api.git .tmp/dsp-stack $(MAKE) -C .tmp/dsp-stack env-file $(MAKE) -C .tmp/dsp-stack init-db-test $(MAKE) -C .tmp/dsp-stack stack-up @@ -51,7 +51,7 @@ install: ## install from source (runs setup.py) .PHONY: test test: dsp-stack ## run all tests located in the "test" folder (intended for local usage) - -pytest test/ + -pytest test/ # ignore errors, continue anyway with stack-down $(MAKE) stack-down .PHONY: test-no-stack @@ -60,7 +60,7 @@ test-no-stack: ## run all tests located in the "test" folder, without starting t .PHONY: test-end-to-end test-end-to-end: dsp-stack ## run e2e tests (intended for local usage) - -pytest test/e2e/ + -pytest test/e2e/ # ignore errors, continue anyway with stack-down $(MAKE) stack-down .PHONY: test-end-to-end-ci diff --git a/docs/assets/templates/lists/de.xlsx b/docs/assets/data_model_templates/lists/de.xlsx similarity index 100% rename from docs/assets/templates/lists/de.xlsx rename to docs/assets/data_model_templates/lists/de.xlsx diff --git a/docs/assets/templates/lists/en.xlsx b/docs/assets/data_model_templates/lists/en.xlsx similarity index 100% rename from docs/assets/templates/lists/en.xlsx rename to docs/assets/data_model_templates/lists/en.xlsx diff --git a/docs/assets/templates/properties_template.xlsx b/docs/assets/data_model_templates/onto_name (onto_label)/properties.xlsx similarity index 100% rename from docs/assets/templates/properties_template.xlsx rename to docs/assets/data_model_templates/onto_name (onto_label)/properties.xlsx diff --git a/docs/assets/templates/resources_template.xlsx b/docs/assets/data_model_templates/onto_name (onto_label)/resources.xlsx similarity index 100% rename from docs/assets/templates/resources_template.xlsx rename to docs/assets/data_model_templates/onto_name (onto_label)/resources.xlsx diff --git a/docs/dsp-tools-create.md b/docs/dsp-tools-create.md index 4d99d46e2..a82ebf8f3 100644 --- a/docs/dsp-tools-create.md +++ b/docs/dsp-tools-create.md @@ -437,8 +437,7 @@ To do so, it would be necessary to place the following two files into the folder ![Colors_en](./assets/images/img-list-english-colors.png) ![Farben_de](./assets/images/img-list-german-colors.png) -The expected format of the Excel files is documented -[here](./dsp-tools-excel.md#create-the-lists-section-of-a-json-project-file-from-excel-files). The only difference to +The expected format of the Excel files is documented [here](./dsp-tools-excel2json.md#lists-section). The only difference to the explanations there is that column A of the Excel worksheet is not interpreted as list name (root node), but as node name of the first children level below the root node. diff --git a/docs/dsp-tools-excel.md b/docs/dsp-tools-excel2json.md similarity index 75% rename from docs/dsp-tools-excel.md rename to docs/dsp-tools-excel2json.md index d162dca12..e019f77a7 100644 --- a/docs/dsp-tools-excel.md +++ b/docs/dsp-tools-excel2json.md @@ -1,22 +1,62 @@ [![PyPI version](https://badge.fury.io/py/dsp-tools.svg)](https://badge.fury.io/py/dsp-tools) -# Excel files for data modelling and data import +# `excel2json`: Create a data model (JSON project file) from Excel -dsp-tools is able to process Excel files and output the appropriate JSON or XML file. The JSON/XML file can then be -used to create the ontology on the DSP server or import data to the DSP repository. dsp-tools can also be used to -create a list from an Excel file. +With dsp-tools, a JSON project file can be created from Excel files. The command for this is documented +[here](./dsp-tools-usage.md#create-a-json-project-file-from-excel-files). +A JSON project consists of + - 0-1 "lists" sections + - 1-n ontologies, each containing + - 1 "properties" section + - 1 "resources" section +For each of these 3 sections, one or several Excel files are necessary. The Excel files and their format are described +below. If you want to convert the Excel files to JSON, it is possible to invoke a command for each of these sections +separately (as described below). -## JSON project file: "resources" section from Excel file +But it is more convenient to use the command that creates the entire JSON project file. In order to do so, put all +involved files into a folder with the following structure: +``` +data_model_files +|-- lists +| |-- de.xlsx +| `-- en.xlsx +`-- onto_name (onto_label) + |-- properties.xlsx + `-- resources.xlsx +``` + +Conventions for the folder names: + + - The "lists" folder must have exactly this name, if it exists. It can also be omitted. + - Replace "onto_name" by your ontology's name, and "onto_label" by your ontology's label. + - The only name that can be chosen freely is the name of the topmost folder ("data_model_files" in this example). + +Then, use the following command: +``` +dsp-tools excel2json data_model_files project.json +``` + +This will create a file `project.json` with the lists, properties, and resources from the Excel files. + +Please note that the "header" of the resulting JSON file is empty and thus invalid. It is necessary to add the project +shortcode, name, description, keywords, etc. by hand. + +Continue reading the following paragraphs to learn more about the expected structure of the Excel files. + + + + +## "resources" section With dsp-tools, the `resources` section used in a data model (JSON) can be created from an Excel file. The command for this is documented [here](./dsp-tools-usage.md#create-the-resources-section-of-a-json-project-file-from-an-excel-file). Only `XLSX` files are allowed. The `resources` section can be inserted into the ontology file and then be uploaded onto a DSP server. -**An Excel file template can be found [here](assets/templates/resources_template.xlsx). It is recommended to work from +**An Excel file template can be found [here](assets/data_model_templates/onto_name (onto_label)/resources.xlsx). It is recommended to work from the template.** The expected worksheets of the Excel file are: @@ -51,14 +91,14 @@ For further information about resources, see [here](./dsp-tools-create-ontologie -## JSON project file: "properties" section from Excel file +## "properties" section With dsp-tools, the `properties` section used in a data model (JSON) can be created from an Excel file. The command for this is documented [here](./dsp-tools-usage.md#create-the-properties-section-of-a-json-project-file-from-an-excel-file). Only the first worksheet of the Excel file is considered and only XLSX files are allowed. The `properties` section can be inserted into the ontology file and then be uploaded onto a DSP server. -**An Excel file template can be found [here](assets/templates/properties_template.xlsx). It is recommended to work +**An Excel file template can be found [here](assets/data_model_templates/onto_name (onto_label)/properties.xlsx). It is recommended to work from the template.** The Excel sheet must have the following structure: @@ -84,7 +124,7 @@ For further information about properties, see [here](./dsp-tools-create-ontologi -## JSON project file: "lists" section from Excel file(s) +## "lists" section With dsp-tools, the "lists" section of a JSON project file can be created from one or several Excel files. The lists can then be inserted into a JSON project file and uploaded to a DSP server. The command for this is documented @@ -116,8 +156,8 @@ Some notes: printed out if the list is not valid. **It is recommended to work from the following templates: -[en.xlsx](assets/templates/lists/en.xlsx): File with the English labels -[de.xlsx](assets/templates/lists/de.xlsx): File with the German labels** +[en.xlsx](assets/data_model_templates/lists/en.xlsx): File with the English labels +[de.xlsx](assets/data_model_templates/lists/de.xlsx): File with the German labels** The output of the above command, with the template files, is: @@ -190,37 +230,3 @@ The output of the above command, with the template files, is: ] } ``` - - - -## XML data file from Excel/CSV file - -There are two use cases for a transformation from Excel/CSV to XML: - - - The CLI command `dsp-tools excel2xml` creates an XML file from an Excel/CSV file which is already structured - according to the DSP specifications. This is mostly used for DaSCH-interal data migration. - - The module `excel2xml` can be imported into a custom Python script that transforms any tabular data into an XML. This - use case is more frequent, because data from research projects have a variety of formats/structures. The module - `excel2xml` is documented [here](./dsp-tools-excel2xml.md). - - -### CLI command `excel2xml` - -The command line tool is used as follows: -```bash -dsp-tools excel2xml data-source.xlsx 1234 shortname -``` - -There are no flags/options for this command. - -The Excel file must be structured as in this image: -![img-excel2xml.png](assets/images/img-excel2xml.png) - -Some notes: - - - The special tags ``, ``, and `` are represented as resources of restype `Annotation`, -`LinkObj`, and `Region`. - - The columns "ark", "iri", and "creation_date" are only used for DaSCH-internal data migration. - - If `file` is provided, but no `file permissions`, an attempt will be started to deduce them from the resource - permissions (`res-default` --> `prop-default` and `res-restricted` --> `prop-restricted`). If this attempt is not - successful, a `BaseError` will be raised. diff --git a/docs/dsp-tools-excel2xml.md b/docs/dsp-tools-excel2xml.md index 790da569f..9934b0948 100644 --- a/docs/dsp-tools-excel2xml.md +++ b/docs/dsp-tools-excel2xml.md @@ -1,11 +1,13 @@ [![PyPI version](https://badge.fury.io/py/dsp-tools.svg)](https://badge.fury.io/py/dsp-tools) -# `excel2xml`: Convert a data source to XML -dsp-tools assists you in converting a data source in CSV/XLS(X) format to an XML file. +# Module `excel2xml`: Convert a data source to XML -| **Hint** | -|-------------------------------------------------------------------------------------------------------------------------------------------| -| This page is about the **module** `excel2xml`. The CLI command is documented [here](dsp-tools-excel.md#xml-data-file-from-excelcsv-file). | +This page is about the module `excel2xml` that can be imported into a custom Python script that transforms any tabular +data into an XML. + +There is also a CLI command `dsp-tools excel2xml` that creates an XML file from an Excel/CSV file which is already +structured according to the DSP specifications. The CLI command is documented +[here](./dsp-tools-usage.md#use-the-module-excel2xml-to-convert-a-data-source-to-xml). To demonstrate the usage of the `excel2xml` module, there is a GitHub repository named `0123-import-scripts`. It contains: diff --git a/docs/dsp-tools-usage.md b/docs/dsp-tools-usage.md index 473699c87..277e4a9aa 100644 --- a/docs/dsp-tools-usage.md +++ b/docs/dsp-tools-usage.md @@ -32,13 +32,13 @@ dsp-tools create [options] project_definition.json The following options are available: -- `-s` | `--server` _server_: URL of the DSP server (default: 0.0.0.0:3333) -- `-u` | `--user` _username_: username used for authentication with the DSP API (default: root@example.com) -- `-p` | `--password` _password_: password used for authentication with the DSP API (default: test) -- `-V` | `--validate-only`: If set, only the validation of the JSON file is performed. -- `-l` | `--lists-only`: If set, only the lists are created. Please note that in this case the project must already exist. -- `-v` | `--verbose`: If set, more information about the progress is printed to the console. -- `-d` | `--dump`: If set, dump test files for DSP-API requests. +- `-s` | `--server` (optional, default: `0.0.0.0:3333`): URL of the DSP server +- `-u` | `--user` (optional, default: `root@example.com`): username used for authentication with the DSP API +- `-p` | `--password` (optional, default: `test`): password used for authentication with the DSP API +- `-V` | `--validate-only` (optional): If set, only the validation of the JSON file is performed. +- `-l` | `--lists-only` (optional): If set, only the lists are created. Please note that in this case the project must already exist. +- `-v` | `--verbose` (optional): If set, more information about the progress is printed to the console. +- `-d` | `--dump` (optional): If set, dump test files for DSP-API requests. The command is used to read the definition of a project with its data model(s) (provided in a JSON file) and create it on the DSP server. The following example shows how to upload the project defined in `project_definition.json` to the DSP @@ -61,12 +61,12 @@ dsp-tools get [options] output_file.json The following options are available: -- `-s` | `--server`: URL of the DSP server (default: 0.0.0.0:3333) -- `-u` | `--user`: username used for authentication with the DSP API (default: root@example.com) -- `-p` | `--password`: password used for authentication with the DSP API (default: test) -- `-P` | `--project`: shortcode, shortname or - [IRI](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier) of the project (mandatory) -- `-v` | `--verbose`: If set, some information about the progress is printed to the console. +- `-s` | `--server` (optional, default: `0.0.0.0:3333`): URL of the DSP server +- `-u` | `--user` (optional, default: `root@example.com`): username used for authentication with the DSP API +- `-p` | `--password` (optional, default: `test`): password used for authentication with the DSP API +- `-P` | `--project` (mandatory): shortcode, shortname or + [IRI](https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier) of the project +- `-v` | `--verbose` (optional): If set, some information about the progress is printed to the console. The command is used to get the definition of a project with its data model(s) from a DSP server and write it into a JSON file. This JSON file can then be used to create the same project on another DSP server. The following example shows how @@ -131,21 +131,34 @@ to use this file to replace internal IDs in an existing XML file to reference ex -## Create the "lists" section of a JSON project file from Excel files +## Create a JSON project file from Excel files + +``` +dsp-tools excel2json data_model_files project.json +``` + +The expected file and folder structures are described [here](./dsp-tools-excel2json.md#json-project-file-from-excel). + + + + +### Create the "lists" section of a JSON project file from Excel files ```bash -dsp-tools excel2lists folder output.json +dsp-tools excel2lists [options] folder output.json ``` -Arguments: - - `folder` (optional, default: "lists"): folder with the Excel file(s) - - `output.json` (optional, default: "lists.json"): Output file +The following options are available: + +- `-v` | `--verbose` (optional): If set, more information about the progress is printed to the console. -The expected Excel format is [documented here](./dsp-tools-excel.md#create-the-lists-section-of-a-json-project-file-from-excel-files). +The expected Excel format is [documented here](./dsp-tools-excel2json.md#lists-section). +**Tip: The command [`excel2json`](#create-a-json-project-file-from-excel-files) might be more convenient to use.** -## Create the "resources" section of a JSON project file from an Excel file + +### Create the "resources" section of a JSON project file from an Excel file ```bash dsp-tools excel2resources excel_file.xlsx output_file.json @@ -154,20 +167,14 @@ dsp-tools excel2resources excel_file.xlsx output_file.json The command is used to create the resources section of an ontology from an Excel file. Therefore, an Excel file has to be provided with the data in the first worksheet of the Excel file. -The following example shows how to create the resources section from an Excel file called `Resources.xlsx`. The output -is written to a file called `resources.json`. - -```bash -dsp-tools excel2resources Resources.xlsx resources.json -``` +The expected Excel format is [documented here](./dsp-tools-excel2json.md#resources-section). -More information about the usage of this command can be -found [here](./dsp-tools-excel.md#create-the-resources-for-a-data-model-from-an-excel-file). +**Tip: The command [`excel2json`](#create-a-json-project-file-from-excel-files) might be more convenient to use.** -## Create the "properties" section of a JSON project file from an Excel file +### Create the "properties" section of a JSON project file from an Excel file ```bash dsp-tools excel2properties excel_file.xlsx output_file.json @@ -176,32 +183,38 @@ dsp-tools excel2properties excel_file.xlsx output_file.json The command is used to create the properties section of an ontology from an Excel file. Therefore, an Excel file has to be provided with the data in the first worksheet of the Excel file. -The following example shows how to create the properties section from an Excel file called `Properties.xlsx`. The output -is written to a file called `properties.json`. - -```bash -dsp-tools excel2properties Properties.xlsx properties.json -``` +The expected Excel format is [documented here](./dsp-tools-excel2json.md#properties-section). -More information about the usage of this command can be found -[here](./dsp-tools-excel.md#create-the-properties-for-a-data-model-from-an-excel-file). +**Tip: The command [`excel2json`](#create-a-json-project-file-from-excel-files) might be more convenient to use.** ## Create an XML file from Excel/CSV + +If your data source is already structured according to the DSP specifications, but it is not in XML format yet, the +command `excel2xml` will transform it into XML. This is mostly used for DaSCH-interal data migration. + ```bash dsp-tools excel2xml data-source.xlsx project_shortcode ontology_name ``` Arguments: - - data-source.xlsx: An Excel/CSV file that is structured according to [these requirements](dsp-tools-excel.md#cli-command-excel2xml) - - project_shortcode: The four-digit hexadecimal shortcode of the project - - ontology_name: the name of the ontology that the data belongs to + - data-source.xlsx (mandatory): An Excel/CSV file that is structured as explained below + - project_shortcode (mandatory): The four-digit hexadecimal shortcode of the project + - ontology_name (mandatory): the name of the ontology that the data belongs to -If your data source is already structured according to the DSP specifications, but it is not in XML format yet, the -command `excel2xml` will transform it into XML. This is mostly used for DaSCH-interal data migration. There are no -flags/options for this command. The details of this command are documented [here](dsp-tools-excel.md#cli-command-excel2xml). +The Excel file must be structured as in this image: +![img-excel2xml.png](assets/images/img-excel2xml.png) + +Some notes: + + - The special tags ``, ``, and `` are represented as resources of restype `Annotation`, +`LinkObj`, and `Region`. + - The columns "ark", "iri", and "creation_date" are only used for DaSCH-internal data migration. + - If `file` is provided, but no `file permissions`, an attempt will be started to deduce them from the resource + permissions (`res-default` --> `prop-default` and `res-restricted` --> `prop-restricted`). If this attempt is not + successful, a `BaseError` will be raised. If your data source is not yet structured according to the DSP specifications, you need a custom Python script for the data transformation. For this, you might want to import the module `excel2xml` into your Python script, which is diff --git a/docs/index.md b/docs/index.md index 7e7774c7f..517b323a4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,8 @@ dsp-tools helps you with the following tasks: a DSP server and writes it into a JSON file. - [`dsp-tools xmlupload`](./dsp-tools-usage.md#upload-data-to-a-dsp-server) uploads data from an XML file (bulk data import) and writes the mapping from internal IDs to IRIs into a local file. +- [`dsp-tools excel2json`](./dsp-tools-usage.md#create-a-json-project-file-from-excel-files) creates an entire JSON + project file from a folder with Excel files in it. - [`dsp-tools excel2lists`](./dsp-tools-usage.md#create-the-lists-section-of-a-json-project-file-from-excel-files) creates the "lists" section of a JSON project file from one or several Excel files. The resulting section can be integrated into a JSON project file and then be uploaded to a DSP server with `dsp-tools create`. diff --git a/knora/dsp_tools.py b/knora/dsp_tools.py index 8ffc37a97..0b68b7f7d 100644 --- a/knora/dsp_tools.py +++ b/knora/dsp_tools.py @@ -9,6 +9,7 @@ from importlib.metadata import version from knora.dsplib.utils.excel_to_json_lists import excel2lists, validate_lists_section_with_schema +from knora.dsplib.utils.excel_to_json_project import excel2json from knora.dsplib.utils.excel_to_json_properties import excel2properties from knora.dsplib.utils.excel_to_json_resources import excel2resources from knora.dsplib.utils.id_to_iri import id_to_iri @@ -89,7 +90,17 @@ def program(user_args: list[str]) -> None: parser_upload.add_argument('-I', '--incremental', action='store_true', help='Incremental XML upload') parser_upload.add_argument('xmlfile', help='path to xml file containing the data', default='data.xml') - # excel + # excel2json + parser_excel2json = subparsers.add_parser( + 'excel2json', + help='Create a JSON project file from a folder containing the required Excel files (lists folder, ' + 'properties.xlsx, resources.xlsx)' + ) + parser_excel2json.set_defaults(action='excel2json') + parser_excel2json.add_argument('data_model_files', help='Path to the folder containing the Excel files') + parser_excel2json.add_argument('outfile', help='Path to the output JSON file') + + # excel2lists parser_excel_lists = subparsers.add_parser( 'excel2lists', help='Create the "lists" section of a JSON project file from one or multiple Excel files. If the list should ' @@ -98,26 +109,22 @@ def program(user_args: list[str]) -> None: ) parser_excel_lists.set_defaults(action='excel2lists') parser_excel_lists.add_argument('excelfolder', help='Path to the folder containing the Excel file(s)') - parser_excel_lists.add_argument('outfile', help='Path to the output JSON file containing the "lists" section', - default='lists.json') + parser_excel_lists.add_argument('outfile', help='Path to the output JSON file containing the "lists" section') + parser_excel_lists.add_argument('-v', '--verbose', action='store_true', help=verbose_text) # excel2resources parser_excel_resources = subparsers.add_parser('excel2resources', help='Create a JSON file from an Excel file ' 'containing resources for a DSP ontology. ') parser_excel_resources.set_defaults(action='excel2resources') - parser_excel_resources.add_argument('excelfile', help='Path to the Excel file containing the resources', - default='resources.xlsx') - parser_excel_resources.add_argument('outfile', help='Path to the output JSON file containing the resource data', - default='resources.json') + parser_excel_resources.add_argument('excelfile', help='Path to the Excel file containing the resources') + parser_excel_resources.add_argument('outfile', help='Path to the output JSON file containing the resource data') # excel2properties parser_excel_properties = subparsers.add_parser('excel2properties', help='Create a JSON file from an Excel file ' 'containing properties for a DSP ontology. ') parser_excel_properties.set_defaults(action='excel2properties') - parser_excel_properties.add_argument('excelfile', help='Path to the Excel file containing the properties', - default='properties.xlsx') - parser_excel_properties.add_argument('outfile', help='Path to the output JSON file containing the properties data', - default='properties.json') + parser_excel_properties.add_argument('excelfile', help='Path to the Excel file containing the properties') + parser_excel_properties.add_argument('outfile', help='Path to the output JSON file containing the properties data') # id2iri parser_id2iri = subparsers.add_parser('id2iri', help='Replace internal IDs in an XML with their corresponding IRIs ' @@ -203,9 +210,13 @@ def program(user_args: list[str]) -> None: sipi=args.sipi, verbose=args.verbose, incremental=args.incremental) + elif args.action == 'excel2json': + excel2json(data_model_files=args.data_model_files, + path_to_output_file=args.outfile) elif args.action == 'excel2lists': excel2lists(excelfolder=args.excelfolder, - path_to_output_file=args.outfile) + path_to_output_file=args.outfile, + verbose=args.verbose) elif args.action == 'excel2resources': excel2resources(excelfile=args.excelfile, path_to_output_file=args.outfile) diff --git a/knora/dsplib/utils/excel_to_json_lists.py b/knora/dsplib/utils/excel_to_json_lists.py index f3b84ff00..f8531e54e 100644 --- a/knora/dsplib/utils/excel_to_json_lists.py +++ b/knora/dsplib/utils/excel_to_json_lists.py @@ -6,10 +6,10 @@ from typing import Any, Union, Optional, Tuple import jsonschema +import regex from openpyxl import load_workbook from openpyxl.cell import Cell from openpyxl.worksheet.worksheet import Worksheet -import regex from knora.dsplib.models.helpers import BaseError from knora.dsplib.utils.shared import simplify_name @@ -253,7 +253,8 @@ def validate_lists_section_with_schema( """ if bool(path_to_json_project_file) == bool(lists_section): raise BaseError("Validation of the 'lists' section works only if exactly one of the two arguments is given.") - with open("knora/dsplib/schemas/lists-only.json") as schema: + current_dir = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(current_dir, "../schemas/lists-only.json")) as schema: lists_schema = json.load(schema) if path_to_json_project_file: @@ -297,24 +298,30 @@ def _extract_excel_file_paths(excelfolder: str) -> list[str]: return excel_file_paths -def excel2lists(excelfolder: str, path_to_output_file: Optional[str] = None) -> list[dict[str, Any]]: +def excel2lists( + excelfolder: str, + path_to_output_file: Optional[str] = None, + verbose: bool = False +) -> list[dict[str, Any]]: """ Converts lists described in Excel files into a "lists" section that can be inserted into a JSON project file. Args: excelfolder: path to the folder containing the Excel file(s) path_to_output_file: if provided, the output is written into this JSON file + verbose: verbose switch Returns: the "lists" section as Python list """ # read the data excel_file_paths = _extract_excel_file_paths(excelfolder) - print("The following Excel files will be processed:") - [print(f" - {filename}") for filename in excel_file_paths] + if verbose: + print("The following Excel files will be processed:") + [print(f" - {filename}") for filename in excel_file_paths] # construct the "lists" section - finished_lists = _make_json_lists_from_excel(excel_file_paths, verbose=True) + finished_lists = _make_json_lists_from_excel(excel_file_paths, verbose=verbose) validate_lists_section_with_schema(lists_section=finished_lists) # write final "lists" section diff --git a/knora/dsplib/utils/excel_to_json_project.py b/knora/dsplib/utils/excel_to_json_project.py new file mode 100644 index 000000000..0fbbc3d8d --- /dev/null +++ b/knora/dsplib/utils/excel_to_json_project.py @@ -0,0 +1,111 @@ +import json +import os +import re + +from knora.dsplib.models.helpers import BaseError +from knora.dsplib.utils.excel_to_json_lists import excel2lists +from knora.dsplib.utils.excel_to_json_properties import excel2properties +from knora.dsplib.utils.excel_to_json_resources import excel2resources + + +def excel2json( + data_model_files: str, + path_to_output_file: str +) -> None: + """ + Converts a folder containing Excel files into a JSON data model file. The folder must be structured like this: + + :: + + data_model_files + |-- lists + | |-- de.xlsx + | `-- en.xlsx + `-- onto_name (onto_label) + |-- properties.xlsx + `-- resources.xlsx + + The names of the files must be exactly like in the example. The folder "lists" can be missing, because it is + optional to have lists in a DSP project. Only XLSX files are allowed. + + Args: + data_model_files: path to the folder (called "data_model_files" in the example) + path_to_output_file: path to the file where the output JSON file will be saved + + Returns: + None + """ + + # validate input + # -------------- + if not os.path.isdir(data_model_files): + raise BaseError(f"ERROR: {data_model_files} is not a directory.") + folder = [x for x in os.scandir(data_model_files) if not re.search(r"^(\.|~\$).+", x.name)] + + processed_files = [] + onto_folders = [x for x in folder if os.path.isdir(x) and re.search(r"([\w.-]+) (\([\w.\- ]+\))", x.name)] + if len(onto_folders) == 0: + raise BaseError(f"'{data_model_files}' must contain at least one subfolder named after the pattern " + f"'onto_name (onto_label)'") + for onto_folder in onto_folders: + contents = sorted([x.name for x in os.scandir(onto_folder) if not re.search(r"^(\.|~\$).+", x.name)]) + if contents != ["properties.xlsx", "resources.xlsx"]: + raise BaseError(f"ERROR: '{data_model_files}/{onto_folder.name}' must contain one file 'properties.xlsx' " + f"and one file 'resources.xlsx', but nothing else.") + processed_files.extend([f"{data_model_files}/{onto_folder.name}/{file}" for file in contents]) + + listfolder = [x for x in folder if os.path.isdir(x) and x.name == "lists"] + if listfolder: + listfolder_contents = list(os.scandir(listfolder[0])) + if not all([re.search(r"(de|en|fr|it|rm).xlsx", file.name) for file in listfolder_contents]): + raise BaseError(f"The only files allowed in '{data_model_files}/lists' are en.xlsx, de.xlsx, fr.xlsx, " + f"it.xlsx, rm.xlsx") + processed_files = [f"{data_model_files}/lists/{file.name}" for file in listfolder_contents] + processed_files + + if len(onto_folders) + len(listfolder) != len(folder): + raise BaseError(f"The only allowed subfolders in '{data_model_files}' are 'lists' and folders that match the " + f"pattern 'onto_name (onto_label)'") + + print(f"The following files will be processed:") + [print(f" - {file}") for file in processed_files] + + + # create output + # ------------- + lists = excel2lists(f"{data_model_files}/lists") if listfolder else None + + ontologies = [] + for onto_folder in onto_folders: + name, label = re.search(r"([\w.-]+) \(([\w.\- ]+)\)", onto_folder.name).groups() + ontologies.append({ + "name": name, + "label": label, + "properties": excel2properties(f"{data_model_files}/{onto_folder.name}/properties.xlsx"), + "resources": excel2resources(f"{data_model_files}/{onto_folder.name}/resources.xlsx") + }) + + project = { + "prefixes": { + "": "" + }, + "$schema": "https://raw.githubusercontent.com/dasch-swiss/dsp-tools/main/knora/dsplib/schemas/ontology.json", + "project": { + "shortcode": "", + "shortname": "", + "longname": "", + "descriptions": { + "en": "" + }, + "keywords": [ + "" + ] + } + } + if lists: + project["project"]["lists"] = lists + project["project"]["ontologies"] = ontologies + + with open(path_to_output_file, "w") as f: + json.dump(project, f, indent=4, ensure_ascii=False) + + print(f"JSON project file successfully saved at {path_to_output_file}") diff --git a/knora/dsplib/utils/excel_to_json_properties.py b/knora/dsplib/utils/excel_to_json_properties.py index 78b800457..3f1c87897 100644 --- a/knora/dsplib/utils/excel_to_json_properties.py +++ b/knora/dsplib/utils/excel_to_json_properties.py @@ -1,11 +1,13 @@ import json +import os import re from typing import Any, Optional + import jsonschema import pandas as pd from knora.dsplib.models.helpers import BaseError -from knora.dsplib.utils.shared import prepare_dataframe +from knora.dsplib.utils.shared import prepare_dataframe, check_notna languages = ["en", "de", "fr", "it", "rm"] @@ -20,7 +22,8 @@ def _validate_properties_with_schema(properties_list: list[dict[str, Any]]) -> b Returns: True if the "properties" section passed validation. Otherwise, a BaseError with a detailed error report is raised. """ - with open("knora/dsplib/schemas/properties-only.json") as schema: + current_dir = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(current_dir, "../schemas/properties-only.json")) as schema: properties_schema = json.load(schema) try: jsonschema.validate(instance=properties_list, schema=properties_schema) @@ -99,8 +102,15 @@ def excel2properties(excelfile: str, path_to_output_file: Optional[str] = None) df: pd.DataFrame = pd.read_excel(excelfile) df = prepare_dataframe( df=df, - required_columns=["name", "super", "object", "gui_element"], - location_of_sheet=f"File '{excelfile}'") + required_columns=["name"], + location_of_sheet=f"File '{excelfile}'" + ) + + required = ["super", "object", "gui_element"] + for index, row in df.iterrows(): + for req in required: + if not check_notna(row[req]): + raise BaseError(f"'{excelfile}' has a missing value in row {index + 2}, column '{req}'") # transform every row into a property props = [_row2prop(row, i, excelfile) for i, row in df.iterrows()] diff --git a/knora/dsplib/utils/excel_to_json_resources.py b/knora/dsplib/utils/excel_to_json_resources.py index af9615e03..24a55ad95 100644 --- a/knora/dsplib/utils/excel_to_json_resources.py +++ b/knora/dsplib/utils/excel_to_json_resources.py @@ -1,9 +1,12 @@ import json +import os from typing import Any, Optional + import jsonschema import pandas as pd + from knora.dsplib.models.helpers import BaseError -from knora.dsplib.utils.shared import prepare_dataframe +from knora.dsplib.utils.shared import prepare_dataframe, check_notna languages = ["en", "de", "fr", "it", "rm"] @@ -18,7 +21,8 @@ def _validate_resources_with_schema(resources_list: list[dict[str, Any]]) -> boo Returns: True if the "resources" section passed validation. Otherwise, a BaseError with a detailed error report is raised. """ - with open("knora/dsplib/schemas/resources-only.json") as schema: + current_dir = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(current_dir, "../schemas/resources-only.json")) as schema: resources_schema = json.load(schema) try: jsonschema.validate(instance=resources_list, schema=resources_schema) @@ -92,10 +96,14 @@ def excel2resources(excelfile: str, path_to_output_file: Optional[str] = None) - all_classes_df: pd.DataFrame = pd.read_excel(excelfile) all_classes_df = prepare_dataframe( df=all_classes_df, - required_columns=["name", "super"], + required_columns=["name"], location_of_sheet=f"Sheet 'classes' in file '{excelfile}'" ) + for index, row in all_classes_df.iterrows(): + if not check_notna(row["super"]): + raise BaseError(f"Sheet 'classes' of '{excelfile}' has a missing value in row {index + 2}, column 'super'") + # transform every row into a resource resources = [_row2resource(row, excelfile) for i, row in all_classes_df.iterrows()] _validate_resources_with_schema(resources) diff --git a/mkdocs.yml b/mkdocs.yml index 44a908361..5082a4006 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,9 +11,9 @@ nav: - JSON project definition format: - Overview: dsp-tools-create.md - Ontologies: dsp-tools-create-ontologies.md - - excel2xml: dsp-tools-excel2xml.md - Bulk data import: dsp-tools-xmlupload.md - - Excel file processing: dsp-tools-excel.md + - excel2json: dsp-tools-excel2json.md + - excel2xml: dsp-tools-excel2xml.md - Information for developers: dsp-tools-information-for-developers.md - Changelog: changelog.md diff --git a/test/e2e/test_tools.py b/test/e2e/test_tools.py index 5da6c68de..ccbb9480e 100644 --- a/test/e2e/test_tools.py +++ b/test/e2e/test_tools.py @@ -4,25 +4,27 @@ can be called with the basic configuration that is available via CLI. More thorough testing of each method is done in separate unit tests/e2e tests. """ +import copy +import datetime import json -import unittest import os -import datetime import re +import unittest + import jsonpath_ng import jsonpath_ng.ext -import copy from knora.dsplib.utils.excel_to_json_lists import excel2lists, validate_lists_section_with_schema +from knora.dsplib.utils.excel_to_json_project import excel2json from knora.dsplib.utils.excel_to_json_properties import excel2properties from knora.dsplib.utils.excel_to_json_resources import excel2resources from knora.dsplib.utils.id_to_iri import id_to_iri -from knora.dsplib.utils.onto_create_ontology import create_project -from knora.dsplib.utils.onto_validate import validate_project from knora.dsplib.utils.onto_create_lists import create_lists +from knora.dsplib.utils.onto_create_ontology import create_project from knora.dsplib.utils.onto_get import get_ontology -from knora.dsplib.utils.xml_upload import xml_upload +from knora.dsplib.utils.onto_validate import validate_project from knora.dsplib.utils.shared import validate_xml_against_schema +from knora.dsplib.utils.xml_upload import xml_upload from knora.excel2xml import excel2xml @@ -294,6 +296,17 @@ def test_xml_upload(self) -> None: os.remove(id2iri_replaced_xml_filename) + def test_excel_to_json_project(self) -> None: + excel2json(data_model_files="testdata/excel2json_files", + path_to_output_file="testdata/tmp/_out_project.json") + with open("testdata/excel2json-expected-output.json") as f: + output_expected = json.load(f) + with open("testdata/tmp/_out_project.json") as f: + output = json.load(f) + self.assertDictEqual(output, output_expected) + os.remove("testdata/tmp/_out_project.json") + + def test_excel_to_json_list(self) -> None: excel2lists(excelfolder="testdata/lists_multilingual", path_to_output_file="testdata/tmp/_lists-out.json") @@ -302,14 +315,14 @@ def test_excel_to_json_list(self) -> None: def test_excel_to_json_resources(self) -> None: - excel2resources(excelfile="testdata/Resources.xlsx", + excel2resources(excelfile="testdata/excel2json_files/test-name (test_label)/resources.xlsx", path_to_output_file="testdata/tmp/_out_resources.json") self.assertTrue(os.path.isfile("testdata/tmp/_out_resources.json")) os.remove("testdata/tmp/_out_resources.json") def test_excel_to_json_properties(self) -> None: - excel2properties(excelfile="testdata/Properties.xlsx", + excel2properties(excelfile="testdata/excel2json_files/test-name (test_label)/properties.xlsx", path_to_output_file="testdata/tmp/_out_properties.json") self.assertTrue(os.path.isfile("testdata/tmp/_out_properties.json")) os.remove("testdata/tmp/_out_properties.json") diff --git a/test/unittests/test_excel_to_json_properties.py b/test/unittests/test_excel_to_json_properties.py index d9d4c3b5e..94722b4a8 100644 --- a/test/unittests/test_excel_to_json_properties.py +++ b/test/unittests/test_excel_to_json_properties.py @@ -1,10 +1,11 @@ """unit tests for excel to properties""" +import json import os import unittest -import json +from typing import Any + import jsonpath_ng import jsonpath_ng.ext -from typing import Any from knora.dsplib.utils import excel_to_json_properties as e2j @@ -24,7 +25,7 @@ def tearDownClass(cls) -> None: os.rmdir('testdata/tmp') def test_excel2properties(self) -> None: - excelfile = "testdata/Properties.xlsx" + excelfile = "testdata/excel2json_files/test-name (test_label)/properties.xlsx" outfile = "testdata/tmp/_out_properties.json" output_from_method = e2j.excel2properties(excelfile, outfile) @@ -35,7 +36,7 @@ def test_excel2properties(self) -> None: "hasInterval", "hasBoolean", "hasGeoname", "partOfDocument"] excel_supers = [["hasLinkTo"], ["hasValue", "dcterms:creator"], ["hasValue"], ["hasValue"], ["hasLinkTo"], ["hasValue"], ["hasValue"], ["hasValue"], ["hasRepresentation"], - ["hasValue", "dcterms:description"], ["hasValue"],["hasValue"], ["hasColor"], ["hasValue"], + ["hasValue", "dcterms:description"], ["hasValue"], ["hasValue"], ["hasColor"], ["hasValue"], ["hasValue"], ["hasSequenceBounds"], ["hasValue"], ["hasValue"], ["isPartOf"]] excel_objects = [":GenericAnthroponym", "TextValue", "ListValue", "ListValue", ":Titles", "ListValue", "IntValue", "DateValue", "Representation", "TextValue", "DateValue", "UriValue", diff --git a/test/unittests/test_excel_to_json_resources.py b/test/unittests/test_excel_to_json_resources.py index a92add6c9..f2a4e81e4 100644 --- a/test/unittests/test_excel_to_json_resources.py +++ b/test/unittests/test_excel_to_json_resources.py @@ -1,10 +1,12 @@ """unit tests for excel to resource""" +import json import os import unittest -import json +from typing import Any + import jsonpath_ng import jsonpath_ng.ext -from typing import Any + from knora.dsplib.utils import excel_to_json_resources as e2j @@ -24,7 +26,7 @@ def tearDownClass(cls) -> None: def test_excel2resources(self) -> None: - excelfile = "testdata/Resources.xlsx" + excelfile = "testdata/excel2json_files/test-name (test_label)/resources.xlsx" outfile = "testdata/tmp/_out_resources.json" output_from_method = e2j.excel2resources(excelfile, outfile) diff --git a/testdata/Properties.xlsx b/testdata/Properties.xlsx deleted file mode 100644 index 9d5e8d9181b2034940e473f6430b1fc595e33deb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13688 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 U!v_HX*pCm{hZ}P-{&Dnw0Cf{dP5=M^ diff --git a/testdata/Resources.xlsx b/testdata/Resources.xlsx deleted file mode 100644 index 88759a9940405fe2eb1c8835ec6dd53a4e0eb5d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23090 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 diff --git a/testdata/excel2json-expected-output.json b/testdata/excel2json-expected-output.json new file mode 100644 index 000000000..77b1b0ffb --- /dev/null +++ b/testdata/excel2json-expected-output.json @@ -0,0 +1,1078 @@ +{ + "prefixes": { + "": "" + }, + "$schema": "https://raw.githubusercontent.com/dasch-swiss/dsp-tools/main/knora/dsplib/schemas/ontology.json", + "project": { + "shortcode": "", + "shortname": "", + "longname": "", + "descriptions": { + "en": "" + }, + "keywords": [ + "" + ], + "lists": [ + { + "name": "first-list", + "labels": { + "fr": "première liste", + "en": "first list", + "de": "erste Liste" + }, + "comments": { + "fr": "première liste", + "en": "first list", + "de": "erste Liste" + }, + "nodes": [ + { + "name": "special-characters-12-0-are-embedded", + "labels": { + "fr": "caractères spéciales 1&2-%*_0 dedans", + "en": "special characters 1&2-%*_0 are embedded", + "de": "Spezialzeichen 1&2-%*_0 sind eingebettet" + }, + "nodes": [ + { + "name": "very", + "labels": { + "fr": "très", + "en": "very", + "de": "sehr" + }, + "nodes": [ + { + "name": "deeply", + "labels": { + "fr": "profondément", + "en": "deeply", + "de": "tief" + }, + "nodes": [ + { + "name": "nested", + "labels": { + "fr": "niché!", + "en": "nested!", + "de": "verschachtelt!" + } + } + ] + } + ] + } + ] + } + ] + }, + { + "name": "second-list", + "labels": { + "fr": "deuxième liste", + "en": "second list", + "de": "zweite Liste" + }, + "comments": { + "fr": "deuxième liste", + "en": "second list", + "de": "zweite Liste" + }, + "nodes": [ + { + "name": "first-node", + "labels": { + "fr": "premier noeud", + "en": "first node", + "de": "erster Knoten" + } + }, + { + "name": "duplicate-nodename", + "labels": { + "fr": "noeud doublé", + "en": "duplicate nodename", + "de": "Doppelung" + } + }, + { + "name": "duplicate-nodename-2", + "labels": { + "fr": "noeud doublé!", + "en": "duplicate nodename!", + "de": "Doppelung!" + } + }, + { + "name": "duplicate-nodename-3", + "labels": { + "fr": "noeud doublé?", + "en": "duplicate nodename?", + "de": "Doppelung?" + } + } + ] + } + ], + "ontologies": [ + { + "name": "test-name", + "label": "test_label", + "properties": [ + { + "name": "correspondsToGenericAnthroponym", + "super": [ + "hasLinkTo" + ], + "object": ":GenericAnthroponym", + "labels": { + "en": "only English" + }, + "comments": { + "en": "I had already looked at several pieces of property when, one day, the notary, who had been giving me some necessary directions for one of my explorations, said to me:", + "de": "Ich hatte bereits mehrere Grundstücke besichtigt, als eines Tages der Notar, der mir die notwendigen Anweisungen für eine meiner Erkundungen gegeben hatte, zu mir sagte:", + "fr": "J'avais déjà examiné plusieurs propriétés quand, un jour, le notaire, qui me donnait des indications nécessaires pour une de mes explorations, me dit :", + "it": "Avevo già visto diverse proprietà quando un giorno il notaio,", + "rm": "Rumantsch" + }, + "gui_element": "Searchbox" + }, + { + "name": "hasAnthroponym", + "super": [ + "hasValue", + "dcterms:creator" + ], + "object": "TextValue", + "labels": { + "de": "only German" + }, + "comments": { + "en": "A strange chance put me in possession of this journal.", + "de": "Ein seltsamer Zufall brachte mich in den Besitz dieses Tagebuchs.", + "fr": "Un étrange hasard m'a mis en possession de ce journal.", + "it": "Uno strano caso mi mise in possesso di questo diario.", + "rm": "Rumantsch" + }, + "gui_element": "Richtext" + }, + { + "name": "hasGender", + "super": [ + "hasValue" + ], + "object": "ListValue", + "labels": { + "fr": "only French" + }, + "comments": { + "en": "I know nothing of it whatever; but if you would like to see it, monsieur, here are the precise directions how to find it.", + "de": "Ich weiß nichts davon, aber wenn Sie es sehen möchten, Monsieur, finden Sie hier die genaue Wegbeschreibung.", + "fr": "Je n'en sais rien du tout ; mais si vous voulez la voir, monsieur, voici les indications précises pour la trouver.", + "it": "Non ne so nulla; ma se volete vederla, signore, eccovi le indicazioni precise per trovarla.", + "rm": "Rumantsch" + }, + "gui_element": "List", + "gui_attributes": { + "hlist": "gender" + } + }, + { + "name": "isDesignatedAs", + "super": [ + "hasValue" + ], + "object": "ListValue", + "labels": { + "it": "only Italian" + }, + "comments": { + "en": "You will have to arrange the affair with the curé of the village of ——.\"", + "de": "Sie werden die Angelegenheit mit dem Pfarrer des Dorfes -- regeln müssen.\"", + "fr": "Vous devrez arranger l'affaire avec le curé du village de --.\"", + "it": "Dovrete organizzare l'affare con il curato del villaggio di --\".", + "rm": "Rumantsch" + }, + "gui_element": "Radio", + "gui_attributes": { + "hlist": "designation" + } + }, + { + "name": "hasTitle", + "super": [ + "hasLinkTo" + ], + "object": ":Titles", + "labels": { + "rm": "only Romansh" + }, + "comments": { + "en": "A strange chance put me in possession of this journal.", + "de": "Ein seltsamer Zufall brachte mich in den Besitz dieses Tagebuchs.", + "fr": "Un étrange hasard m'a mis en possession de ce journal.", + "it": "Uno strano caso mi mise in possesso di questo diario.", + "rm": "Rumantsch" + }, + "gui_element": "Searchbox" + }, + { + "name": "hasStatus", + "super": [ + "hasValue" + ], + "object": "ListValue", + "labels": { + "en": "status", + "fr": "statut", + "rm": "Rumantsch" + }, + "comments": { + "en": "only English" + }, + "gui_element": "List", + "gui_attributes": { + "hlist": "status" + } + }, + { + "name": "hasLifeYearAmount", + "super": [ + "hasValue" + ], + "object": "IntValue", + "labels": { + "en": "age", + "fr": "Âge", + "rm": "Rumantsch" + }, + "comments": { + "de": "only German" + }, + "gui_element": "Spinbox" + }, + { + "name": "hasBirthDate", + "super": [ + "hasValue" + ], + "object": "DateValue", + "labels": { + "en": "birth date", + "fr": "Date de naissance", + "rm": "Rumantsch" + }, + "comments": { + "fr": "only French" + }, + "gui_element": "Date" + }, + { + "name": "hasRepresentation", + "super": [ + "hasRepresentation" + ], + "object": "Representation", + "labels": { + "en": "has a multimedia file", + "de": "hat eine Multimediadatei" + }, + "comments": { + "rm": "only Rumantsch" + }, + "gui_element": "Searchbox" + }, + { + "name": "hasRemarks", + "super": [ + "hasValue", + "dcterms:description" + ], + "object": "TextValue", + "labels": { + "en": "remark", + "fr": "Commentaire", + "rm": "Rumantsch" + }, + "gui_element": "Textarea" + }, + { + "name": "hasTerminusPostQuem", + "super": [ + "hasValue" + ], + "object": "DateValue", + "labels": { + "en": "terminus post quem", + "fr": "terminus post quem", + "rm": "Rumantsch" + }, + "comments": { + "en": "I had already looked at several pieces of property when, one day, the notary, who had been giving me some necessary directions for one of my explorations, said to me:", + "de": "Ich hatte bereits mehrere Grundstücke besichtigt, als eines Tages der Notar, der mir die notwendigen Anweisungen für eine meiner Erkundungen gegeben hatte, zu mir sagte:", + "fr": "J'avais déjà examiné plusieurs propriétés quand, un jour, le notaire, qui me donnait des indications nécessaires pour une de mes explorations, me dit :", + "it": "Avevo già visto diverse proprietà quando un giorno il notaio,", + "rm": "Rumantsch" + }, + "gui_element": "Date" + }, + { + "name": "hasGND", + "super": [ + "hasValue" + ], + "object": "UriValue", + "labels": { + "en": "GND", + "de": "GND", + "fr": "GND", + "it": "GND", + "rm": "Rumantsch" + }, + "comments": { + "en": "Gemeinsame Normdatei", + "de": "Gemeinsame Normdatei", + "fr": "Gemeinsame Normdatei", + "it": "Gemeinsame Normdatei", + "rm": "Rumantsch" + }, + "gui_element": "SimpleText", + "gui_attributes": { + "size": 100 + } + }, + { + "name": "hasColor", + "super": [ + "hasColor" + ], + "object": "ColorValue", + "labels": { + "en": "Color", + "de": "Farbe", + "rm": "Rumantsch" + }, + "comments": { + "en": "Color", + "de": "Farbe" + }, + "gui_element": "Colorpicker" + }, + { + "name": "hasDecimal", + "super": [ + "hasValue" + ], + "object": "DecimalValue", + "labels": { + "en": "Decimal number", + "de": "Dezimalzahl", + "fr": "Chiffre décimale", + "rm": "Rumantsch" + }, + "comments": { + "en": "Decimal number", + "de": "Dezimalzahl", + "fr": "Chiffre décimale" + }, + "gui_element": "Slider", + "gui_attributes": { + "min": 0.0, + "max": 100.0 + } + }, + { + "name": "hasTime", + "super": [ + "hasValue" + ], + "object": "TimeValue", + "labels": { + "en": "Time", + "de": "Zeit", + "fr": "Temps", + "rm": "Rumantsch" + }, + "comments": { + "en": "Time", + "de": "Zeit", + "fr": "Temps" + }, + "gui_element": "TimeStamp" + }, + { + "name": "hasInterval", + "super": [ + "hasSequenceBounds" + ], + "object": "IntervalValue", + "labels": { + "en": "Time interval", + "de": "Zeitintervall" + }, + "comments": { + "en": "Time interval", + "de": "Zeitintervall" + }, + "gui_element": "Interval" + }, + { + "name": "hasBoolean", + "super": [ + "hasValue" + ], + "object": "BooleanValue", + "labels": { + "en": "Boolean value", + "de": "Bool'sche Variable" + }, + "comments": { + "en": "Boolean value", + "de": "Bool'sche Variable" + }, + "gui_element": "Checkbox" + }, + { + "name": "hasGeoname", + "super": [ + "hasValue" + ], + "object": "GeonameValue", + "labels": { + "en": "Geoname link", + "de": "Link zu Geonames" + }, + "comments": { + "en": "Geoname link", + "de": "Link zu Geonames" + }, + "gui_element": "Geonames" + }, + { + "name": "partOfDocument", + "super": [ + "isPartOf" + ], + "object": ":Documents", + "labels": { + "en": "is part of a document", + "de": "ist Teil eines Dokuments" + }, + "comments": { + "en": "is part of a document", + "de": "ist Teil eines Dokuments" + }, + "gui_element": "Searchbox" + } + ], + "resources": [ + { + "name": "Owner", + "super": [ + "Resource", + "dcterms:fantasy" + ], + "labels": { + "en": "Owner", + "de": "Eigentümer", + "fr": "Propriétaire", + "it": "Proprietario", + "rm": "Rumantsch" + }, + "comments": { + "en": "A strange chance put me in possession of this journal.", + "de": "Ein seltsamer Zufall brachte mich in den Besitz dieses Tagebuchs.", + "fr": "Un étrange hasard m'a mis en possession de ce journal.", + "it": "Uno strano caso mi mise in possesso di questo diario.", + "rm": "Rumantsch" + }, + "cardinalities": [ + { + "propname": ":hasAnthroponym", + "cardinality": "1", + "gui_order": 1 + }, + { + "propname": ":isOwnerOf", + "cardinality": "0-1", + "gui_order": 2 + }, + { + "propname": ":correspondsToGenericAnthroponym", + "cardinality": "0-n", + "gui_order": 3 + }, + { + "propname": ":hasAlias", + "cardinality": "1", + "gui_order": 4 + }, + { + "propname": ":hasGender", + "cardinality": "0-n", + "gui_order": 5 + }, + { + "propname": ":isDesignatedAs", + "cardinality": "0-1", + "gui_order": 6 + }, + { + "propname": ":hasTitle", + "cardinality": "1-n", + "gui_order": 7 + }, + { + "propname": ":hasStatus", + "cardinality": "0-1", + "gui_order": 8 + }, + { + "propname": ":hasFamilyRelationTo", + "cardinality": "1-n", + "gui_order": 9 + }, + { + "propname": ":hasLifeYearAmount", + "cardinality": "0-1", + "gui_order": 10 + }, + { + "propname": ":hasBirthDate", + "cardinality": "0-1", + "gui_order": 11 + }, + { + "propname": ":hasDeathDate", + "cardinality": "0-1", + "gui_order": 12 + }, + { + "propname": ":hasBibliography", + "cardinality": "1-n", + "gui_order": 13 + }, + { + "propname": ":hasRemarks", + "cardinality": "1-n", + "gui_order": 14 + } + ] + }, + { + "name": "Title", + "super": [ + "Resource" + ], + "labels": { + "en": "Title", + "de": "Titel", + "fr": "Titre", + "it": "Titolo", + "rm": "Rumantsch" + }, + "comments": { + "en": "Only English" + }, + "cardinalities": [ + { + "propname": ":isTitle", + "cardinality": "1", + "gui_order": 1 + }, + { + "propname": ":isTitleInEnglish", + "cardinality": "0-1", + "gui_order": 2 + }, + { + "propname": ":isTitleInFrench", + "cardinality": "0-1", + "gui_order": 3 + }, + { + "propname": ":isTitleInGerman", + "cardinality": "0-1", + "gui_order": 4 + }, + { + "propname": ":isEquivalentTo", + "cardinality": "0-1", + "gui_order": 5 + }, + { + "propname": ":belongsToOccupationField", + "cardinality": "0-1", + "gui_order": 6 + }, + { + "propname": ":isGodRelatedTo", + "cardinality": "0-n", + "gui_order": 7 + }, + { + "propname": ":isPlaceRelatedTo", + "cardinality": "0-n", + "gui_order": 8 + }, + { + "propname": ":hasBibliography", + "cardinality": "0-n", + "gui_order": 9 + }, + { + "propname": ":hasRemarks", + "cardinality": "0-n", + "gui_order": 10 + } + ] + }, + { + "name": "GenericAnthroponym", + "super": [ + "Resource" + ], + "labels": { + "en": "Generic anthroponym", + "de": "Allgemeines Anthroponym", + "fr": "Anthroponyme générique", + "it": "Antroponimo generico", + "rm": "Rumantsch" + }, + "comments": { + "de": "Only German" + }, + "cardinalities": [ + { + "propname": ":isGenericAnthroponym", + "cardinality": "1", + "gui_order": 1 + }, + { + "propname": ":hasTMNameID", + "cardinality": "0-1", + "gui_order": 2 + }, + { + "propname": ":hasEgyptianVersion", + "cardinality": "0-1", + "gui_order": 3 + }, + { + "propname": ":hasGreekVersion", + "cardinality": "0-1", + "gui_order": 4 + }, + { + "propname": ":hasLatinVersion", + "cardinality": "0-1", + "gui_order": 5 + }, + { + "propname": ":hasCopticVersion", + "cardinality": "0-1", + "gui_order": 6 + }, + { + "propname": ":hasOtherVersion", + "cardinality": "0-n", + "gui_order": 7 + }, + { + "propname": ":hasGender", + "cardinality": "0-1", + "gui_order": 8 + }, + { + "propname": ":hasLinguisticOrigin", + "cardinality": "0-1", + "gui_order": 9 + }, + { + "propname": ":hasEnglishTranslation", + "cardinality": "0-1", + "gui_order": 10 + }, + { + "propname": ":hasFrenchTranslation", + "cardinality": "0-1", + "gui_order": 11 + }, + { + "propname": ":hasGermanTranslation", + "cardinality": "0-1", + "gui_order": 12 + }, + { + "propname": ":isGodRelatedTo", + "cardinality": "0-1", + "gui_order": 13 + }, + { + "propname": ":isPlaceRelatedTo", + "cardinality": "0-1", + "gui_order": 14 + }, + { + "propname": ":hasAttestationAmount", + "cardinality": "0-1", + "gui_order": 15 + }, + { + "propname": ":isMostCommonIn", + "cardinality": "0-n", + "gui_order": 16 + }, + { + "propname": ":isMostWidespreadIn", + "cardinality": "0-n", + "gui_order": 17 + }, + { + "propname": ":hasTerminusPostQuem", + "cardinality": "0-1", + "gui_order": 18 + }, + { + "propname": ":hasTerminusAnteQuem", + "cardinality": "0-1", + "gui_order": 19 + }, + { + "propname": ":hasBibliography", + "cardinality": "0-n", + "gui_order": 20 + }, + { + "propname": ":hasRemarks", + "cardinality": "0-n", + "gui_order": 21 + } + ] + }, + { + "name": "FamilyMember", + "super": [ + "Resource" + ], + "labels": { + "en": "Family member", + "de": "Familienmitglied", + "fr": "Membre de la famille", + "it": "Membro della famiglia", + "rm": "Rumantsch" + }, + "comments": { + "fr": "Only French" + }, + "cardinalities": [ + { + "propname": ":hasAnthroponym", + "cardinality": "1", + "gui_order": 1 + }, + { + "propname": ":isRelatedTo", + "cardinality": "0-1", + "gui_order": 2 + }, + { + "propname": ":isRelatedAs", + "cardinality": "0-1", + "gui_order": 3 + }, + { + "propname": ":correspondsToGenericAnthroponym", + "cardinality": "0-1", + "gui_order": 4 + }, + { + "propname": ":hasAlias", + "cardinality": "0-1", + "gui_order": 5 + }, + { + "propname": ":hasGender", + "cardinality": "0-1", + "gui_order": 6 + }, + { + "propname": ":isDesignatedAs", + "cardinality": "0-1", + "gui_order": 7 + }, + { + "propname": ":hasTitle", + "cardinality": "1-n", + "gui_order": 8 + }, + { + "propname": ":hasStatus", + "cardinality": "0-1", + "gui_order": 9 + }, + { + "propname": ":hasFamilyRelationTo", + "cardinality": "1-n", + "gui_order": 10 + }, + { + "propname": ":hasLifeYearAmount", + "cardinality": "0-1", + "gui_order": 11 + }, + { + "propname": ":hasBirthDate", + "cardinality": "0-1", + "gui_order": 12 + }, + { + "propname": ":hasDeathDate", + "cardinality": "0-1", + "gui_order": 13 + }, + { + "propname": ":hasBibliography", + "cardinality": "1-n", + "gui_order": 14 + }, + { + "propname": ":hasRemarks", + "cardinality": "1-n", + "gui_order": 15 + } + ] + }, + { + "name": "MentionedPerson", + "super": [ + "Resource" + ], + "labels": { + "en": "Mentioned person", + "de": "Erwähnte Person", + "fr": "Personne mentionnée", + "it": "Persona menzionata", + "rm": "Rumantsch" + }, + "comments": { + "it": "Only Italian" + }, + "cardinalities": [ + { + "propname": ":hasAnthroponym", + "cardinality": "1", + "gui_order": 1 + }, + { + "propname": ":isOwnerOf", + "cardinality": "0-1", + "gui_order": 2 + }, + { + "propname": ":correspondsToGenericAnthroponym", + "cardinality": "0-1", + "gui_order": 3 + }, + { + "propname": ":hasAlias", + "cardinality": "0-1", + "gui_order": 4 + }, + { + "propname": ":hasGender", + "cardinality": "0-1", + "gui_order": 5 + }, + { + "propname": ":isDesignatedAs", + "cardinality": "0-1", + "gui_order": 6 + }, + { + "propname": ":hasTitle", + "cardinality": "1-n", + "gui_order": 7 + }, + { + "propname": ":hasStatus", + "cardinality": "0-1", + "gui_order": 8 + }, + { + "propname": ":hasFamilyRelationTo", + "cardinality": "1-n", + "gui_order": 9 + }, + { + "propname": ":hasLifeYearAmount", + "cardinality": "0-1", + "gui_order": 10 + }, + { + "propname": ":hasBirthDate", + "cardinality": "0-1", + "gui_order": 11 + }, + { + "propname": ":hasDeathDate", + "cardinality": "0-1", + "gui_order": 12 + }, + { + "propname": ":hasBibliography", + "cardinality": "1-n", + "gui_order": 13 + }, + { + "propname": ":hasRemarks", + "cardinality": "1-n", + "gui_order": 14 + } + ] + }, + { + "name": "Alias", + "super": [ + "Resource" + ], + "labels": { + "en": "Alias", + "de": "Alias", + "fr": "Alias", + "it": "Alias", + "rm": "Rumantsch" + }, + "comments": { + "rm": "Only Romansh" + }, + "cardinalities": [ + { + "propname": ":isAlias", + "cardinality": "1", + "gui_order": 1 + }, + { + "propname": ":isAliasOf", + "cardinality": "0-1", + "gui_order": 2 + }, + { + "propname": ":correspondsToGenericAnthroponym", + "cardinality": "0-1", + "gui_order": 3 + }, + { + "propname": ":hasGender", + "cardinality": "0-1", + "gui_order": 4 + } + ] + }, + { + "name": "Image", + "super": [ + "StillImageRepresentation", + "dcterms:image" + ], + "labels": { + "en": "Only English" + }, + "comments": { + "en": "Image", + "de": "Bild" + }, + "cardinalities": [ + { + "propname": ":hasRemarks", + "cardinality": "1", + "gui_order": 1 + } + ] + }, + { + "name": "Video", + "super": [ + "MovingImageRepresentation" + ], + "labels": { + "de": "Only German" + }, + "comments": { + "en": "Video", + "de": "Video" + }, + "cardinalities": [ + { + "propname": ":hasRemarks", + "cardinality": "1", + "gui_order": 1 + } + ] + }, + { + "name": "Audio", + "super": [ + "AudioRepresentation" + ], + "labels": { + "fr": "Only French" + }, + "comments": { + "en": "Audio", + "de": "Audio" + }, + "cardinalities": [ + { + "propname": ":hasRemarks", + "cardinality": "1", + "gui_order": 1 + } + ] + }, + { + "name": "ZIP", + "super": [ + "ArchiveRepresentation" + ], + "labels": { + "it": "Only Italian" + }, + "comments": { + "en": "ZIP", + "de": "ZIP" + }, + "cardinalities": [ + { + "propname": ":hasRemarks", + "cardinality": "1", + "gui_order": 1 + } + ] + }, + { + "name": "PDFDocument", + "super": [ + "DocumentRepresentation" + ], + "labels": { + "rm": "Only Rumantsch" + }, + "comments": { + "en": "PDF Document", + "de": "PDF-Dokument" + }, + "cardinalities": [ + { + "propname": ":hasRemarks", + "cardinality": "1", + "gui_order": 1 + } + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/testdata/excel2json_files/lists/de.xlsx b/testdata/excel2json_files/lists/de.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0ed08b1b303d81b41a9290f11c6e6ec5f4bb079b GIT binary patch literal 9679 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 Pm&ofGGCgi*_}TkEKbmf$ literal 0 HcmV?d00001 diff --git a/testdata/excel2json_files/lists/fr.xlsx b/testdata/excel2json_files/lists/fr.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..0eecf401003c35b8e29747b2062e5c2c1f172538 GIT binary patch literal 9554 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 HkJ0}Dkz{e2 literal 0 HcmV?d00001 diff --git a/testdata/excel2json_files/test-name (test_label)/properties.xlsx b/testdata/excel2json_files/test-name (test_label)/properties.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..3ca1d79f0b37b966f317c23ee946fd9dc2826fbb GIT binary patch literal 13662 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 literal 0 HcmV?d00001 diff --git a/testdata/excel2json_files/test-name (test_label)/resources.xlsx b/testdata/excel2json_files/test-name (test_label)/resources.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8da7120b76faa988abbc26ae477bfe29a744f6c1 GIT binary patch literal 23082 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 literal 0 HcmV?d00001