diff --git a/Pipfile b/Pipfile index 5a89bc3f4..6e5842566 100644 --- a/Pipfile +++ b/Pipfile @@ -32,4 +32,4 @@ pytest = "*" types-requests = "*" [requires] -python_version = "3.9" +python_version = "3" diff --git a/docs/dsp-tools-create-ontologies.md b/docs/dsp-tools-create-ontologies.md index f6c722488..a59aca91c 100644 --- a/docs/dsp-tools-create-ontologies.md +++ b/docs/dsp-tools-create-ontologies.md @@ -46,10 +46,14 @@ Example of an `ontologies` object: ``` + + ## Ontologies Object in Detail The following properties can occur within each object in `ontologies`. + + ### Name (required) @@ -59,6 +63,8 @@ The following properties can occur within each object in `ontologies`. The ontology's (short) name should be in the form of a [xsd:NCNAME](https://www.w3.org/TR/xmlschema11-2/#NCName). This means a string without blanks or special characters but `-` and `_` are allowed (although not as first character). + + ### Label (required) @@ -67,6 +73,8 @@ means a string without blanks or special characters but `-` and `_` are allowed A string that provides the full name of the ontology. + + ### Properties (required) @@ -92,6 +100,8 @@ The following fields are optional: A detailed description of `properties` can be found [below](#properties-object-in-detail). + + ### Resources (required) @@ -117,8 +127,12 @@ The following field is optional: A detailed description of `resources` can be found [below](#properties-object-in-detail). + + ## Properties Object in Detail + + ### Name (required) @@ -131,6 +145,8 @@ but `-` and `_` are allowed (although not as first character). By convention, property names start with a lower case letter. + + ### Labels (required) @@ -140,6 +156,8 @@ By convention, property names start with a lower case letter. Collection of `labels` for the property as strings with language tag (currently "en", "de", "fr", "it", and "rm" are supported). + + ### Comments (optional) @@ -148,6 +166,8 @@ and "rm" are supported). Comments with language tags. Currently, "de", "en", "fr", "it", and "rm" are supported. The `comments` element is optional. + + ### Super (required) @@ -163,7 +183,7 @@ super-property: 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: +The following DSP base properties are available: - `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 @@ -173,7 +193,10 @@ The following base properties can be used as super-property: 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. - +- `isSequenceOf`: A special variant of `hasLinkTo`. It says that an instance of the given resource class is a section + of an audio/video resource. +- `hasSequenceBounds`: This base property is used together with `isSequenceOf`. It denotes a time interval of an audio/ + video resource. Example of a `properties` object: @@ -220,6 +243,8 @@ Example of a `properties` object: } ``` + + ### Subject (optional) @@ -229,6 +254,8 @@ Example of a `properties` object: The `subject` defines the resource class the property can be used on. It has to be provided as prefixed name of the resource class (see [below](#referencing-ontologies) on how prefixed names are used). + + ### Object / gui_element / gui_attributes These three are related as follows: @@ -237,7 +264,7 @@ These three are related as follows: - `gui_element` (required) depends on the value of `object`. - `gui_attributes` (optional) depends on the value of `gui_element`. -The following data types are allowed: +The following `object`s are available, and will be discussed below, in this order: - `BooleanValue` - `ColorValue` @@ -250,7 +277,9 @@ The following data types are allowed: - `TextValue` - `TimeValue` - `UriValue` -- in case of a link property: any resource class +- `Representation` +- in case of the supers `hasLinkTo` or `isPartOf`: any resource class + #### BooleanValue @@ -279,6 +308,7 @@ Represents a Boolean ("true" or "false). } ``` + #### ColorValue `"object": "ColorValue"` @@ -307,6 +337,7 @@ A string representation of the color in the hexadecimal form e.g. "#ff8000". } ``` + #### DateValue `object": "DateValue"` @@ -347,6 +378,7 @@ which means anytime in between 1925 and the 22nd March 1927. } ``` + #### DecimalValue `"object": "DecimalValue"` @@ -385,6 +417,7 @@ A number with decimal point. } ``` + #### GeonameValue Represents a location ID in geonames.org. DSP uses identifiers provided by @@ -412,11 +445,17 @@ Represents a location ID in geonames.org. DSP uses identifiers provided by } ``` + #### IntervalValue `"object": "IntervalValue"` -Represents a time-interval +This `object` belongs to the DSP base property `super: hasSequenceBounds`. It represents a time interval of an +audio or video. It can be used together with an `isSequenceOf` property on a resource that represents the sequence. The +`isSequenceOf` would then point to the audio/video resource, and the `hasSequenceBounds` would be the time interval of +the sequence. + +See the [`isSequenceOf` property](#issequenceof-property) for more detailed explanations. *gui-elements / gui_attributes*: @@ -432,18 +471,20 @@ Represents a time-interval ```json { - "name": "hasInterval", + "name": "hasBounds", "super": [ - "hasValue" + "hasSequenceBounds" ], + "subject": ":AudioSequence", "object": "IntervalValue", "labels": { - "en": "Time interval" + "en": "Interval defining the start and end point of a sequence of an audio or video file" }, "gui_element": "Interval" } ``` + #### IntValue `"object": "IntValue"` @@ -483,6 +524,7 @@ Represents an integer value. } ``` + #### ListValue `"object": "ListValue"` @@ -518,6 +560,7 @@ Represents a node of a (possibly hierarchical) list } ``` + #### TextValue `"object": "TextValue"` @@ -559,6 +602,7 @@ Represents a text that may contain standoff markup. } ``` + #### TimeValue `"object": "TimeValue"` @@ -586,6 +630,7 @@ A time value represents a precise moment in time in the Gregorian calendar. Sinc } ``` + #### UriValue `"object": "UriValue"` @@ -620,6 +665,7 @@ Represents an URI } ``` + #### Representation `"object": "Representation"` @@ -650,6 +696,7 @@ A property pointing to a `knora-base:Representation`. Has to be used in combinat } ``` + #### hasLinkTo Property `"object": ""` @@ -692,11 +739,16 @@ When defining a link property, its "super" element has to be `hasLinkTo` or deri } ``` + #### 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. +is required. When defined, the user is able to leaf through the parts of a compound object, p.ex. to leaf through pages +of a book. + +The DSP base properties `isPartOf` and `seqnum` can be used to derive a custom property from them, or they can be used +directly as cardinalities in a resource. The example belows shows both possibilities. *gui-elements/gui_attributes*: @@ -707,31 +759,58 @@ is required. When defined, a client is able to leaf through the parts of a compo *Example:* ```json -{ - "name": "partOfBook", - "super": [ - "isPartOf" - ], - "object": ":Book", - "labels": { - "en": "is part of" - }, - "gui_element": "Searchbox" -}, -{ - "name": "hasPageNumber", - "super": [ - "seqnum" - ], - "object": "IntValue", - "labels": { - "en": "has page number" - }, - "gui_element": "Spinbox" -} +"properties": [ + { + "name": "partOfBook", + "super": ["isPartOf"], + "object": ":Book", + "labels": {"en": "is part of"}, + "gui_element": "Searchbox" + }, + { + "name": "hasPageNumber", + "super": ["seqnum"], + "object": "IntValue", + "labels": {"en": "has page number"}, + "gui_element": "Spinbox" + } +], +"resources": [ + { + "name": "Page", + "labels": {"en": "Page using properties derived from 'isPartOf' and 'seqnum'"}, + "super": "StillImageRepresentation", + "cardinalities": [ + { + "propname": ":partOfBook", + "cardinality": "1" + }, + { + "propname": ":hasPageNumber", + "cardinality": "1" + } + ] + }, + { + "name": "MinimalisticPage", + "labels": {"en": "Page using 'isPartOf' and 'seqnum' directly"}, + "super": "StillImageRepresentation", + "cardinalities": [ + { + "propname": "isPartOf", + "cardinality": "1" + }, + { + "propname": "seqnum", + "cardinality": "1" + } + ] + } +] ``` -#### hasComment Property + +#### hasComment property `"object": "TextValue"` @@ -755,6 +834,82 @@ This property is actually very similar to a simple text field. } ``` + +#### isSequenceOf property + +`"object": (AudioRepresentation/MovingImageRepresentation or a subclass of one of them)` + +This property can be used, together with a `hasSequenceBounds` property, on a resource representing a sequence of an +audio/video resource. The `isSequenceOf` would then point to the audio/video resource, and the `hasSequenceBounds` +would be the time interval of the sequence. + +The DSP base properties `isSequenceOf` and `hasSequenceBounds` can be used to derive a custom property from them, or +they can be used directly as cardinalities in a resource. The example below shows both possibilities. + +*gui-elements/gui_attributes*: + +- `Searchbox`: The only GUI element for _isSequenceOf_. Allows searching resources by entering the target resource name. + - _gui_attributes_: + - `numprops=integer` (optional): Number of search results to be displayed + +*Example:* + +```json +"properties": [ + { + "name": "sequenceOfAudio", + "super": ["isSequenceOf"], + "subject": ":AudioSequence", + "object": ":Audio", + "labels": {"en": "is sequence of"}, + "gui_element": "Searchbox" + }, + { + "name": "hasBounds", + "super": ["hasSequenceBounds"], + "subject": ":AudioSequence", + "object": "IntervalValue", + "labels": {"en": "Start and end point of a sequence of an audio/video"}, + "gui_element": "Interval" + } +], +"resources": [ + { + "name": "AudioSequence", + "labels": {"en": "Sequence of audio using properties derived from 'isSequenceOf' and 'hasSequenceBounds'"}, + "super": "Resource", + "cardinalities": [ + { + "propname": ":sequenceOfAudio", + "cardinality": "1" + }, + { + "propname": ":hasBounds", + "cardinality": "1" + } + ] + }, + { + "name": "MinimalisticAudioSequence", + "labels": {"en": "Sequence of audio using 'isSequenceOf' and 'hasSequenceBounds' directly"}, + "super": "Resource", + "cardinalities": [ + { + "propname": "isSequenceOf", + "cardinality": "1" + }, + { + "propname": "hasSequenceBounds", + "cardinality": "1" + } + ] + } +] +``` + + + + ## Resources Object in Detail ### Name @@ -769,6 +924,8 @@ but `-` and `_` are allowed (although not as first character). By convention, resource names start with a upper case letter. + + ### Labels (required) @@ -778,6 +935,8 @@ By convention, resource names start with a upper case letter. Collection of `labels` for the resource as strings with language tag (currently "en", "de", "fr", "it", and "rm" are supported). + + ### Super (required) @@ -806,6 +965,7 @@ used in all cases when your resource is none of the special cases below. - `TextRepresentation`: A resource representing a text + ### Cardinalities (required) @@ -826,6 +986,8 @@ resource can have as well as how many times the relation is established. - `"1-n"`: At least one value must be present, but multiple values may be present. - `"0-n"`: The value may be omitted, but may also occur multiple times. + + ### Comments (optional) @@ -877,6 +1039,8 @@ Example for a resource definition: ``` + + ## Referencing Ontologies For several fields, such as `super` in both `resources` and `properties` or `propname` in `cardinalities` @@ -897,6 +1061,8 @@ it is necessary to reference entities that are defined elsewhere. The following `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: diff --git a/knora/dsplib/models/resource.py b/knora/dsplib/models/resource.py index 1678ed34a..8e3b79cd1 100644 --- a/knora/dsplib/models/resource.py +++ b/knora/dsplib/models/resource.py @@ -64,7 +64,9 @@ class ResourceInstance(Model): "knora-api:isPartOf", "knora-api:isRegionOf", "knora-api:isAnnotationOf", - "knora-api:seqnum" + "knora-api:seqnum", + "knora-api:isSequenceOf", + "knora-api:hasSequenceBounds" } _iri: Optional[str] _ark: Optional[str] @@ -407,12 +409,8 @@ def get_resclass_type(self, prefixedresclass: str) -> Type: 'knora-api:LinkValue': LinkValue, } for propname, has_property in resclass.has_properties.items(): - if any([ - propname == "knora-api:isAnnotationOf", - propname == "knora-api:isRegionOf", - propname == "knora-api:isPartOf", - propname == "knora-api:hasLinkTo" - ]): + if propname in ["knora-api:isAnnotationOf", "knora-api:isRegionOf", "knora-api:isPartOf", + "knora-api:hasLinkTo", "knora-api:isSequenceOf"]: valtype = LinkValue props[propname] = Propinfo(valtype=valtype, cardinality=has_property.cardinality, @@ -438,6 +436,11 @@ def get_resclass_type(self, prefixedresclass: str) -> Type: props[propname] = Propinfo(valtype=valtype, cardinality=has_property.cardinality, gui_order=has_property.gui_order) + elif propname == "knora-api:hasSequenceBounds": + valtype = IntervalValue + props[propname] = Propinfo(valtype=valtype, + cardinality=has_property.cardinality, + gui_order=has_property.gui_order) elif has_property.ptype == HasProperty.Ptype.other: valtype = switcher.get(self._properties[propname].object) if valtype == LinkValue: diff --git a/knora/dsplib/schemas/ontology.json b/knora/dsplib/schemas/ontology.json index ea4558041..71e3de06c 100644 --- a/knora/dsplib/schemas/ontology.json +++ b/knora/dsplib/schemas/ontology.json @@ -264,6 +264,8 @@ "hasColor", "hasComment", "isPartOf", + "isSequenceOf", + "hasSequenceBounds", "hasRepresentation", "seqnum" ] @@ -766,7 +768,7 @@ "oneOf": [ { "enum": [ - "hasValue" + "hasSequenceBounds" ] }, { @@ -1054,7 +1056,9 @@ { "enum": [ "isPartOf", - "seqnum" + "seqnum", + "isSequenceOf", + "hasSequenceBounds" ] }, { diff --git a/knora/dsplib/schemas/properties-only.json b/knora/dsplib/schemas/properties-only.json index 72cb5e52d..6720d6758 100644 --- a/knora/dsplib/schemas/properties-only.json +++ b/knora/dsplib/schemas/properties-only.json @@ -47,6 +47,8 @@ "hasColor", "hasComment", "isPartOf", + "isSequenceOf", + "hasSequenceBounds", "hasRepresentation", "seqnum" ] @@ -548,7 +550,7 @@ "oneOf": [ { "enum": [ - "hasValue" + "hasSequenceBounds" ] }, { diff --git a/knora/dsplib/schemas/resources-only.json b/knora/dsplib/schemas/resources-only.json index c87cf448d..5b2f0531d 100644 --- a/knora/dsplib/schemas/resources-only.json +++ b/knora/dsplib/schemas/resources-only.json @@ -98,7 +98,9 @@ { "enum": [ "isPartOf", - "seqnum" + "seqnum", + "isSequenceOf", + "hasSequenceBounds" ] }, { diff --git a/test/e2e/test_tools.py b/test/e2e/test_tools.py index ccc8f264d..1f851a8c6 100644 --- a/test/e2e/test_tools.py +++ b/test/e2e/test_tools.py @@ -191,7 +191,7 @@ def test_xml_upload(self) -> None: password=self.password, imgdir=self.imgdir, sipi=self.sipi, - verbose=True, + verbose=False, validate_only=False, incremental=False) self.assertTrue(result) diff --git a/test/unittests/test_create_ontology.py b/test/unittests/test_create_ontology.py index 20937c0ed..af3b97c15 100644 --- a/test/unittests/test_create_ontology.py +++ b/test/unittests/test_create_ontology.py @@ -25,12 +25,11 @@ def test_sort_resources(self) -> None: unsorted_resources: list[dict[str, Any]] = self.ontology['resources'] sorted_resources = _sort_resources(unsorted_resources, onto_name) - unsorted_resources = sorted(unsorted_resources, key=lambda a: a['name']) - sorted_resources = sorted(sorted_resources, key=lambda a: a['name']) + unsorted_resources = sorted(unsorted_resources, key=lambda a: str(a['name'])) + sorted_resources = sorted(sorted_resources, key=lambda a: str(a['name'])) self.assertListEqual(unsorted_resources, sorted_resources) - def test_sort_prop_classes(self) -> None: """ The 'properties' section of an onto is a list of dictionaries. The safest way to test @@ -41,8 +40,8 @@ def test_sort_prop_classes(self) -> None: unsorted_props: list[dict[str, Any]] = self.ontology['resources'] sorted_props = _sort_prop_classes(unsorted_props, onto_name) - unsorted_props = sorted(unsorted_props, key=lambda a: a['name']) - sorted_props = sorted(sorted_props, key=lambda a: a['name']) + unsorted_props = sorted(unsorted_props, key=lambda a: str(a['name'])) + sorted_props = sorted(sorted_props, key=lambda a: str(a['name'])) self.assertListEqual(unsorted_props, sorted_props) diff --git a/test/unittests/test_excel_to_properties.py b/test/unittests/test_excel_to_properties.py index 0bc548a03..9a3a6727e 100644 --- a/test/unittests/test_excel_to_properties.py +++ b/test/unittests/test_excel_to_properties.py @@ -27,7 +27,7 @@ def test_excel2json(self) -> None: excel_supers = [["hasLinkTo"], ["hasValue", "dcterms:creator"], ["hasValue"], ["hasValue"], ["hasLinkTo"], ["hasValue"], ["hasValue"], ["hasValue"], ["hasRepresentation"], ["hasValue", "dcterms:description"], ["hasValue"],["hasValue"], ["hasColor"], ["hasValue"], - ["hasValue"], ["hasValue"], ["hasValue"], ["hasValue"], ["isPartOf"]] + ["hasValue"], ["hasSequenceBounds"], ["hasValue"], ["hasValue"], ["isPartOf"]] excel_objects = [":GenericAnthroponym", "TextValue", "ListValue", "ListValue", ":Titles", "ListValue", "IntValue", "DateValue", "Representation", "TextValue", "DateValue", "UriValue", "ColorValue", "DecimalValue", "TimeValue", "IntervalValue", "BooleanValue", "GeonameValue", diff --git a/testdata/Properties.xlsx b/testdata/Properties.xlsx index 31ea63db0..9d5e8d918 100644 Binary files a/testdata/Properties.xlsx and b/testdata/Properties.xlsx differ diff --git a/testdata/test-data-systematic.xml b/testdata/test-data-systematic.xml index e7340642b..10682cf6f 100644 --- a/testdata/test-data-systematic.xml +++ b/testdata/test-data-systematic.xml @@ -102,9 +102,6 @@ 5416656 - - 12.5:14.2 - #00ff00 @@ -198,9 +195,6 @@ 5416656 - - 12.5:14.2 - #00ff00 @@ -208,7 +202,7 @@ test_thing_0 - + + + + + Sequence of a video + + + video_thing_1 + + + 2.0:10.0 + + + + + + + Sequence of an Audio Resource + + + audio_thing_1 + + + 8.0:12.0 + + + + + + + Sequence of a Video Resource + + + video_thing_1 + + + 8.0:12.0 + + + + + + Sequence of an Audio Resource + + + audio_thing_1 + + + 8.0:12.0 + + diff --git a/testdata/test-project-systematic.json b/testdata/test-project-systematic.json index 69e0dafc8..bf6f6155c 100644 --- a/testdata/test-project-systematic.json +++ b/testdata/test-project-systematic.json @@ -253,7 +253,7 @@ "size": 80 } }, - { + { "name": "hasRichtext", "super": [ "hasValue" @@ -343,13 +343,14 @@ "gui_element": "Geonames" }, { - "name": "hasInterval", + "name": "hasBounds", "super": [ - "hasValue" + "hasSequenceBounds" ], "object": "IntervalValue", "labels": { - "en": "Time interval" + "de": "Intervall, das den Start- und Endpunkt der Sequenz definiert", + "en": "Interval defining the start and end point of the sequence" }, "gui_element": "Interval" }, @@ -443,6 +444,32 @@ "en": "hasMovingImageRepresentation" }, "gui_element": "Searchbox" + }, + { + "name": "sequenceOfVideo", + "super": [ + "isSequenceOf" + ], + "subject": ":VideoSequence", + "object": ":MovieThing", + "labels": { + "de": "ist Sequenz von", + "en": "is sequence of" + }, + "gui_element": "Searchbox" + }, + { + "name": "sequenceOfAudio", + "super": [ + "isSequenceOf" + ], + "subject": ":AudioSequence", + "object": ":AudioThing", + "labels": { + "de": "ist Sequenz von", + "en": "is sequence of" + }, + "gui_element": "Searchbox" } ], "resources": [ @@ -498,11 +525,6 @@ "gui_order": 9, "cardinality": "0-n" }, - { - "propname": ":hasInterval", - "gui_order": 10, - "cardinality": "0-n" - }, { "propname": ":hasColor", "gui_order": 11, @@ -729,6 +751,79 @@ "cardinality": "1" } ] + }, + { + "name": "VideoSequence", + "labels": { + "de": "Sequenz einer Video-Ressource", + "en": "Sequence of a video resource" + }, + "super": "Resource", + "cardinalities": [ + { + "propname": ":hasText", + "cardinality": "0-1" + }, + { + "propname": ":sequenceOfVideo", + "cardinality": "1" + }, + { + "propname": ":hasBounds", + "cardinality": "1", + "gui_order": 1 + } + ] + }, + { + "name": "AudioSequence", + "labels": { + "de": "Sequenz einer Audio-Ressource", + "en": "Sequence of an audio resource" + }, + "super": "Resource", + "cardinalities": [ + { + "propname": ":hasText", + "cardinality": "0-1" + }, + { + "propname": ":sequenceOfAudio", + "cardinality": "1" + }, + { + "propname": ":hasBounds", + "cardinality": "1", + "gui_order": 1 + } + ] + }, + { + "name": "MinimalisticSequence", + "labels": { + "de": "Minimalistische Sequenz einer Audio-/Video-Ressource", + "en": "Minimalistic sequence of an audio/video resource" + }, + "comments": { + "de": "Sequenz einer Audio-/Video-Ressource, welche die base properties 'isSequenceOf' und 'hasSequenceBounds' direkt verwendet", + "en": "Sequence of an audio/video resource that uses the base properties 'isSequenceOf' and 'hasSequenceBounds' directly" + }, + "super": "Resource", + "cardinalities": [ + { + "propname": ":hasText", + "cardinality": "0-1" + }, + { + "propname": "isSequenceOf", + "cardinality": "1" + }, + { + "propname": "hasSequenceBounds", + "cardinality": "1", + "gui_order": 1 + } + ] } ] },