From 5044a9ec5a238b250b74355da915e1bef5216cd4 Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum <39048939+jnussbaum@users.noreply.github.com> Date: Mon, 18 Jul 2022 16:14:04 +0200 Subject: [PATCH] feat(xmlupload): implement , , and tags (DEV-855) (#204) These tags correspond to the DSP base resources Annotation, Region, LinkObj --- .gitignore | 2 + docs/dsp-tools-create-ontologies.md | 235 ++++++++++-------- docs/dsp-tools-create.md | 2 + docs/dsp-tools-xmlupload.md | 148 +++++++++-- knora/dsplib/models/ontology.py | 6 +- knora/dsplib/models/propertyclass.py | 5 +- knora/dsplib/models/resource.py | 62 ++--- knora/dsplib/models/value.py | 2 +- knora/dsplib/models/xmlproperty.py | 2 +- knora/dsplib/models/xmlresource.py | 2 +- knora/dsplib/schemas/data.xsd | 205 +++++++++------ knora/dsplib/schemas/ontology.json | 82 ++---- knora/dsplib/schemas/properties-only.json | 45 +--- knora/dsplib/schemas/resources-only.json | 5 +- knora/dsplib/utils/xml_upload.py | 94 +++++-- test/e2e/test_tools.py | 10 +- test/unittests/test_excel_to_properties.py | 16 +- test/unittests/test_excel_to_resource.py | 14 +- test/unittests/test_id_to_iri.py | 20 +- test/unittests/test_xmlupload.py | 69 +++--- testdata/Properties.xlsx | Bin 13750 -> 13681 bytes testdata/Resources.xlsx | Bin 25792 -> 23090 bytes testdata/test-data.xml | 274 ++++++++++++++++----- testdata/test-id2iri-data.xml | 4 +- testdata/test-id2iri-mapping.json | 4 +- testdata/test-onto.json | 84 +++++-- 26 files changed, 850 insertions(+), 542 deletions(-) diff --git a/.gitignore b/.gitignore index 284f01fc8..a8c70a213 100644 --- a/.gitignore +++ b/.gitignore @@ -63,8 +63,10 @@ venv.bak/ # created files lists.json +lists-only.json out.json id2iri_* +stashed* **/~$*.* testdata/tmp/ testdata/test-list.json diff --git a/docs/dsp-tools-create-ontologies.md b/docs/dsp-tools-create-ontologies.md index 4a45ff0c8..65873a670 100644 --- a/docs/dsp-tools-create-ontologies.md +++ b/docs/dsp-tools-create-ontologies.md @@ -119,10 +119,6 @@ A detailed description of `resources` can be found [below](#properties-object-in ## Properties Object in Detail -Please note that `object` is used to define the data type. The `gui_element` depends on the value of the `object`. - -The `gui_attributes` depends on the value of the `gui_element`. - ### Name (required) @@ -158,28 +154,26 @@ Comments with language tags. Currently, "de", "en", "fr", "it", and "rm" are sup `"super": ["", ", ...]` -A property has to be derived from at least one base property. The most generic base property that the DSP offers is -_hasValue_. In addition, the property may be a sub-property of properties defined in external or other ontologies. -External ontologies like `dcterms` or `foaf` must be defined in the "prefix" section. In this case the qualified name - -including the prefix of the external or internal ontology - has to be given. - -The following base properties are defined by DSP: - -- `hasValue`: This is the most generic base. -- `hasLinkTo`: This value represents a link to another resource. You have to indicate the "_object_" as a prefixed name - that identifies the resource class this link points to (a ":" prepended to the name is sufficient if the resource is - defined in the current ontology). -- `hasColor`: Defines a color value -- `hasComment`: Defines a standard comment -- `hasGeometry`: Defines a geometry value (a JSON describing a polygon, circle or rectangle) -- `isPartOf`: A special variant of _hasLinkTo_. It says that an instance of the given resource class is an integral part +A property is always derived from at least one other property. There are three groups of properties that can serve as +super-property: + + - DSP base properties + - properties defined in external ontologies + - properties defined in the project ontology itself + +The syntax how to refer to these different groups of properties is described [here](#referencing-ontologies). + +The following base properties can be used as super-property: + +- `hasValue`: This is the most general case, to be used in all cases when your property is none of the special cases below. +- `hasLinkTo`: a link to another resource +- `isPartOf`: A special variant of `hasLinkTo`. It says that an instance of the given resource class is an integral part of another resource class. E.g. a "page" is part of a "book". -- `isRegionOf`: A special variant of _hasLinkTo_. It means that the given resource class is a "region" of another - resource class. This is typically used to describe regions of interest in images. -- `isAnnotationOf`: A special variant of _hasLinkTo_. It denotes the given resource class as an annotation to another - resource class. - `seqnum`: An integer that is used to define a sequence number in an ordered set of instances, e.g. the ordering of the - pages in a book (independent of the page naming) in combination with a property derived from `isPartOf`. + pages in a book. A resource that has a property derived from `seqnum` must also have a property derived from `isPartOf`. +- `hasColor`: Defines a color value. +- `hasComment`: Defines a standard comment. + Example of a `properties` object: @@ -237,29 +231,27 @@ resource class (see [below](#referencing-ontologies) on how prefixed names are u ### Object / gui_element / gui_attributes -- `object`: required -- `gui_element`: required -- `gui_attributes`: optional +These three are related as follows: -`"object": ""` + - `object` (required) is used to define the data type of the value that the property will store. + - `gui_element` (required) depends on the value of `object`. + - `gui_attributes` (optional) depends on the value of `gui_element`. -The `object` defines the data type of the value that the property will store. `gui_element` and `gui_attributes` depend -on the data type. The following data types are allowed: +The following data types are allowed: - `TextValue` - `ColorValue` - `DateValue` - `TimeValue` - `DecimalValue` -- `GeomValue` - `GeonameValue` - `IntValue` - `BooleanValue` - `UriValue` - `IntervalValue` - `ListValue` -- `Representation` -- any previously defined resource class in case of a link property +- in case of a link property: any resource class + #### TextValue @@ -401,7 +393,7 @@ which means anytime in between 1925 and the 22nd March 1927. *gui_elements / gui_attributes*: - `Date`: The only GUI element for _DateValue_. A date picker gui. -- _gui_attributes_: No attributes + - _gui_attributes_: No attributes *Example:* @@ -594,36 +586,6 @@ A string representation of the color in the hexadecimal form e.g. "#ff8000". } ``` -#### GeomValue - -`"object": "GeomValue"` - -Represents a geometrical shape as JSON. Geometrical shapes are used to define regions of interest (ROI) on still images -or moving images. - -*gui-elements / gui_attributes*: - -- `Geometry`: not yet implemented. - - _gui_attributes_: No attributes -- `SimpleText`: A GUI element for _TextValue_. A simple text entry box (one line only). The attributes - "maxlength=integer" and "size=integer" are optional. - - _gui_attributes_: - - `maxlength=integer` (optional): The maximum number of characters accepted - - `size=integer` (optional): The size of the input field - -*Example*: - -```json -{ - "name": "hasGeometry", - "super": [ - "hasGeometry" - ], - "object": "GeomValue", - "labels": "Geometry", - "gui_element": "SimpleText" -} -``` #### ListValue @@ -692,24 +654,31 @@ A property pointing to a `knora-base:Representation`. Has to be used in combinat #### hasLinkTo Property -`"object": ":"` +`"object": ""` Link properties do not follow the pattern of the previous data types, because they do not connect to a final value but -to another (existing) resource. Thus, the "object" denominates the resource class the link will point to. If -the resource is defined in the same ontology, the name has to be prepended by a ":", if the resource is defined in -another (previously defined) ontology, the ontology name has to be prepended separated by a colon ":", e.g. -"other-onto:MyResource". When defining a link property, its "super" element has to be "hasLinkTo" or "isPartOf" or -derived from "hasLinkTo" or "isPartOf". "isPartOf" is a special type of linked resources, for more information see -[below](#ispartof-property). +to an existing resource. Thus, the `object` denominates the resource class the link will point to. There are different +groups of resource classes that can be the object: + + - project resources: a resource class defined in the present ontology itself + - external resources: a resource class defined in another ontology + - DSP base resources: + - `Resource`: the most generic one, can point to any resource class, be it a DSP base resource, a project resource, + or an external resource. `Resource` is at the very top of the inheritance hierarchy. + - `Region`: a region in an image + - `StillImageRepresentation`, `MovingImageRepresentation`, `TextRepresentation`, `AudioRepresentation`, + `DDDRepresentation`, `DocumentRepresentation`, or `ArchiveRepresentation` + - `Representation`: any type of the just mentioned representations. `Representation` is the parent class of them. + +The syntax how to refer to these different groups of resources is described [here](#referencing-ontologies). + +When defining a link property, its "super" element has to be `hasLinkTo` or derived from `hasLinkTo`. *gui-elements/gui_attributes*: -- `Searchbox`: Has to be used with _hasLinkTo_ property. Allows searching resources by entering the resource name that the - given resource should link to. It has one gui_attribute that indicates how many properties of the found resources - should be indicated. +- `Searchbox`: The only GUI element for _hasLinkTo_. Allows searching resources by entering the target resource name. - _gui_attributes_: - - `numprops=integer` (optional): While dynamically displaying the search result, the number of properties that - should be displayed. + - `numprops=integer` (optional): Number of search results to be displayed *Example:* @@ -725,12 +694,18 @@ derived from "hasLinkTo" or "isPartOf". "isPartOf" is a special type of linked r } ``` -#### IsPartOf Property -A special case of linked resources is resources in a part-of / part-whole relation, i.e. resources that are composed of +#### isPartOf Property +A special case of linked resources are resources in a part-whole relation, i.e. resources that are composed of other resources. A `isPartOf` property has to be added to the resource that is part of another resource. In case of resources that are of type `StillImageRepresentation`, an additional property derived from `seqnum` with object `IntValue` is required. When defined, a client is able to leaf through the parts of a compound object, p.ex. to leaf through pages of a book. +*gui-elements/gui_attributes*: + +- `Searchbox`: The only GUI element for _isPartOf_. Allows searching resources by entering the target resource name. + - _gui_attributes_: + - `numprops=integer` (optional): Number of search results to be displayed + *Example:* ```json @@ -758,9 +733,31 @@ is required. When defined, a client is able to leaf through the parts of a compo } ``` +#### hasComment Property -## Resources Object in Detail +`"object": "TextValue"` + +This property is actually very similar to a simple text field. + +*Example:* + +```json +{ + "name": "hasComment", + "super": [ + "hasComment" + ], + "object": "TextValue", + "labels": { + "de": "Kommentar", + "en": "Comment", + "fr": "Commentaire" + }, + "gui_element": "SimpleText" +} +``` +## Resources Object in Detail ### Name @@ -789,34 +786,27 @@ and "rm" are supported). `"super": ["", "", ...]` -A resource is always derived from at least one other resource. The most generic resource class for DSP is `Resource`. -A resource may be derived from resources defined in external ontologies. +A resource is always derived from at least one other resource. There are three groups of resources that can serve +as super-resource: + + - DSP base resources + - resources defined in external ontologies + - resources defined in the project ontology itself -The following predefined resources are provided by DSP: +The syntax how to refer to these different groups of resources is described [here](#referencing-ontologies). + +The following base resources can be used as super-resource: - `Resource`: A generic resource representing an item from the real world. This is the most general case, to be used in all cases when your resource is none of the special cases below. -- `StillImageRepresentation`: An object representing a still image -- `TextRepresentation`: An object representing an (external) text (not yet implemented) -- `AudioRepresentation`: An object representing an audio file -- `DDDRepresentation`: An object representing a 3-D representation (not yet implemented) -- `DocumentRepresentation`: An object representing an opaque document (e.g. a PDF) -- `MovingImageRepresentation`: An object representing a moving image (video, film) -- `ArchiveRepresentation`: An object representing an archive file (e.g. Zip) -- `Annotation`: A predefined annotation object. It has automatically the following predefined properties defined: - - `hasComment` (1-n) - - `isAnnotationOf` (1) -- `LinkObj`: A resource class linking together several other resource classes. The class has the following - properties: - - `hasComment` (1-n) - - `hasLinkTo` (1-n) -- `Region`: Represents a region in an image. The class has the following properties: - - `hasColor` (1) - - `isRegionOf` (1) - - `hasGeometry` (1) - - `hasComment` (0-n) +- `StillImageRepresentation`: A resource representing an image +- `TextRepresentation`: A resource representing a text +- `AudioRepresentation`: A resource representing an audio file +- `DDDRepresentation`: A resource representing a 3-D representation (not yet implemented) +- `DocumentRepresentation`: A resource representing an opaque document (e.g. a PDF) +- `MovingImageRepresentation`: A resource representing a video +- `ArchiveRepresentation`: A resource representing an archive file (e.g. ZIP) -Additionally, resources can be derived from external ontologies or from resources specified in the present document. ### Cardinalities @@ -907,3 +897,44 @@ it is necessary to reference entities that are defined elsewhere. The following These will be created in the exact order they appear in the `ontologies` array. Once an ontology has been created, it can be referenced by the following ontologies by its name, e.g. `first-onto:hasName`. It is not necessary to add `first-onto` to the prefixes. + + +## DSP base resources / base properties to be used directly in the XML file +There is a number of DSP base resources that must not be subclassed in a project ontology. They are directly available +in the XML data file: + +- `Annotation` is an annotation to another resource of any class. It can be used in the XML file with the + [<annotation> tag](dsp-tools-xmlupload.md#annotation). It automatically has the following predefined properties: + - `hasComment` (1-n) + - `isAnnotationOf` (1) +- `LinkObj` is a resource linking together several other resources of different classes. It can be used in the XML file + with the [<link> tag](dsp-tools-xmlupload.md#link). It automatically has the following predefined properties: + - `hasComment` (1-n) + - `hasLinkTo` (1-n) +- A `Region` resource defines a region of interest (ROI) in an image. It can be used in the XML file with the + [<region> tag](dsp-tools-xmlupload.md#region). It automatically has the following predefined properties: + - `hasColor` (1) + - `isRegionOf` (1) + - `hasGeometry` (1) + - `hasComment` (1-n) + +There are some DSP base properties that are used directly in the above resource classes. Some of them can also be +subclassed and used in a resource class. + +- `hasLinkTo`: a link to another resource + - can be subclassed ([hasLinkTo Property](#haslinkto-property)) + - can be used directly in the XML data file in the [<link> tag](dsp-tools-xmlupload.md#link) +- `hasColor`: Defines a color value. + - can be subclassed ([ColorValue](#colorvalue)) + - can be used directly in the XML data file in the [<region> tag](dsp-tools-xmlupload.md#region) +- `hasComment`: Defines a standard comment. + - can be subclassed ([hasComment Property](#hascomment-property)) + - can be used directly in the XML data file in the [<region> tag](dsp-tools-xmlupload.md#region) or + [<link> tag](dsp-tools-xmlupload.md#link) +- `hasGeometry`: Defines a geometry value (a JSON describing a polygon, circle or rectangle). + - must be used directly in the XML data file in the [<region> tag](dsp-tools-xmlupload.md#region) +- `isRegionOf`: A special variant of `hasLinkTo`. It means that the given resource class is a region of interest in an image. + - must be used directly in the XML data file in the [<region> tag](dsp-tools-xmlupload.md#region) +- `isAnnotationOf`: A special variant of `hasLinkTo`. It means that the given resource class is an annotation to another + resource class. + - must be used directly in the XML data file in the [<annotation> tag](dsp-tools-xmlupload.md#annotation) diff --git a/docs/dsp-tools-create.md b/docs/dsp-tools-create.md index 97c72ae40..05fe6a5e4 100644 --- a/docs/dsp-tools-create.md +++ b/docs/dsp-tools-create.md @@ -355,6 +355,8 @@ The `lists` element is optional. If not used, it should be omitted. The `groups` object contains groups definitions. This is used to specify the permissions a user gets. A project may define several groups such as "project-admins", "editors" etc. in order to provide their members specific permissions. +The groups that were created here are then available in the XML file in the +[<allow> sub-element](dsp-tools-xmlupload.md#the-allow-sub-element). A group definition has the following elements: diff --git a/docs/dsp-tools-xmlupload.md b/docs/dsp-tools-xmlupload.md index 4f423f4ab..ab3cc9243 100644 --- a/docs/dsp-tools-xmlupload.md +++ b/docs/dsp-tools-xmlupload.md @@ -72,8 +72,9 @@ permissions. By default, the following groups always exist, and each user belong - `Creator`: The user is the owner of the element (created the element). - `SystemAdmin`: The user is a system administrator. -In addition, more groups with arbitrary names can be created by a project admin. For referencing a group, the project -name has to be prepended before the group name, separated by a colon, e.g. `dsp-test:MlsEditors`. +In addition, more groups with arbitrary names can be created by a project admin. See [here](dsp-tools-create.md#groups) +how to create a group in an ontology JSON file. For referencing a group, the project name has to be prepended to the +group name, separated by a colon, e.g. `dsp-test:MlsEditors`. A `` element contains the permissions given to the selected groups and is called a _permission set_. It has a mandatory attribute `id` and must contain at least one `` element: @@ -185,9 +186,13 @@ resource: ``` - - With `permissions="prop-default"`, a logged-in user who is not member of the project has `V` rights on the image: Normal view. - - With `permissions="prop-restricted"`, a logged-in user who is not member of the project has `RV` rights on the image: Blurred image. - - With a blank `` tag, a logged-in user who is not member of the project has no rights on the image: No view possible. +To take `KnownUser` as example: + - With `permissions="prop-default"`, a logged-in user who is not member of the project (`KnownUser`) has `V` rights + on the image: Normal view. + - With `permissions="prop-restricted"`, a logged-in user who is not member of the project (`KnownUser`) has `RV` + rights on the image: Blurred image. + - With a blank `` tag, a logged-in user who is not member of the project (`KnownUser`) has no rights on + the image: No view possible. Only users from `ProjectAdmin` upwards are able to look at the image. ## Describing resources with the <resource> element @@ -227,10 +232,6 @@ Example for a property element of type text (``) with two value eleme ``` -| ⚠ Look out | -|:---------------------------------------------------------------------------------------------------------------------------------------------------| -| In case of a cardinality 1-n, multiple `` tags have to be created inside the `` tag (do not use multiple `` tags). | - The following property elements exist: - ``: contains a path to a file (if the resource is a multimedia resource) @@ -264,7 +265,7 @@ Note: Attributes: -- `permissions` : Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions` : Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) Example: @@ -288,7 +289,7 @@ The `` element must contain the string "true" or "false", or the numera Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -322,7 +323,7 @@ followed by 3 or 6 hex numerals. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) A property with two color values would be defined as follows: @@ -362,7 +363,7 @@ it _month_, if also the month is omitted, the precision is _year_. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -395,7 +396,7 @@ The `` element contains a decimal number. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -462,7 +463,7 @@ Example of a `` element: Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) @@ -482,7 +483,7 @@ Contains a valid [geonames.org](http://geonames.org) ID. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example (city of Vienna): @@ -512,7 +513,7 @@ The `` element references a node in a (pull-down or hierarchical) list. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -543,7 +544,7 @@ References an [iconclass.org](https://iconclass.org) ID. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Usage: @@ -570,7 +571,7 @@ The `` element contains an integer value. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -600,7 +601,7 @@ seconds, and the places after the decimal points are fractions of a second. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -630,7 +631,7 @@ resources, `xmlupload --incremental` has to be used. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -662,7 +663,7 @@ The `` element has the following attributes: - `utf8`: The element describes a simple text without markup. The text is a simple UTF-8 string. - `xml`: The element describes a complex text containing markup. It must follow the XML format as defined by the [DSP standard mapping](https://docs.knora.org/03-apis/api-v1/xml-to-standoff-mapping/). -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) There are two variants of text: Simple (UTF8) and complex (XML). Within a text property, multiple simple and @@ -746,7 +747,7 @@ The timezone is defined as follows: Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -781,7 +782,7 @@ The `` element contains a syntactically valid URI. Attributes: -- `permissions`: Permission ID (optional, but if omitted, very restricted default permissions apply) +- `permissions`: Permission ID (optional, but if omitted, users who are lower than a `ProjectAdmin` have no permissions at all, not even view rights) - `comment`: a comment for this specific value (optional) Example: @@ -793,6 +794,103 @@ Example: ``` +## DSP base resources / base properties to be used directly in the XML file +There is a number of base resources and base properties that must not be subclassed in a project ontology. They are +directly available in the XML data file. Please have in mind that built-in names of the knora-base ontology must be used +without prepended colon. +See also [the related part of the ontology documentation](dsp-tools-create-ontologies.md#dsp-base-resources-base-properties-to-be-used-directly-in-the-xml-file). + +### `` +`` is an annotation to another resource of any class. It must have the following predefined properties: + +- `hasComment` (1-n) +- `isAnnotationOf` (1) + +Example: +```xml + + + This is an annotation to a resource. + + + img_1 + + +``` + +Technical note: An `` is in fact a ``. But it is mandatory to use the +shortcut, so that the XML file can be validated more precisely. + +### `` +A `` resource defines a region of interest (ROI) in an image. It must have the following predefined properties: + +- `hasColor` (1) +- `isRegionOf` (1) +- `hasGeometry` (1) +- `hasComment` (1-n) + +There are three types of Geometry shapes (rectangle, circle, polygon), but only the rectangle is implemented. + +Example: +```xml + + + #5d1f1e + + + img_1 + + + + { + "status": "active", + "type": "rectangle", + "lineColor": "#ff3333", + "lineWidth": 2, + "points": [ + {"x":0.08098591549295775,"y":0.16741071428571427}, + {"x":0.739436619718309900,"y":0.72991071428571430} + ], + "original_index": 0 + } + + + + This is a rectangle-formed region of interest. + + +``` + +Technical note: A `` is in fact a ``. But it is mandatory to use the +shortcut, so that the XML file can be validated more precisely. + +### `` +`` is a resource linking together several other resources of different classes. It must have the following +predefined properties: + +- `hasComment` (1-n) +- `hasLinkTo` (1-n) + +Example: +```xml + + + + A link object can link together an arbitrary number of resources from any resource class. + + + + doc_001 + img_obj_5 + audio_obj_0 + + +``` + +Technical note: A `` is in fact a ``. But it is mandatory to use the +shortcut, so that the XML file can be validated more precisely. + + ## Incremental XML Upload After a successful upload of the data, an output file is written (called `id2iri_mapping_[timstamp].json`) with the @@ -985,7 +1083,7 @@ To do an incremental XML upload, one of the following procedures is recommended. Tree list node 02 - This is bold and string text! + This is bold and strong text! aa bbb cccc ddddd diff --git a/knora/dsplib/models/ontology.py b/knora/dsplib/models/ontology.py index 98fda01e0..c4ba93b2a 100644 --- a/knora/dsplib/models/ontology.py +++ b/knora/dsplib/models/ontology.py @@ -1,5 +1,6 @@ import copy import json +import re from typing import Tuple, Optional, Any, Union from urllib.parse import quote_plus @@ -416,7 +417,10 @@ def getProjectOntologies(con: Connection, project_id: str) -> list['Ontology']: @staticmethod def getOntologyFromServer(con: Connection, shortcode: str, name: str) -> 'Ontology': - result = con.get("/ontology/" + shortcode + "/" + name + "/v2" + Ontology.ALL_LANGUAGES) + if re.search(r'[0-9A-F]{4}', shortcode): + result = con.get("/ontology/" + shortcode + "/" + name + "/v2" + Ontology.ALL_LANGUAGES) + else: + result = con.get("/ontology/" + name + "/v2" + Ontology.ALL_LANGUAGES) return Ontology.fromJsonObj(con, result) def createDefinitionFileObj(self): diff --git a/knora/dsplib/models/propertyclass.py b/knora/dsplib/models/propertyclass.py index ea86a4b3e..306d266f1 100644 --- a/knora/dsplib/models/propertyclass.py +++ b/knora/dsplib/models/propertyclass.py @@ -260,9 +260,8 @@ def fromJsonObj(cls, con: Connection, context: Context, json_obj: Any) -> Any: superproperties: list[Union[None, str]] if not isinstance(superproperties_obj, list): superproperties_obj = [superproperties_obj] # make a list out of it - if superproperties_obj is not None: - superprops: list[Any] = list(filter(lambda a: a.get('@id') is not None, superproperties_obj)) - superproperties = list(map(lambda a: a['@id'], superprops)) + if superproperties_obj: + superproperties = [x['@id'] for x in superproperties_obj if x and x.get('@id')] else: superproperties = None object = WithId(json_obj.get(knora_api + ':objectType')).str() diff --git a/knora/dsplib/models/resource.py b/knora/dsplib/models/resource.py index b430d1534..1678ed34a 100644 --- a/knora/dsplib/models/resource.py +++ b/knora/dsplib/models/resource.py @@ -57,8 +57,14 @@ class ResourceInstance(Model): 'TextRepresentation' } knora_properties: set[str] = { + "knora-api:hasLinkTo", + "knora-api:hasColor", + "knora-api:hasComment", + "knora-api:hasGeometry", "knora-api:isPartOf", - "knora-api:seqnum", + "knora-api:isRegionOf", + "knora-api:isAnnotationOf", + "knora-api:seqnum" } _iri: Optional[str] _ark: Optional[str] @@ -314,19 +320,6 @@ def print(self): print(name, ':', str(val)) -# ToDo: special resourceclasses and properties of knora-api -# -# - knora-api:isPartOf -> Object: knora-api:Resource -# - knora-api:author -> Object: knora-api:User -# - knora-api:seqnum -> Object: knora-api:IntValue -# - knora-api:hasComment -> Object: knora-api:TextValue -# -# knora-api:Region: IS a resource -# - knora-api:hasGeometry -# - knora-api:isRegionOf -# - knora-api:hasColor -# - knora-api:hasComment - @strict class ResourceInstanceFactory: _con: Connection @@ -336,29 +329,26 @@ class ResourceInstanceFactory: _ontoname2iri = dict[str, str] _context: Context - def __init__(self, - con: Connection, - projident: str): + def __init__(self, con: Connection, projident: str) -> None: self._con = con if re.match("^[0-9a-fA-F]{4}$", projident): - project = Project(con=self._con, shortcode=projident) + project = Project(con=con, shortcode=projident) elif re.match("^[\\w-]+$", projident): - project = Project(con=self._con, shortname=projident) + project = Project(con=con, shortname=projident) elif re.match("^(http)s?://([\\w\\.\\-~]+:?\\d{,4})(/[\\w\\-~]+)+$", projident): - project = Project(con=self._con, shortname=projident) + project = Project(con=con, shortname=projident) else: raise BaseError("Invalid project identification!") self._project = project.read() - tmp = ListNode.getAllLists(con=self._con, project_iri=self._project.id) - self._lists = [] - for rnode in tmp: - self._lists.append(rnode.getAllNodes()) + self._lists = [x.getAllNodes() for x in ListNode.getAllLists(con=con, project_iri=self._project.id)] - tmp_ontologies = Ontology.getProjectOntologies(con, self._project.id) - shared_project = Project(con=self._con, shortcode="0000").read() - shared_ontologies = Ontology.getProjectOntologies(con, shared_project.id) + tmp_ontologies = Ontology.getProjectOntologies(con=con, project_id=self._project.id) + shared_project = Project(con=con, shortcode="0000").read() + shared_ontologies = Ontology.getProjectOntologies(con=con, project_id=shared_project.id) tmp_ontologies.extend(shared_ontologies) + knora_api_onto = [x for x in Ontology.getAllOntologies(con=con) if x.name=="knora-api"][0] + tmp_ontologies.append(knora_api_onto) self._ontoname2iri = {x.name: x.id for x in tmp_ontologies} ontology_ids = [x.id for x in tmp_ontologies] @@ -417,11 +407,27 @@ def get_resclass_type(self, prefixedresclass: str) -> Type: 'knora-api:LinkValue': LinkValue, } for propname, has_property in resclass.has_properties.items(): - if propname == "knora-api:isPartOf": + if any([ + propname == "knora-api:isAnnotationOf", + propname == "knora-api:isRegionOf", + propname == "knora-api:isPartOf", + propname == "knora-api:hasLinkTo" + ]): valtype = LinkValue props[propname] = Propinfo(valtype=valtype, cardinality=has_property.cardinality, gui_order=has_property.gui_order) + + elif propname == "knora-api:hasGeometry": + valtype = GeomValue + props[propname] = Propinfo(valtype=valtype, + cardinality=has_property.cardinality, + gui_order=has_property.gui_order) + elif propname == "knora-api:hasColor": + valtype = ColorValue + props[propname] = Propinfo(valtype=valtype, + cardinality=has_property.cardinality, + gui_order=has_property.gui_order) elif propname == "knora-api:seqnum": valtype = IntValue props[propname] = Propinfo(valtype=valtype, diff --git a/knora/dsplib/models/value.py b/knora/dsplib/models/value.py index bf98020c7..4d0f63f63 100644 --- a/knora/dsplib/models/value.py +++ b/knora/dsplib/models/value.py @@ -401,7 +401,7 @@ def __init__(self, vark_url: Optional[str] = None): self._value = value if isinstance(value, str): - m = re.match('^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$', value) + m = re.match(r'^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$', value) if m: self._value = float(value) else: diff --git a/knora/dsplib/models/xmlproperty.py b/knora/dsplib/models/xmlproperty.py index b22219aa7..d276e8d32 100644 --- a/knora/dsplib/models/xmlproperty.py +++ b/knora/dsplib/models/xmlproperty.py @@ -31,7 +31,7 @@ def __init__(self, node: etree.Element, valtype: str, default_ontology: Optional # replace an empty namespace with the default ontology name self._name = default_ontology + ':' + tmp_prop_name[1] else: - self._name = 'knora-admin:' + tmp_prop_name[0] + self._name = 'knora-api:' + tmp_prop_name[0] listname = node.attrib.get('list') # safe the list name if given (only for lists) self._valtype = valtype self._values = [] diff --git a/knora/dsplib/models/xmlresource.py b/knora/dsplib/models/xmlresource.py index f68ed1a7c..fe8eb4948 100644 --- a/knora/dsplib/models/xmlresource.py +++ b/knora/dsplib/models/xmlresource.py @@ -45,7 +45,7 @@ def __init__(self, node: etree.Element, default_ontology: str) -> None: # replace an empty namespace with the default ontology name self._restype = default_ontology + ':' + tmp_res_type[1] else: - self._restype = 'knora-admin:' + tmp_res_type[0] + self._restype = 'knora-api:' + tmp_res_type[0] self._permissions = node.attrib.get("permissions") self._bitstream = None self._properties = [] diff --git a/knora/dsplib/schemas/data.xsd b/knora/dsplib/schemas/data.xsd index 4e1c9850d..a863466b6 100644 --- a/knora/dsplib/schemas/data.xsd +++ b/knora/dsplib/schemas/data.xsd @@ -1,6 +1,6 @@ - @@ -65,7 +65,7 @@ - + @@ -75,7 +75,7 @@ - + @@ -85,7 +85,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -105,7 +105,7 @@ - + @@ -115,17 +115,7 @@ - - - - - - - - - - - + @@ -135,7 +125,7 @@ - + @@ -145,7 +135,7 @@ - + @@ -155,7 +145,7 @@ - + @@ -165,7 +155,7 @@ - + @@ -175,7 +165,7 @@ - + @@ -185,7 +175,7 @@ - + @@ -195,21 +185,21 @@ - + - - + + @@ -242,14 +232,6 @@ - - - - - - - - @@ -267,14 +249,6 @@ - - - - - - - - @@ -339,40 +313,60 @@ - - - - - - - + + + + + + - - - - - + + + + + + - - - - - + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + - - + + - + + + @@ -380,22 +374,14 @@ - - - @@ -414,6 +400,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -454,12 +475,32 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/knora/dsplib/schemas/ontology.json b/knora/dsplib/schemas/ontology.json index ac537eb90..ea4558041 100644 --- a/knora/dsplib/schemas/ontology.json +++ b/knora/dsplib/schemas/ontology.json @@ -23,6 +23,21 @@ "pattern": "^([a-zA-Z_][\\w.-]*)?:([-\\w]+)$", "additionalProperties": false }, + "baseresource": { + "type": "string", + "enum": [ + "Resource", + "Region", + "Representation", + "StillImageRepresentation", + "TextRepresentation", + "AudioRepresentation", + "DDDRepresentation", + "DocumentRepresentation", + "MovingImageRepresentation", + "ArchiveRepresentation" + ] + }, "label": { "$ref": "#/definitions/langstring" }, @@ -48,19 +63,7 @@ ], "oneOf": [ { - "enum": [ - "Resource", - "StillImageRepresentation", - "TextRepresentation", - "AudioRepresentation", - "DDDRepresentation", - "DocumentRepresentation", - "MovingImageRepresentation", - "ArchiveRepresentation", - "Annotation", - "LinkObj", - "Region" - ] + "$ref": "#/definitions/baseresource" }, { "$ref": "#/definitions/prefixedname" @@ -260,10 +263,7 @@ "hasLinkTo", "hasColor", "hasComment", - "hasGeometry", "isPartOf", - "isRegionOf", - "isAnnotationOf", "hasRepresentation", "seqnum" ] @@ -286,20 +286,18 @@ "ColorValue", "DateValue", "DecimalValue", - "GeomValue", "GeonameValue", "IntValue", "BooleanValue", "TimeValue", "UriValue", "IntervalValue", - "ListValue", - "Region", - "Resource", - "Representation", - "Annotation" + "ListValue" ] }, + { + "$ref": "#/definitions/baseresource" + }, { "$ref": "#/definitions/prefixedname" } @@ -316,7 +314,6 @@ "enum": [ "Colorpicker", "Date", - "Geometry", "Geonames", "Interval", "TimeStamp", @@ -436,7 +433,8 @@ "oneOf": [ { "enum": [ - "hasRepresentation" + "hasRepresentation", + "hasLinkTo" ] }, { @@ -619,41 +617,6 @@ } } }, - { - "if": { - "properties": { - "object": { - "const": "GeomValue" - } - } - }, - "then": { - "properties": { - "super": { - "type": "array", - "items": { - "type": "string", - "oneOf": [ - { - "enum": [ - "hasGeometry" - ] - }, - { - "$ref": "#/definitions/prefixedname" - } - ] - } - }, - "gui_element": { - "enum": [ - "Geometry", - "SimpleText" - ] - } - } - } - }, { "if": { "properties": { @@ -1065,7 +1028,6 @@ "Richtext", "Date", "TimeStamp", - "Geometry", "Geonames", "Checkbox", "Interval" diff --git a/knora/dsplib/schemas/properties-only.json b/knora/dsplib/schemas/properties-only.json index ce3dd39df..72cb5e52d 100644 --- a/knora/dsplib/schemas/properties-only.json +++ b/knora/dsplib/schemas/properties-only.json @@ -46,10 +46,7 @@ "hasLinkTo", "hasColor", "hasComment", - "hasGeometry", "isPartOf", - "isRegionOf", - "isAnnotationOf", "hasRepresentation", "seqnum" ] @@ -72,7 +69,6 @@ "ColorValue", "DateValue", "DecimalValue", - "GeomValue", "GeonameValue", "IntValue", "BooleanValue", @@ -80,10 +76,8 @@ "UriValue", "IntervalValue", "ListValue", - "Region", "Resource", - "Representation", - "Annotation" + "Representation" ] }, { @@ -102,7 +96,6 @@ "enum": [ "Colorpicker", "Date", - "Geometry", "Geonames", "Interval", "TimeStamp", @@ -406,41 +399,6 @@ } } }, - { - "if": { - "properties": { - "object": { - "const": "GeomValue" - } - } - }, - "then": { - "properties": { - "super": { - "type": "array", - "items": { - "type": "string", - "oneOf": [ - { - "enum": [ - "hasGeometry" - ] - }, - { - "$ref": "#/definitions/prefixedname" - } - ] - } - }, - "gui_element": { - "enum": [ - "Geometry", - "SimpleText" - ] - } - } - } - }, { "if": { "properties": { @@ -852,7 +810,6 @@ "Richtext", "Date", "TimeStamp", - "Geometry", "Geonames", "Checkbox", "Interval" diff --git a/knora/dsplib/schemas/resources-only.json b/knora/dsplib/schemas/resources-only.json index b9ee60b51..c87cf448d 100644 --- a/knora/dsplib/schemas/resources-only.json +++ b/knora/dsplib/schemas/resources-only.json @@ -44,10 +44,7 @@ "DDDRepresentation", "DocumentRepresentation", "MovingImageRepresentation", - "ArchiveRepresentation", - "Annotation", - "LinkObj", - "Region" + "ArchiveRepresentation" ] }, { diff --git a/knora/dsplib/utils/xml_upload.py b/knora/dsplib/utils/xml_upload.py index a00b19423..633be9593 100644 --- a/knora/dsplib/utils/xml_upload.py +++ b/knora/dsplib/utils/xml_upload.py @@ -211,6 +211,39 @@ def _convert_ark_v0_to_resource_iri(ark: str) -> str: return "http://rdfh.ch/" + project_id + "/" + dsp_uuid +def parse_xml_file(input_file: str) -> etree.ElementTree: + """ + Parse an XML file with DSP-conform data, remove namespace URI from the elements' names, and transform the special + tags , , and to their technically correct form , + , and . + + Args: + input_file: path to the XML file + + Returns: + the parsed etree.ElementTree + """ + tree = etree.parse(input_file) + for elem in tree.getiterator(): + if not (isinstance(elem, etree._Comment) or isinstance(elem, etree._ProcessingInstruction)): + # remove namespace URI in the element's name + elem.tag = etree.QName(elem).localname + if elem.tag == "annotation": + elem.attrib["restype"] = "Annotation" + elem.tag = "resource" + elif elem.tag == "link": + elem.attrib["restype"] = "LinkObj" + elem.tag = "resource" + elif elem.tag == "region": + elem.attrib["restype"] = "Region" + elem.tag = "resource" + + # remove unused namespace declarations + etree.cleanup_namespaces(tree) + + return tree + + def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: str, sipi: str, verbose: bool, validate_only: bool, incremental: bool) -> bool: """ @@ -249,13 +282,7 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s proj_context = ProjectContext(con=con) sipi_server = Sipi(sipi, con.get_token()) - # parse the XML file - tree = etree.parse(input_file) - for elem in tree.getiterator(): - if not (isinstance(elem, etree._Comment) or isinstance(elem, etree._ProcessingInstruction)): - elem.tag = etree.QName(elem).localname # remove namespace URI in the element's name - etree.cleanup_namespaces(tree) # remove unused namespace declarations - + tree = parse_xml_file(input_file) root = tree.getroot() default_ontology = root.attrib['default-ontology'] shortcode = root.attrib['shortcode'] @@ -270,9 +297,9 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s resources.append(XMLResource(child, default_ontology)) # get the project information and project ontology from the server - project = ResourceInstanceFactory(con, shortcode) + res_inst_factory = ResourceInstanceFactory(con, shortcode) permissions_lookup: dict[str, Permissions] = {s: perm.get_permission_instance() for s, perm in permissions.items()} - resclass_name_2_type: dict[str, type] = {s: project.get_resclass_type(s) for s in project.get_resclass_names()} + resclass_name_2_type: dict[str, type] = {s: res_inst_factory.get_resclass_type(s) for s in res_inst_factory.get_resclass_names()} # temporarily remove circular references, but only if not an incremental upload if not incremental: @@ -309,7 +336,6 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s # write log files success = True timestamp_str = datetime.now().strftime("%Y%m%d-%H%M%S") - _write_id2iri_mapping(input_file, id2iri_mapping, timestamp_str) if len(nonapplied_xml_texts) > 0: _write_stashed_xml_texts(nonapplied_xml_texts, timestamp_str) success = False @@ -319,6 +345,9 @@ def xml_upload(input_file: str, server: str, user: str, password: str, imgdir: s if failed_uploads: print(f"Could not upload the following resources: {failed_uploads}") success = False + if success: + print("All resources have successfully been uploaded.") + _write_id2iri_mapping(input_file, id2iri_mapping, timestamp_str) return success @@ -378,7 +407,7 @@ def _upload_resources( # create the resource in DSP resclass_type = resclass_name_2_type[resource.restype] properties = resource.get_propvals(id2iri_mapping, permissions_lookup) - resclass_instance: ResourceInstance = _try_network_action( + resource_instance: ResourceInstance = _try_network_action( method=resclass_type, kwargs={ 'con': con, @@ -390,12 +419,12 @@ def _upload_resources( }, terminal_output_on_failure=f"ERROR while trying to create resource '{resource.label}' ({resource.id})." ) - if not resclass_instance: + if not resource_instance: failed_uploads.append(resource.id) continue created_resource: ResourceInstance = _try_network_action( - object=resclass_instance, + object=resource_instance, method='create', terminal_output_on_failure=f"ERROR while trying to create resource '{resource.label}' ({resource.id})." ) @@ -432,16 +461,16 @@ def _upload_stashed_xml_texts( if resource.id not in id2iri_mapping: # resource could not be uploaded to DSP, so the stash cannot be uploaded either continue - print(f' Upload XML text(s) of resource "{resource.id}"...') res_iri = id2iri_mapping[resource.id] existing_resource = _try_network_action( object=con, method='get', kwargs={'path': f'/v2/resources/{quote_plus(res_iri)}'}, - terminal_output_on_failure=f'ERROR while uploading the xml texts of resource "{resource.id}"' + terminal_output_on_failure=f' ERROR while retrieving resource "{resource.id}" from DSP server' ) if not existing_resource: continue + print(f' Upload XML text(s) of resource "{resource.id}"...') for link_prop, hash_to_value in link_props.items(): existing_values = existing_resource[link_prop.name] if not isinstance(existing_values, list): @@ -485,7 +514,7 @@ def _upload_stashed_xml_texts( object=con, method='put', kwargs={'path': '/v2/values', 'jsondata': jsondata}, - terminal_output_on_failure=f'ERROR while uploading the xml text of "{link_prop.name}" ' + terminal_output_on_failure=f' ERROR while uploading the xml text of "{link_prop.name}" ' f'of resource "{resource.id}"' ) if not response: @@ -536,9 +565,16 @@ def _upload_stashed_resptr_props( if resource.id not in id2iri_mapping: # resource could not be uploaded to DSP, so the stash cannot be uploaded either continue - print(f' Upload resptrs of resource "{resource.id}"...') res_iri = id2iri_mapping[resource.id] - existing_resource = con.get(path=f'/v2/resources/{quote_plus(res_iri)}') + existing_resource = _try_network_action( + object=con, + method='get', + kwargs={'path': f'/v2/resources/{quote_plus(res_iri)}'}, + terminal_output_on_failure=f' ERROR while retrieving resource "{resource.id}" from DSP server' + ) + if not existing_resource: + continue + print(f' Upload resptrs of resource "{resource.id}"...') for link_prop, resptrs in prop_2_resptrs.items(): for resptr in resptrs.copy(): jsonobj = { @@ -547,7 +583,9 @@ def _upload_stashed_resptr_props( f'{link_prop.name}Value': { '@type': 'knora-api:LinkValue', 'knora-api:linkValueHasTargetIri': { - '@id': id2iri_mapping[resptr] + # if target doesn't exist in DSP, send the (invalid) resource ID of target to DSP, which + # will produce an understandable error message + '@id': id2iri_mapping.get(resptr, resptr) } }, '@context': existing_resource['@context'] @@ -615,11 +653,21 @@ def _try_network_action( print(f'{datetime.now().isoformat()}: Try reconnecting to DSP server, next attempt in {2 ** i} seconds...') time.sleep(2 ** i) continue - except BaseError: - print(terminal_output_on_failure) + except BaseError as err: + if hasattr(err, 'message'): + err_message = err.message + else: + err_message = str(err).replace('\n', ' ') + err_message = err_message[:150] if len(err_message) > 150 else err_message + print(f"{terminal_output_on_failure} Error message: {err_message}") return None - except Exception: - print(terminal_output_on_failure) + except Exception as exc: + if hasattr(exc, 'message'): + exc_message = exc.message + else: + exc_message = str(exc).replace('\n', ' ') + exc_message = exc_message[:150] if len(exc_message) > 150 else exc_message + print(f"{terminal_output_on_failure} Error message: {exc_message}") return None print(terminal_output_on_failure) return None diff --git a/test/e2e/test_tools.py b/test/e2e/test_tools.py index 28135e55b..210020670 100644 --- a/test/e2e/test_tools.py +++ b/test/e2e/test_tools.py @@ -46,7 +46,7 @@ def test_get(self) -> None: server=self.server, user=self.user, password='test', - verbose=False) + verbose=True) with open('testdata/tmp/_test-onto.json') as f: onto_json_str = f.read() @@ -169,7 +169,7 @@ def test_create_ontology(self) -> None: server=self.server, user_mail=self.user, password='test', - verbose=False, + verbose=True, dump=False) def test_xml_upload(self) -> None: @@ -180,7 +180,7 @@ def test_xml_upload(self) -> None: password=self.password, imgdir=self.imgdir, sipi=self.sipi, - verbose=False, + verbose=True, validate_only=False, incremental=False) self.assertTrue(result) @@ -196,7 +196,7 @@ def test_xml_upload(self) -> None: id_to_iri(xml_file='testdata/test-id2iri-data.xml', json_file=mapping_file, out_file=id2iri_replaced_xml_filename, - verbose=False) + verbose=True) self.assertEqual(os.path.isfile(id2iri_replaced_xml_filename), True) result = xml_upload( @@ -206,7 +206,7 @@ def test_xml_upload(self) -> None: password=self.password, imgdir=self.imgdir, sipi=self.sipi, - verbose=False, + verbose=True, validate_only=False, incremental=True ) diff --git a/test/unittests/test_excel_to_properties.py b/test/unittests/test_excel_to_properties.py index fc7311dcd..0bc548a03 100644 --- a/test/unittests/test_excel_to_properties.py +++ b/test/unittests/test_excel_to_properties.py @@ -21,23 +21,23 @@ def test_excel2json(self) -> None: # define the expected values from the excel file excel_names = ["correspondsToGenericAnthroponym", "hasAnthroponym", "hasGender", "isDesignatedAs", "hasTitle", - "hasStatus", "hasLifeYearAmount", "hasBirthDate", "hasGeometry", "hasRepresentation", + "hasStatus", "hasLifeYearAmount", "hasBirthDate", "hasRepresentation", "hasRemarks", "hasTerminusPostQuem", "hasGND", "hasColor", "hasDecimal", "hasTime", "hasInterval", "hasBoolean", "hasGeoname", "partOfDocument"] excel_supers = [["hasLinkTo"], ["hasValue", "dcterms:creator"], ["hasValue"], ["hasValue"], ["hasLinkTo"], - ["hasValue"], ["hasValue"], ["hasValue"], ["hasGeometry"], ["hasRepresentation"], + ["hasValue"], ["hasValue"], ["hasValue"], ["hasRepresentation"], ["hasValue", "dcterms:description"], ["hasValue"],["hasValue"], ["hasColor"], ["hasValue"], ["hasValue"], ["hasValue"], ["hasValue"], ["hasValue"], ["isPartOf"]] excel_objects = [":GenericAnthroponym", "TextValue", "ListValue", "ListValue", ":Titles", "ListValue", - "IntValue", "DateValue", "GeomValue", "Representation", "TextValue", "DateValue", "UriValue", + "IntValue", "DateValue", "Representation", "TextValue", "DateValue", "UriValue", "ColorValue", "DecimalValue", "TimeValue", "IntervalValue", "BooleanValue", "GeonameValue", ":Documents"] excel_labels = dict() - excel_labels["de"] = ["", "only German", "", "", "", "", "", "", "Geometrisches Objekt", + excel_labels["de"] = ["", "only German", "", "", "", "", "", "", "hat eine Multimediadatei", "", "", "GND", "Farbe", "Dezimalzahl", "Zeit", "Zeitintervall", "Bool'sche Variable", "Link zu Geonames", "ist Teil eines Dokuments"] - excel_labels["it"] = ["", "", "", "only Italian", "", "", "", "", "", "", "", "", "GND", "", "", "", "", "", "", ""] + excel_labels["it"] = ["", "", "", "only Italian", "", "", "", "", "", "", "", "GND", "", "", "", "", "", "", ""] excel_comments = dict() excel_comments["comment_fr"] = ["J'avais déjà examiné plusieurs propriétés quand, un jour, le notaire, qui me " @@ -46,7 +46,7 @@ def test_excel2json(self) -> None: "Je n'en sais rien du tout ; mais si vous voulez la voir, monsieur, voici les " "indications précises pour la trouver.", "Vous devrez arranger l'affaire avec le curé du village de --.\"", - "Un étrange hasard m'a mis en possession de ce journal.", "", "", "only French", "", "", + "Un étrange hasard m'a mis en possession de ce journal.", "", "", "only French", "", "", "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 :", "Gemeinsame Normdatei", "", "Chiffre décimale", "Temps", "", "", "", ""] @@ -54,12 +54,12 @@ def test_excel2json(self) -> None: "Uno strano caso mi mise in possesso di questo diario.", "Non ne so nulla; ma se volete vederla, signore, eccovi le indicazioni precise per trovarla.", "Dovrete organizzare l'affare con il curato del villaggio di --\".", - "Uno strano caso mi mise in possesso di questo diario.", "", "", "", "only Italian", + "Uno strano caso mi mise in possesso di questo diario.", "", "", "", "", "", "Avevo già visto diverse proprietà quando un giorno il notaio,", "Gemeinsame Normdatei", "", "", "", "", "", "", ""] excel_gui_elements = ["Searchbox", "Richtext", "List", "Radio", "Searchbox", "List", "Spinbox", "Date", - "SimpleText", "Searchbox", "Textarea", "Date", "SimpleText", "Colorpicker", "Slider", + "Searchbox", "Textarea", "Date", "SimpleText", "Colorpicker", "Slider", "TimeStamp", "Interval", "Checkbox", "Geonames", "Searchbox"] excel_gui_attributes_hasGender = {"hlist": "gender"} diff --git a/test/unittests/test_excel_to_resource.py b/test/unittests/test_excel_to_resource.py index 04a63db2e..16e583dfe 100644 --- a/test/unittests/test_excel_to_resource.py +++ b/test/unittests/test_excel_to_resource.py @@ -44,25 +44,23 @@ def test_excel2json(self) -> None: # define the expected values from the excel file excel_names = ["Owner", "Title", "GenericAnthroponym", "FamilyMember", "MentionedPerson", "Alias", "Image", - "Video", "Audio", "ZIP", "PDFDocument", "Annotation", "LinkObject", "RegionOfImage"] + "Video", "Audio", "ZIP", "PDFDocument"] excel_supers = [["Resource", "dcterms:fantasy"], ["Resource"], ["Resource"], ["Resource"], ["Resource"], ["Resource"], ["StillImageRepresentation", "dcterms:image"], ["MovingImageRepresentation"], - ["AudioRepresentation"], ["ArchiveRepresentation"], ["DocumentRepresentation"], ["Annotation"], - ["LinkObj"], ["Region"]] + ["AudioRepresentation"], ["ArchiveRepresentation"], ["DocumentRepresentation"]] excel_labels = dict() excel_labels["en"] = ["Owner", "Title", "Generic anthroponym", "Family member", "Mentioned person", "Alias", - "Only English", "", "", "", "", "Annotation", "Link Object", "Region of an image"] + "Only English", "", "", "", ""] excel_labels["rm"] = ["Rumantsch", "Rumantsch", "Rumantsch", "Rumantsch", "Rumantsch", "Rumantsch", "", "", "", - "", "Only Rumantsch", "", "", ""] + "", "Only Rumantsch"] excel_labels_of_image = {"en": "Only English"} excel_comments = dict() excel_comments["comment_de"] = ["Ein seltsamer Zufall brachte mich in den Besitz dieses Tagebuchs.", "", - "Only German", "", "", "", "Bild", "Video", "Audio", "ZIP", "PDF-Dokument", - "Annotation", "Linkobjekt", ""] + "Only German", "", "", "", "Bild", "Video", "Audio", "ZIP", "PDF-Dokument"] excel_comments["comment_fr"] = ["Un étrange hasard m'a mis en possession de ce journal.", "", "", "Only French", - "", "", "", "", "", "", "", "", "", ""] + "", "", "", "", "", "", ""] excel_comments_of_image = {"en": "Image", "de": "Bild"} excel_first_class_properties = [":hasAnthroponym", ":isOwnerOf", ":correspondsToGenericAnthroponym", ":hasAlias", diff --git a/test/unittests/test_id_to_iri.py b/test/unittests/test_id_to_iri.py index 581c75d0f..3eee43f07 100644 --- a/test/unittests/test_id_to_iri.py +++ b/test/unittests/test_id_to_iri.py @@ -3,8 +3,7 @@ import unittest import os -from lxml import etree - +from knora.dsplib.utils.xml_upload import parse_xml_file from knora.dsplib.utils.id_to_iri import id_to_iri @@ -15,7 +14,7 @@ def setUp(self) -> None: """Is executed before each test""" os.makedirs('testdata/tmp', exist_ok=True) - def test_invalid_xml_file_name(self): + def test_invalid_xml_file_name(self) -> None: with self.assertRaises(SystemExit) as cm: id_to_iri(xml_file='test.xml', json_file='testdata/test-id2iri-mapping.json', @@ -24,7 +23,7 @@ def test_invalid_xml_file_name(self): self.assertEqual(cm.exception.code, 1) - def test_invalid_json_file_name(self): + def test_invalid_json_file_name(self) -> None: with self.assertRaises(SystemExit) as cm: id_to_iri(xml_file='testdata/test-id2iri-data.xml', json_file='test.json', @@ -33,22 +32,13 @@ def test_invalid_json_file_name(self): self.assertEqual(cm.exception.code, 1) - def test_replace_id_with_iri(self): + def test_replace_id_with_iri(self) -> None: id_to_iri(xml_file='testdata/test-id2iri-data.xml', json_file='testdata/test-id2iri-mapping.json', out_file=self.out_file, verbose=True) - tree = etree.parse(self.out_file) - - for elem in tree.getiterator(): - # skip comments and processing instructions as they do not have namespaces - if not ( - isinstance(elem, etree._Comment) - or isinstance(elem, etree._ProcessingInstruction) - ): - # remove namespace declarations - elem.tag = etree.QName(elem).localname + tree = parse_xml_file(self.out_file) resource_elements = tree.xpath("/knora/resource/resptr-prop/resptr") result = [] diff --git a/test/unittests/test_xmlupload.py b/test/unittests/test_xmlupload.py index 2bf469006..a6ee41a53 100644 --- a/test/unittests/test_xmlupload.py +++ b/test/unittests/test_xmlupload.py @@ -4,7 +4,7 @@ from lxml import etree from knora.dsplib.models.helpers import BaseError -from knora.dsplib.utils.xml_upload import _convert_ark_v0_to_resource_iri, _remove_circular_references +from knora.dsplib.utils.xml_upload import _convert_ark_v0_to_resource_iri, _remove_circular_references, parse_xml_file from knora.dsplib.models.xmlresource import XMLResource @@ -34,11 +34,7 @@ def test_convert_ark_v0_to_resource_iri(self) -> None: def test_remove_circular_references(self) -> None: # create a list of XMLResources from the test data file - tree = etree.parse('testdata/test-data.xml') - for elem in tree.getiterator(): - if not (isinstance(elem, etree._Comment) or isinstance(elem, etree._ProcessingInstruction)): - elem.tag = etree.QName(elem).localname # remove namespace URI in the element's name - etree.cleanup_namespaces(tree) # remove unused namespace declarations + tree = parse_xml_file('testdata/test-data.xml') resources = [XMLResource(x, 'testonto') for x in tree.getroot() if x.tag == "resource"] # get the purged resources and the stashes from the function to be tested @@ -59,46 +55,49 @@ def test_remove_circular_references(self) -> None: # hardcode the expected values stashed_xml_texts_expected = { - 'obj_0001': { + 'test_thing_1': { 'testonto:hasRichtext': [ - '\n This isbold andstringtext! It contains links to all ' + '\n This is bold and strong text! It contains links to all ' 'resources:\n' - ' obj_0000\n' - ' obj_0001\n' - ' obj_0002\n' - ' obj_0003\n' - ' obj_0004\n' - ' obj_0005\n' - ' obj_0006\n' - ' obj_0007\n' - ' obj_0008\n' - ' obj_0009\n' - ' obj_0010\n' - ' obj_0011\n' + ' test_thing_0\n' + ' test_thing_1\n' + ' image_thing_0\n' + ' compound_thing_0\n' + ' partof_thing_1\n' + ' partof_thing_2\n' + ' partof_thing_3\n' + ' document_thing_1\n' + ' text_thing_1\n' + ' zip_thing_1\n' + ' audio_thing_1\n' + ' test_thing_2\n' ' \n ' ] }, - 'obj_0011': { + 'test_thing_2': { 'testonto:hasRichtext': [ - '\n This isbold andstringtext! It contains links to all ' + '\n This is bold and strong text! It contains links to all ' 'resources:\n' - ' obj_0000\n' - ' obj_0001\n' - ' obj_0002\n' - ' obj_0003\n' - ' obj_0004\n' - ' obj_0005\n' - ' obj_0006\n' - ' obj_0007\n' - ' obj_0008\n' - ' obj_0009\n' - ' obj_0010\n' - ' obj_0011\n' + ' test_thing_0\n' + ' test_thing_1\n' + ' image_thing_0\n' + ' compound_thing_0\n' + ' partof_thing_1\n' + ' partof_thing_2\n' + ' partof_thing_3\n' + ' document_thing_1\n' + ' text_thing_1\n' + ' zip_thing_1\n' + ' audio_thing_1\n' + ' test_thing_2\n' ' \n ' ] } } - stashed_resptr_props_expected = {'obj_0000': {'testonto:hasTestThing': ['obj_0001']}} + stashed_resptr_props_expected = { + 'test_thing_0': {'testonto:hasTestThing': ['test_thing_1']}, + 'test_thing_1': {'testonto:hasResource': ['test_thing_2', 'link_obj_1']} + } # check if the stashes are equal to the expected stashes self.assertDictEqual(stashed_resptr_props, stashed_resptr_props_expected) diff --git a/testdata/Properties.xlsx b/testdata/Properties.xlsx index ac867acaccba4ee6eb28391a052f5a80e07f5dfe..31ea63db0907dc60bac83abf23a145a06f50ba3d 100644 GIT binary patch delta 5720 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 K1~oDO0001d4eww8 delta 5821 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 Z^u(a+48Rw9T RV V - CR + M CR @@ -21,7 +21,7 @@ V V - CR + D CR @@ -32,18 +32,30 @@ + id="test_thing_0"> Dies ist ein TestThing ohne Angabe von permissions - obj_0001 + test_thing_1 - + + This is an annotation to TestthingOhnePermissions. + Second comment + + + test_thing_0 + + + + Dies ist ein einfacher Text ohne Markup @@ -51,19 +63,19 @@ - This isbold andstringtext! It contains links to all resources: - obj_0000 - obj_0001 - obj_0002 - obj_0003 - obj_0004 - obj_0005 - obj_0006 - obj_0007 - obj_0008 - obj_0009 - obj_0010 - obj_0011 + This is bold and strong text! It contains links to all resources: + test_thing_0 + test_thing_1 + image_thing_0 + compound_thing_0 + partof_thing_1 + partof_thing_2 + partof_thing_3 + document_thing_1 + text_thing_1 + zip_thing_1 + audio_thing_1 + test_thing_2 Another text without salsah-links @@ -87,20 +99,6 @@ 2.718281828459 - - - { - "status":"active", - "lineColor":"#ff3333", - "lineWidth":2, - "points":[ - {"x":0.08098591549295775,"y":0.16741071428571427}, - {"x":0.7394366197183099,"y":0.7299107142857143}], - "type":"rectangle", - "original_index":0 - } - - 5416656 @@ -114,13 +112,46 @@ b2 - obj_0000 + test_thing_0 + + + test_thing_0 + test_thing_2 + image_thing_0 + compound_thing_0 + partof_thing_1 + partof_thing_2 + partof_thing_3 + document_thing_1 + text_thing_1 + zip_thing_1 + audio_thing_1 + TestThing2Child1 + TestThing2Child2 + video_thing_1 + annotation_1 + region_1 + link_obj_1 + + + region_2 + + + image_thing_0 + video_thing_1 + document_thing_1 + text_thing_1 + zip_thing_1 + audio_thing_1 + + + video_thing_1 - Dies ist ein einfacher Text ohne Markup @@ -128,19 +159,19 @@ - This isbold andstringtext! It contains links to all resources: - obj_0000 - obj_0001 - obj_0002 - obj_0003 - obj_0004 - obj_0005 - obj_0006 - obj_0007 - obj_0008 - obj_0009 - obj_0010 - obj_0011 + This is bold and strong text! It contains links to all resources: + test_thing_0 + test_thing_1 + image_thing_0 + compound_thing_0 + partof_thing_1 + partof_thing_2 + partof_thing_3 + document_thing_1 + text_thing_1 + zip_thing_1 + audio_thing_1 + test_thing_2 Another text without salsah-links @@ -150,13 +181,37 @@ - false + false + + https://dasch.swiss + + + JULIAN:BCE:0700:BCE:0600 + + + 4711 + + + 2.718281828459 + + + 5416656 + + + 12.5:14.2 + + + #00ff00 + + + test_thing_0 + testdata/bitstreams/TEMP11.TIF @@ -164,9 +219,40 @@ + + + #5d1f1e + + + image_thing_0 + + + + { + "status": "active", + "type": "rectangle", + "lineColor": "#ff3333", + "lineWidth": 2, + "points": [ + {"x":0.080985915492957750,"y":0.16741071428571427}, + {"x":0.739436619718309900,"y":0.72991071428571430} + ], + "original_index": 0 + } + + + + + This is a rectangle-formed region of interest of an image. It is also displayed as Annotation. + + + + This is a Compoundthing @@ -175,11 +261,11 @@ testdata/bitstreams/TEMP12.TIF - obj_0003 + compound_thing_0 1 @@ -188,11 +274,11 @@ testdata/bitstreams/TEMP13.TIF - obj_0003 + compound_thing_0 2 @@ -201,11 +287,11 @@ testdata/bitstreams/TEMP14.TIF - obj_0003 + compound_thing_0 3 @@ -214,7 +300,7 @@ testdata/bitstreams/test.pdf @@ -224,7 +310,7 @@ testdata/bitstreams/test.csv @@ -234,7 +320,7 @@ testdata/bitstreams/test.zip @@ -244,7 +330,7 @@ testdata/bitstreams/clara.wav @@ -254,24 +340,80 @@ + id="video_thing_1" + permissions="res-restricted"> testdata/bitstreams/Dummy.mp4 Dummy video with music - + + + + A link object can link together an arbitrary number of resources from any resource class. + + + Second comment + + + + test_thing_2 + compound_thing_0 + partof_thing_1 + + + + Some text - + Some text + + + + This is a rectangle-formed region of interest of an image. It is also displayed as Annotation. + + + This is a second comment. + + + + + { + "status": "active", + "type": "rectangle", + "lineColor": "#ff3333", + "lineWidth": 2, + "points": [ + {"x":0.080985915492957750,"y":0.16741071428571427}, + {"x":0.739436619718309900,"y":0.72991071428571430} + ], + "original_index": 0 + } + + + + image_thing_0 + + + #5d1f1e + + + diff --git a/testdata/test-id2iri-data.xml b/testdata/test-id2iri-data.xml index ff5705581..298a5a45f 100644 --- a/testdata/test-id2iri-data.xml +++ b/testdata/test-id2iri-data.xml @@ -37,8 +37,8 @@ Text - obj_0001 - obj_0011 + test_thing_1 + test_thing_2 diff --git a/testdata/test-id2iri-mapping.json b/testdata/test-id2iri-mapping.json index f492d9194..2d9c6cbcd 100644 --- a/testdata/test-id2iri-mapping.json +++ b/testdata/test-id2iri-mapping.json @@ -1,4 +1,4 @@ { - "obj_0001": "http://rdfh.ch/082E/ylRvrg7tQI6aVpcTJbVrwg", - "obj_0011": "http://rdfh.ch/082E/JK63OpYWTDWNYVOYFN7FdQ" + "test_thing_1": "http://rdfh.ch/082E/ylRvrg7tQI6aVpcTJbVrwg", + "test_thing_2": "http://rdfh.ch/082E/JK63OpYWTDWNYVOYFN7FdQ" } diff --git a/testdata/test-onto.json b/testdata/test-onto.json index 0ca42b17c..fb5264fab 100644 --- a/testdata/test-onto.json +++ b/testdata/test-onto.json @@ -297,17 +297,6 @@ "size": 80 } }, - { - "name": "hasGeometry", - "super": [ - "hasGeometry" - ], - "object": "GeomValue", - "labels": { - "en": "Geometry" - }, - "gui_element": "Geometry" - }, { "name": "hasGeoname", "super": [ @@ -356,35 +345,68 @@ } }, { - "name": "hasTestRegion", + "name": "hasTestThing2", + "super": [ + "hasLinkTo" + ], + "object": ":TestThing2", + "labels": { + "en": "hasTestThing2" + }, + "gui_element": "Searchbox" + }, + { + "name": "hasTestThing", + "super": [ + "hasLinkTo" + ], + "object": ":TestThing", + "labels": { + "en": "hasTestThing" + }, + "gui_element": "Searchbox" + }, + { + "name": "hasResource", + "super": [ + "hasLinkTo" + ], + "object": "Resource", + "labels": { + "en": "hasResource" + }, + "gui_element": "Searchbox" + }, + { + "name": "hasRegion", "super": [ "hasLinkTo" ], "object": "Region", "labels": { - "en": "has region" + "en": "hasRegion" }, "gui_element": "Searchbox" }, { - "name": "hasTestThing2", + "name": "hasRepresentation", "super": [ "hasLinkTo" ], - "object": ":TestThing2", + "object": "Representation", "labels": { - "en": "Another thing" + "en": "hasRepresentation" }, "gui_element": "Searchbox" }, { - "name": "hasTestThing", + "name": "hasMovingImageRepresentation", "super": [ "hasLinkTo" ], - "object": ":TestThing", + "object": "MovingImageRepresentation", "labels": { - "en": "Another thing" + "en": "hasMovingImageRepresentation" }, "gui_element": "Searchbox" } @@ -437,11 +459,6 @@ "gui_order": 7, "cardinality": "0-n" }, - { - "propname": ":hasGeometry", - "gui_order": 8, - "cardinality": "0-n" - }, { "propname": ":hasGeoname", "gui_order": 9, @@ -463,14 +480,29 @@ "cardinality": "0-n" }, { - "propname": ":hasTestRegion", + "propname": ":hasTestThing2", "gui_order": 13, "cardinality": "0-n" }, { - "propname": ":hasTestThing2", + "propname": ":hasResource", "gui_order": 14, "cardinality": "0-n" + }, + { + "propname": ":hasRegion", + "gui_order": 15, + "cardinality": "0-n" + }, + { + "propname": ":hasRepresentation", + "gui_order": 16, + "cardinality": "0-n" + }, + { + "propname": ":hasMovingImageRepresentation", + "gui_order": 17, + "cardinality": "0-n" } ] },