diff --git a/docs/01-introduction/standoff-rdf.md b/docs/01-introduction/standoff-rdf.md index 1448c15a22..d18c45ab5f 100644 --- a/docs/01-introduction/standoff-rdf.md +++ b/docs/01-introduction/standoff-rdf.md @@ -5,15 +5,14 @@ # Standoff/RDF Text Markup -[Standoff markup](https://lexiconse.uantwerpen.be/index.php/lexicon/markup-standoff/) -is text markup that is stored separately from the content it describes. Knora's +[Standoff markup](https://lexiconse.uantwerpen.be/lexicon/markupStandoff.html) +is text markup that is stored separately from the content it describes. DSP-API's Standoff/RDF markup stores content as a simple Unicode string, and represents markup separately as RDF data. This approach has some advantages over commonly used markup systems such as XML: First, XML and other hierarchical markup systems assume that a document is a hierarchy, and -have difficulty representing -[non-hierarchical structures](http://www.tei-c.org/release/doc/tei-p5-doc/en/html/NH.html) +have difficulty representing [non-hierarchical structures](http://www.tei-c.org/release/doc/tei-p5-doc/en/html/NH.html) or multiple overlapping hierarchies. Standoff markup can easily represent these structures. Second, markup languages are typically designed to be used in text files. But there is no @@ -22,43 +21,39 @@ markup. It is possible to do this in a non-standard way by using an XML database such as [eXist](http://exist-db.org), but this still does not allow for queries that include text as well as non-textual data not stored in XML. -By storing markup as RDF, Knora can search for markup structures in the same way that it +By storing markup as RDF, DSP-API can search for markup structures in the same way as it searches for any RDF data structure. This makes it possible to do searches that combine text-related criteria with other sorts of criteria. For example, if persons and events are -represented as Knora resources, and texts are represented in Standoff/RDF, a text can contain +represented as resources, and texts are represented in Standoff/RDF, a text can contain tags representing links to persons or events. You could then search for a text that mentions a person who lived in the same city as another person who is the author of a text that mentions an event that occurred during a certain time period. -In Knora's Standoff/RDF, a tag is an RDF entity that is linked to a +In DSP-API's Standoff/RDF, a tag is an RDF entity that is linked to a [text value](../02-knora-ontologies/knora-base.md#textvalue). Each tag points to a substring of the text, and has semantic properties of its own. You can define your own tag classes in your ontology by making subclasses of `knora-base:StandoffTag`, and attach your own -properties to them. You can then search for those properties using Knora's search language, +properties to them. You can then search for those properties using DSP-API's search language, [Gravsearch](../03-apis/api-v2/query-language.md). The built-in [knora-base](../02-knora-ontologies/knora-base.md) and `standoff` ontologies provide some basic tags that can be reused or extended. These include tags that represent -Knora data types. For example, `knora-base:StandoffDateTag` represents a date in exactly the -same way as a Knora [date value](../02-knora-ontologies/knora-base.md#datevalue), i.e. as a +DSP-API data types. For example, `knora-base:StandoffDateTag` represents a date in exactly the +same way as a [date value](../02-knora-ontologies/knora-base.md#datevalue), i.e. as a calendar-independent astronomical date. You can use this tag as-is, or extend it by making a subclass, to represent dates in texts. Gravsearch includes built-in functionality for searching for these data type tags. For example, you can search for text containing a date that falls within a certain [date range](../03-apis/api-v2/query-language.md#matching-standoff-dates). -Knora's APIs support automatic conversion between XML and Standoff/RDF. To make this work, +DSP-API supports automatic conversion between XML and Standoff/RDF. To make this work, Standoff/RDF stores the order of tags and their hierarchical relationships. You must define an [XML-to-Standoff Mapping](../03-apis/api-v2/xml-to-standoff-mapping.md) for your standoff tag classes and properties. -Then you can import an XML document into Knora, which will store it as Standoff/RDF. The text and markup -can then be searched using Gravsearch. When you retrieve the document, Knora converts it back to the +Then you can import an XML document into DSP-API, which will store it as Standoff/RDF. The text and markup +can then be searched using Gravsearch. When you retrieve the document, DSP-API converts it back to the original XML. -To represent overlapping or non-hierarchical markup in exported and imported XML, Knora supports -[CLIX](http://conferences.idealliance.org/extreme/html/2004/DeRose01/EML2004DeRose01.html#t6) tags. +To represent overlapping or non-hierarchical markup in exported and imported XML, DSP-API supports +[CLIX](https://web.archive.org/web/20171222112655/http://conferences.idealliance.org/extreme/html/2004/DeRose01/EML2004DeRose01.html) tags. -Future plans for Standoff/RDF include: - -- Creation and retrieval of standoff markup as such via the DSP-API, - without using XML as an input/output format. -- A user interface for editing standoff markup. -- The ability to create resources that cite particular standoff tags in other resources. +As XML-to-Standoff has proved to be complicated and not very well performing, the use of standoff with custom mappings is discouraged. +Improved integration of text with XML mark up, particularly TEI-XML, is in planning. diff --git a/docs/02-knora-ontologies/knora-base.md b/docs/02-knora-ontologies/knora-base.md index 8f79edb6c6..c12a54b350 100644 --- a/docs/02-knora-ontologies/knora-base.md +++ b/docs/02-knora-ontologies/knora-base.md @@ -589,24 +589,24 @@ pages of a book like in [this](https://docs.dasch.swiss/DSP-API/01-introduction/ ### Text with Standoff Markup -Knora is designed to be able to store text with markup, which can indicate formatting and structure, as well as the +DSP-API is designed to be able to store text with markup, which can indicate formatting and structure, as well as the complex observations involved in transcribing handwritten manuscripts. One popular way of representing text in the -humanities is to encode it in XML using the Text Encoding Initiative -([TEI](http://www.tei-c.org/release/doc/tei-p5-doc/en/html/index.html)) -guidelines. In Knora, a TEI/XML document can be stored as a file with attached metadata, but this is not recommended, -because it does not allow Knora to perform searches across multiple documents. +humanities is to encode it in XML using the Text Encoding Initiative ([TEI](http://www.tei-c.org/release/doc/tei-p5-doc/en/html/index.html)) +guidelines. In DSP-API, a TEI/XML document can be stored as a file with attached metadata, but this is not recommended, +because it does not allow to perform searches across multiple documents. -The recommended way to store text with markup in Knora is to use Knora's built-in support for "standoff" markup, which +The recommended way to store text with markup in DSP-API is to use the built-in support for "standoff" markup, which is stored separately from the text. This has some advantages over embedded markup such as XML. While XML requires markup -to have a hierarchical structure, and does not allow overlapping tags, standoff nodes do not have these limitations ( -see [Using Standoff Properties for Marking-up Historical Documents in the Humanities](http://ecdosis.net/papers/schmidt.d.2016.pdf)) -. A standoff tag can be attached to any substring in the text by giving its start and end positions. Unlike in corpus +to have a hierarchical structure, and does not allow overlapping tags, standoff nodes do not have these limitations +(see [Using Standoff Properties for Marking-up Historical Documents in the Humanities](https://doi.org/10.1515/itit-2015-0030)). +A standoff tag can be attached to any substring in the text by giving its start and end positions. Unlike in corpus linguistics, we do not use any tokenisation resulting in a form of predefined segmentation, which would limit the user's ability to freely annotate any ranges in the text. For example, suppose we have the following text:
This sentence has overlapping visual attributes.
+ This would require just two standoff tags: `(italic, start=5, end=29)` and `(bold, start=14, end=36)`. @@ -614,9 +614,9 @@ Moreover, standoff makes it possible to mark up the same text in different, poss different interpretations without making redundant copies of the text. In the Knora base ontology, any text value can have standoff tags. -By representing standoff as RDF triples, Knora makes markup searchable across multiple text documents in a repository. +By representing standoff as RDF triples, DSP-API makes markup searchable across multiple text documents in a repository. For example, if a repository contains documents in which references to persons are indicated in standoff, it is -straightforward to find all the documents mentioning a particular person. Knora's standoff support is intended to make +straightforward to find all the documents mentioning a particular person. DSP-API's standoff support is intended to make it possible to convert documents with embedded, hierarchical markup, such as TEI/XML, into RDF standoff and back again, with no data loss, thus bringing the benefits of RDF to existing TEI-encoded documents. @@ -624,95 +624,62 @@ In the Knora base ontology, a `TextValue` can have one or more standoff tags. Ea end positions of a substring in the text that has a particular attribute. The OWL class `kb:StandoffTag`, which is the base class of all standoff node classes, has these properties: -`standoffTagHasStart` (1) - -: The index of the first character in the text that has the attribute. - -`standoffTagHasEnd` (1) - -: The index of the last character in the text that has the attribute, plus 1. - -`standoffTagHasUUID` (1) - -: A UUID identifying this instance and those corresponding to it in later versions of the `TextValue` it belongs to. -The UUID is a means to maintain a reference to a particular range of a text also when new versions are made and standoff +- `standoffTagHasStart` (1): The index of the first character in the text that has the attribute. +- `standoffTagHasEnd` (1): The index of the last character in the text that has the attribute, plus 1. +- `standoffTagHasUUID` (1): A UUID identifying this instance and those corresponding to it in later versions of the `TextValue` it belongs to. + The UUID is a means to maintain a reference to a particular range of a text also when new versions are made and standoff tag IRIs change. - -`standoffTagHasOriginalXMLID` (0-1) - -: The original id of the XML element that the standoff tag represents, if any. - -`standoffTagHasStartIndex` (1) - -: The start index of the standoff tag. Start indexes are numbered from 0 within the context of a particular text. When -several standoff tags share the same start position, they can be nested correctly with this information when +- `standoffTagHasOriginalXMLID` (0-1): The original ID of the XML element that the standoff tag represents, if any. +- `standoffTagHasStartIndex` (1): The start index of the standoff tag. Start indexes are numbered from 0 within the context of a particular text. + When several standoff tags share the same start position, they can be nested correctly with this information when transforming them to XML. - -`standoffTagHasEndIndex` (1) - -: The end index of the standoff tag. Start indexes are numbered from 0 within the context of a particular text. When -several standoff tags share the same end position, they can be nested correctly with this information when transforming +- `standoffTagHasEndIndex` (1): The end index of the standoff tag. Start indexes are numbered from 0 within the context of a particular text. + When several standoff tags share the same end position, they can be nested correctly with this information when transforming them to XML. - -`standoffTagHasStartParent` (0-1) - -: Points to the parent standoff tag. This corresponds to the original nesting of tags in XML. If a standoff tag has no -parent, it represents the XML root element. If the original XML element is a CLIX tag, it represents the start of a -virtual (non syntactical) -hierarchy. - -`standoffTagHasEndParent` (0-1) - -: Points to the parent standoff tag if the original XML element is a CLIX tag and represents the end of a virtual (non -syntactical) -hierarchy. +- `standoffTagHasStartParent` (0-1): Points to the parent standoff tag. This corresponds to the original nesting of tags in XML. If a standoff tag has no parent, it represents the XML root element. + If the original XML element is a CLIX tag, it represents the start of a virtual (non syntactical) hierarchy. +- `standoffTagHasEndParent` (0-1): Points to the parent standoff tag if the original XML element is a CLIX tag and represents the end of a virtual (non syntactical) hierarchy. The `StandoffTag` class is not used directly in RDF data; instead, its subclasses are used. A few subclasses are -currently provided in -`standoff-onto.ttl`, and more will be added to support TEI semantics. Projects are able to define their own custom -standoff tag classes -(direct subclasses of `StandoffTag` or one of the standoff data type classes or subclasses of one of the standoff -classes defined in -`standoff-onto.ttl`). +currently provided in `standoff-onto.ttl`, and more will be added to support TEI semantics. +Projects are able to define their own custom standoff tag classes (direct subclasses of `StandoffTag` +or one of the standoff data type classes or subclasses of one of the standoff classes defined in `standoff-onto.ttl`). #### Subclasses of StandoffTag ##### Standoff Data Type Tags -Associates data in some Knora value type with a substring in a text. Standoff data type tags are subclasses -of `ValueBase` classes. +Associates data in some Knora value type with a substring in a text. Standoff data type tags are subclasses of `ValueBase` classes. -* `StandoffLinkTag` Indicates that a substring refers to another `kb:Resource`. See [StandoffLinkTag](#standofflinktag). -* `StandoffInternalReferenceTag` Indicates that a substring refers to another standoff tag in the same text value. +- `StandoffLinkTag` Indicates that a substring refers to another `kb:Resource`. See [StandoffLinkTag](#standofflinktag). +- `StandoffInternalReferenceTag` Indicates that a substring refers to another standoff tag in the same text value. See [Internal Links in a TextValue](#internal-links-in-a-textvalue). -* `StandoffUriTag` Indicates that a substring is associated with a URI, which is stored in the same form that is used +- `StandoffUriTag` Indicates that a substring is associated with a URI, which is stored in the same form that is used for `kb:UriValue`. See [UriValue](#urivalue). -* `StandoffDateTag` Indicates that a substring represents a date, which is stored in the same form that is used +- `StandoffDateTag` Indicates that a substring represents a date, which is stored in the same form that is used for `kb:DateValue`. See [DateValue](#datevalue). -* `StandoffColorTag` Indicates that a substring represents a color, which is stored in the same form that is used +- `StandoffColorTag` Indicates that a substring represents a color, which is stored in the same form that is used for `kb:ColorValue`. See [ColorValue](#colorvalue). -* `StandoffIntegerTag` Indicates that a substring represents an integer, which is stored in the same form that is used +- `StandoffIntegerTag` Indicates that a substring represents an integer, which is stored in the same form that is used for `kb:IntegerValue`. See [IntValue](#intvalue). -* `StandoffDecimalTag` Indicates that a substring represents a number with fractions, which is stored in the same form +- `StandoffDecimalTag` Indicates that a substring represents a number with fractions, which is stored in the same form that is used for `kb:DecimalValue`. See [DecimalValue](#decimalvalue). -* `StandoffIntervalTag` Indicates that a substring represents an interval, which is stored in the same form that is used +- `StandoffIntervalTag` Indicates that a substring represents an interval, which is stored in the same form that is used for `kb:IntervalValue`. See [IntervalValue](#intervalvalue). -* `StandoffBooleanTag` Indicates that a substring represents a Boolean, which is stored in the same form that is used +- `StandoffBooleanTag` Indicates that a substring represents a Boolean, which is stored in the same form that is used for `kb:BooleanValue`. See [BooleanValue](#booleanvalue). -* `StandoffTimeTag` Indicates that a substring represents a timestamp, which is stored in the same form that is used +- `StandoffTimeTag` Indicates that a substring represents a timestamp, which is stored in the same form that is used for `kb:TimeValue`. See [TimeValue](#timevalue). ##### StandoffLinkTag A `StandoffLinkTag` Indicates that a substring is associated with a Knora resource. For example, if a repository contains resources representing persons, a text could be marked up so that each time a person's name is mentioned, -a `StandoffLinkTag` connects the name to the Knora resource describing that person. Property: - -`standoffTagHasLink` (1) +a `StandoffLinkTag` connects the name to the Knora resource describing that person. It has the following property: -: The IRI of the resource that is referred to. +`standoffTagHasLink` (1): The IRI of the resource that is referred to. -One of the design goals of the Knora ontology is to make it easy and efficient to find out which resources contain +One of the design goals of the Knora base ontology is to make it easy and efficient to find out which resources contain references to a given resource. Direct links are easier and more efficient to query than indirect links. Therefore, when a text value contains a resource reference in its standoff nodes, Knora automatically creates a direct link between the containing resource and the target resource, along with an RDF reification (a `kb:LinkValue`) describing the link, as @@ -720,7 +687,7 @@ discussed in [Links Between Resources](#links-between-resources). In this case, always `kb:hasStandoffLinkTo`, and the link value property (which points to the `LinkValue`) is always `kb:hasStandoffLinkToValue`. -Knora automatically updates direct links and reifications for standoff resource references when text values are updated. +DSP-API automatically updates direct links and reifications for standoff resource references when text values are updated. To do this, it keeps track of the number of text values in each resource that contain at least one standoff reference to a given target resource. It stores this number as the reference count of the `LinkValue` (see [LinkValue](#linkvalue)) describing the direct link. Each time this number changes, it makes a new version of @@ -730,7 +697,7 @@ makes a new version of the `LinkValue`, marked with `kb:isDeleted`. For example, if `data:R1` is a resource with a text value in which the resource `data:R2` is referenced, the repository could contain the following triples: -``` +```turtle data:R1 ex:hasComment data:V1 . data:V1 rdf:type kb:TextValue ; @@ -766,83 +733,45 @@ the user has permission to see the source and target resources. Internal links in a `TextValue` can be represented using the data type standoff class `StandoffInternalReferenceTag` or a subclass of it. It has the following property: -`standoffTagHasInternalReference` (1) - -: Points to a `StandoffTag` that belongs to the same `TextValue`. It has an `objectClassConstraint` of `StandoffTag`. +`standoffTagHasInternalReference` (1): Points to a `StandoffTag` that belongs to the same `TextValue`. It has an `objectClassConstraint` of `StandoffTag`. For links to a `kb:Resource`, see [StandoffLinkTag](#standofflinktag). #### Mapping to Create Standoff From XML A mapping allows for the conversion of an XML document to RDF-standoff and back. A mapping defines one-to-one relations -between XML elements -(with or without a class) and attributes and standoff classes and properties ( -see [XML to Standoff Mapping](../03-apis/api-v2/xml-to-standoff-mapping.md)). +between XML elements (with or without a class) and attributes and standoff classes and properties (see +[XML to Standoff Mapping](../03-apis/api-v2/xml-to-standoff-mapping.md)). A mapping is represented by a `kb:XMLToStandoffMapping` which contains one or more `kb:MappingElement`. A `kb:MappingElement` maps an XML element (including attributes) to a standoff class and standoff properties. It has the following properties: -`mappingHasXMLTagname` (1) - -: The name of the XML element that is mapped to a standoff class. - -`mappingHasXMLNamespace` (1) - -: The XML namespace of the XML element that is mapped to a standoff class. If no namespace is given, `noNamespace` is -used. - -`mappingHasXMLClass` (1) - -: The name of the class of the XML element. If it has no class, -`noClass` is used. - -`mappingHasStandoffClass` (1) - -: The standoff class the XML element is mapped to. - -`mappingHasXMLAttribute` (0-n) - -: Maps XML attributes to standoff properties using -`MappingXMLAttribute`. See below. - -`mappingHasStandoffDataTypeClass` (0-1) - -: Indicates the standoff data type class of the standoff class the XML element is mapped to. - -`mappingElementRequiresSeparator` (1) - -: Indicates if there should be an invisible word separator inserted after the XML element in the RDF-standoff -representation. Once the markup is stripped, text segments that belonged to different elements may be concatenated. +- `mappingHasXMLTagname` (1): The name of the XML element that is mapped to a standoff class. +- `mappingHasXMLNamespace` (1): The XML namespace of the XML element that is mapped to a standoff class. If no namespace is given, `noNamespace` is used. +- `mappingHasXMLClass` (1): The name of the class of the XML element. If it has no class, `noClass` is used. +- `mappingHasStandoffClass` (1): The standoff class the XML element is mapped to. +- `mappingHasXMLAttribute` (0-n): Maps XML attributes to standoff properties using `MappingXMLAttribute`. See below. +- `mappingHasStandoffDataTypeClass` (0-1): Indicates the standoff data type class of the standoff class the XML element is mapped to. +- `mappingElementRequiresSeparator` (1): Indicates if there should be an invisible word separator inserted after the XML element in the RDF-standoff representation. + Once the markup is stripped, text segments that belonged to different elements may be concatenated. A `MappingXMLAttribute` has the following properties: +- `mappingHasXMLAttributename`: The name of the XML attribute that is mapped to a standoff property. +- `mappingHasXMLNamespace`: The namespace of the XML attribute that is mapped to a standoff property. If no namespace is given, `noNamespace` is used. +- `mappingHasStandoffProperty`: The standoff property the XML attribute is mapped to. -`mappingHasXMLAttributename` - -: The name of the XML attribute that is mapped to a standoff property. - -`mappingHasXMLNamespace` - -: The namespace of the XML attribute that is mapped to a standoff property. If no namespace is given, `noNamespace` is -used. - -`mappingHasStandoffProperty` - -: The standoff property the XML attribute is mapped to. - -Knora includes a standard mapping used by the SALSAH GUI. It has the IRI -`http://rdfh.ch/standoff/mappings/StandardMapping` and defines mappings for a few elements used to write texts with -simple markup. +DSP-API includes a standard mapping used by the DSP APP. It has the IRI `http://rdfh.ch/standoff/mappings/StandardMapping` and defines mappings for a few elements used to write texts with simple markup. #### Standoff in Digital Editions -Knora's standoff is designed to make it possible to convert XML documents to standoff and back. One application for this +DSP-API's standoff is designed to make it possible to convert XML documents to standoff and back. One application for this feature is an editing workflow in which an editor works in an XML editor, and the resulting XML documents are converted -to standoff and stored in Knora, where they can be searched and annotated. +to standoff and stored in the DSP, where they can be searched and annotated. If an editor wants to correct text that has been imported from XML into standoff, the text can be exported as XML, edited, and imported again. To preserve annotations on standoff tags across edits, each tag can automatically be given a -UUID. In a future version of the Knora base ontology, it will be possible to create annotations that point to UUIDs +UUID. In a future version of the Knora base ontology, it may be possible to create annotations that point to UUIDs rather than to IRIs. When a text is exported to XML, the UUIDs can be included in the XML. When the edited XML is imported again, it can be converted to new standoff tags with the same UUIDs. Annotations that applied to standoff tags in the previous version of the text will therefore also apply to equivalent tags in the new version. @@ -856,34 +785,18 @@ markup to be represented in XML. When non-hierarchical markup is converted to st end position of the standoff tag have indexes and parent indexes. To support these features, a standoff tag can have these additional properties: - -`standoffTagHasStartIndex` (0-1) - -: The index of the start position. - -`standoffTagHasEndIndex` (0-1) - -: The index of the end position, if this is a non-hierarchical tag. - -`standoffTagHasStartParent` (0-1) - -: The IRI of the tag, if any, that contains the start position. - -`standoffTagHasEndParent` (0-1) - -: The IRI of the tag, if any, that contains the end position, if this is a non-hierarchical tag. - -`standoffTagHasUUID` (0-1) - -: A UUID that can be used to annotate a standoff tag that may be present in different versions of a text, or in -different layers of a text (such as a diplomatic transcription and an edited critical text). +- `standoffTagHasStartIndex` (0-1): The index of the start position. +- `standoffTagHasEndIndex` (0-1): The index of the end position, if this is a non-hierarchical tag. +- `standoffTagHasStartParent` (0-1): The IRI of the tag, if any, that contains the start position. +- `standoffTagHasEndParent` (0-1): The IRI of the tag, if any, that contains the end position, if this is a non-hierarchical tag. +- `standoffTagHasUUID` (0-1): A UUID that can be used to annotate a standoff tag that may be present in different versions of a text, + or in different layers of a text (such as a diplomatic transcription and an edited critical text). #### Querying Standoff in SPARQL -A future version of Knora will provide an API for querying standoff markup. In the meantime, it is possible to query it -directly in SPARQL. For example, here is a SPARQL query (using RDFS inference) that finds all the text values texts that -have a standoff date tag referring to Christmas Eve 2016, contained in a -`StandoffItalicTag`: +A future version of DSP-API may provide an API for querying standoff markup. In the meantime, it is possible to query it +directly in SPARQL. For example, here is a SPARQL query (using RDFS inference) that finds all the text values that +have a standoff date tag referring to Christmas Eve 2016, contained in a `StandoffItalicTag`: ```sparql PREFIX knora-base: @@ -976,8 +889,7 @@ administrator of the project that the object belongs to. A user-created ontology can define additional groups, which must belong to the OWL class `knora-admin:UserGroup`. There is one built-in `knora-admin:SystemUser`, which is the creator of link values created automatically for resource -references in standoff markup (see -[StandoffLinkTag](#standofflinktag)). +references in standoff markup (see [StandoffLinkTag](#standofflinktag)). ### Permissions diff --git a/docs/03-apis/api-v2/editing-values.md b/docs/03-apis/api-v2/editing-values.md index a9942d0c74..269a985fb4 100644 --- a/docs/03-apis/api-v2/editing-values.md +++ b/docs/03-apis/api-v2/editing-values.md @@ -175,8 +175,11 @@ Use the predicate `knora-api:valueAsString` of `knora-api:TextValue`: ### Creating a Text Value with Standoff Markup Currently, the only way to create a text value with standoff markup is to submit it in XML format using an -[XML-to-standoff mapping](xml-to-standoff-mapping.md). For example, suppose we use the standard mapping, -`http://rdfh.ch/standoff/mappings/StandardMapping`. We can then make an XML document like this: +[XML-to-standoff mapping](xml-to-standoff-mapping.md). + +#### Creating a Text Value with Standard Mapping + +To create a value with the standard mapping (`http://rdfh.ch/standoff/mappings/StandardMapping`), we can make an XML document like this: ```xml @@ -207,6 +210,24 @@ This document can then be embedded in a JSON-LD request, using the predicate `kn Note that quotation marks and line breaks in the XML must be escaped, and that the IRI of the mapping must be provided. +#### Creating a Text Value with a Custom Mapping + +To create a text value with custom mapping, the following steps are required: + +1. Optionally, an XSL transformation resource (`kb:XSLTransformation`) can be created that may be defined as the default transformation of the mapping. +2. The mapping resource (`kb:XMLToStandoffMapping`) must be created, if it does not already exist. +3. The text value can be created as in the example above, using the mapping resource IRI in `kb:textValueHasMapping`. + +The `kb:XSLTransformation` resource is a subclass of `kb:TextRepresentation`, so it has a `kb:hasTextFileValue` pointing to a `kb:TextFileValue` +which represents the XSLT file stored in SIPI. For more Details, see [Creating File Values](#creating-file-values). + +The `kb:XMLToStandoffMapping` resource requires the mapping XML as specified [here](../api-v1/xml-to-standoff-mapping.md#creating-a-custom-mapping). +If an XSL transformation has been defined, the IRI the transformation can be placed in the `` tag of the mapping XML. + +If a mapping has been defined, then requesting the text value will return both the `kb:textValueAsXml` and the `kb:textValueAsHtml` properties, +where the XML can be used for editing the value, while the HTML can be used to display it. +If no mapping has been defined, only `kb:textValueAsXml` can be returned. + ## Creating File Values Knora supports the storage of certain types of data as files, using [Sipi](https://github.com/dhlab-basel/Sipi) diff --git a/docs/03-apis/api-v2/reading-and-searching-resources.md b/docs/03-apis/api-v2/reading-and-searching-resources.md index 89371d7608..80469c41be 100644 --- a/docs/03-apis/api-v2/reading-and-searching-resources.md +++ b/docs/03-apis/api-v2/reading-and-searching-resources.md @@ -50,13 +50,53 @@ See the interfaces `Resource` and `ResourcesSequence` in module Text markup can be returned in one of two ways: - As XML embedded in the response, using an [XML to Standoff Mapping](xml-to-standoff-mapping.md). - -- As [standoff/RDF](../../02-knora-ontologies/knora-base.md#text-with-standoff-markup), - which is Knora's internal markup representation. +- As [standoff/RDF](../../02-knora-ontologies/knora-base.md#text-with-standoff-markup), which is DSP-API's internal markup representation. Embedded XML is the default. -Implementation of support for standoff/RDF in API v2 is in its early stages. The basic +#### Requesting Text Markup as XML + +When requesting a text value with standoff mark up, there are three possibilities: + +1. The text value uses standard mapping. +2. The text value uses a custom mapping which *does not* specify an XSL transformation. +3. The text value uses a custom mapping which specifies an XSL transformation. + +In the first case, the mapping will be defined as: + +```json +"kb:textValueHasMapping": { + "@id": "http://rdfh.ch/standoff/mappings/StandardMapping" + } +``` + +the text value will only be available as `kb:textValueAsXml`, which will be of the following structure: + +```xml + + + ... + +``` + +where the content of `` is a limited set of HTML tags that can be handled by CKEditor in DSP-APP. +This allows for both displaying and editing the text value. + +In the second and third case, `kb:textValueHasMapping` will point to the custom mapping that may or may not specify an XSL transformation. + +If no transformation is specified (second case), the text value will be returned only as `kb:textValueAsXml`. +This property will be a string containing the contents of the initially uploaded XML. + +Note: The returned XML document is equivalent to the uploaded document but it is not necessarily identical - +the order of the attributes in one element may vary from the original. + +In the third case, when a transformation is specified, both `kb:textValueAsXml` and `kb:textValueAsHtml` will be returned. +`kb:textValueAsHtml` is the result of the XSL transformation applied to `kb:textValueAsXml`. +The HTML representation is intended to display the text value in a human readable and properly styled way, while the XML representation can be used to update the text value. + +#### Requesting Text Markup as Standoff + +Implementation of support for standoff/RDF in API v2 is in its early stages; its use is currently discouraged. The basic procedure works like this: First, request a resource in the [complex schema](introduction.md#api-schema), using any relevant diff --git a/sipi/images/0001/.gitignore b/sipi/images/0001/.gitignore index c96a04f008..117072d0c1 100644 --- a/sipi/images/0001/.gitignore +++ b/sipi/images/0001/.gitignore @@ -1,2 +1,5 @@ * -!.gitignore \ No newline at end of file +!.gitignore +# XSL transformation for standoff with custom mapping in freetest test data () +!Cpl5d73kOLz-FclZg2VVf6r.xsl +!Cpl5d73kOLz-FclZg2VVf6r.info diff --git a/sipi/images/0001/Cpl5d73kOLz-FclZg2VVf6r.info b/sipi/images/0001/Cpl5d73kOLz-FclZg2VVf6r.info new file mode 100644 index 0000000000..1410b62202 --- /dev/null +++ b/sipi/images/0001/Cpl5d73kOLz-FclZg2VVf6r.info @@ -0,0 +1,7 @@ +{ + "originalFilename": "standoffTransformation.xsl", + "checksumOriginal": "42ab2039da04d27446abc686fe93800e95cacf0935932bc4b001feb2243f3129", + "internalFilename": "Cpl5d73kOLz-FclZg2VVf6r.xsl", + "originalInternalFilename": "Cpl5d73kOLz-FclZg2VVf6r.xsl.orig", + "checksumDerivative": "42ab2039da04d27446abc686fe93800e95cacf0935932bc4b001feb2243f3129" +} \ No newline at end of file diff --git a/sipi/images/0001/Cpl5d73kOLz-FclZg2VVf6r.xsl b/sipi/images/0001/Cpl5d73kOLz-FclZg2VVf6r.xsl new file mode 100644 index 0000000000..31beb409ec --- /dev/null +++ b/sipi/images/0001/Cpl5d73kOLz-FclZg2VVf6r.xsl @@ -0,0 +1,26 @@ + + + + + + + +
+ +
+
+ + +

+ +

+
+ + + + + + + +
+ diff --git a/sipi/images/0801/.gitignore b/sipi/images/0801/.gitignore new file mode 100644 index 0000000000..c96a04f008 --- /dev/null +++ b/sipi/images/0801/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/sipi/images/0801/24PaAn6Qs6y-BM61fJZSUqv.txt b/sipi/images/0801/24PaAn6Qs6y-BM61fJZSUqv.txt deleted file mode 100644 index cf7c9142f9..0000000000 --- a/sipi/images/0801/24PaAn6Qs6y-BM61fJZSUqv.txt +++ /dev/null @@ -1,68 +0,0 @@ -PREFIX beol: -PREFIX knora-api: -PREFIX xsd: - - CONSTRUCT { - ?letter knora-api:isMainResource true . - - ?letter beol:creationDate ?date . - - ?letter beol:hasText ?text . - - ?letter knora-api:hasStandoffLinkTo ?standoffLinks . - - ?letter beol:hasAuthor ?person1 . - - ?person1 beol:hasFamilyName ?name1 . - - ?person1 beol:hasGivenName ?givenName1 . - - ?person1 beol:hasIAFIdentifier ?iaf1 . - - ?letter beol:hasRecipient ?person2 . - - ?person2 beol:hasFamilyName ?name2 . - - ?person2 beol:hasGivenName ?givenName2 . - - ?person2 beol:hasIAFIdentifier ?iaf2 . - - - } WHERE { - BIND(<$resourceIri> as ?letter) - - ?letter a beol:letter . - - ?letter beol:creationDate ?date . - - ?letter beol:hasText ?text . - - OPTIONAL { - ?letter knora-api:hasStandoffLinkTo ?standoffLinks . - } - - OPTIONAL { - - ?letter beol:hasAuthor ?person1 . - - ?person1 beol:hasFamilyName ?name1 . - - ?person1 beol:hasGivenName ?givenName1 . - - ?person1 beol:hasIAFIdentifier ?iaf1 . - - } - - OPTIONAL { - - ?letter beol:hasRecipient ?person2 . - - ?person2 beol:hasFamilyName ?name2 . - - ?person2 beol:hasGivenName ?givenName2 . - - ?person2 beol:hasIAFIdentifier ?iaf2 . - - } - - } diff --git a/sipi/images/0801/3oFWiltb5K8-CkJAyd1xKzq.xsl b/sipi/images/0801/3oFWiltb5K8-CkJAyd1xKzq.xsl deleted file mode 100644 index 2329743f54..0000000000 --- a/sipi/images/0801/3oFWiltb5K8-CkJAyd1xKzq.xsl +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - - - - 01 - - - - - - - - - 01 - - - - - -- - - - - - - - - - - 12 - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - - - - - - - - - <xsl:value-of select="$label"/> - - - -

This is the TEI/XML representation of the resource identified by the Iri - . -

-
- -

Representation of the resource's text as TEI/XML

-
-
- - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - , - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - , - - - - - - - - - - -
diff --git a/sipi/images/0801/GYcgKQrbSUo-CpXlnPubcWs.xsl b/sipi/images/0801/GYcgKQrbSUo-CpXlnPubcWs.xsl deleted file mode 100644 index 2329743f54..0000000000 --- a/sipi/images/0801/GYcgKQrbSUo-CpXlnPubcWs.xsl +++ /dev/null @@ -1,250 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - - - - 01 - - - - - - - - - 01 - - - - - -- - - - - - - - - - - 12 - - - - - - - - - - - - - - - - - -- - - - - - - - - - - - - - - - - - - - <xsl:value-of select="$label"/> - - - -

This is the TEI/XML representation of the resource identified by the Iri - . -

-
- -

Representation of the resource's text as TEI/XML

-
-
- - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - , - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - , - - - - - - - - - - -
diff --git a/sipi/images/0801/HOX3ZO0bmkn-DtOIUZBpvrP.xsl b/sipi/images/0801/HOX3ZO0bmkn-DtOIUZBpvrP.xsl deleted file mode 100644 index 1f7f91ca34..0000000000 --- a/sipi/images/0801/HOX3ZO0bmkn-DtOIUZBpvrP.xsl +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - numbered - - - - - - - - bulleted - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - italic - - - - - - - - bold - - - - - - - - underline - - - - - - - - - - - - - - - sup - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tex - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/sipi/images/0801/IhH6F5GD9K7-D1hIZjjmcUq.xsl b/sipi/images/0801/IhH6F5GD9K7-D1hIZjjmcUq.xsl deleted file mode 100644 index 1f7f91ca34..0000000000 --- a/sipi/images/0801/IhH6F5GD9K7-D1hIZjjmcUq.xsl +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - numbered - - - - - - - - bulleted - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - italic - - - - - - - - bold - - - - - - - - underline - - - - - - - - - - - - - - - sup - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - tex - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/sipi/images/0801/J6cQCyIFSZ4-EOdiCxSsgR5.txt b/sipi/images/0801/J6cQCyIFSZ4-EOdiCxSsgR5.txt deleted file mode 100644 index cf7c9142f9..0000000000 --- a/sipi/images/0801/J6cQCyIFSZ4-EOdiCxSsgR5.txt +++ /dev/null @@ -1,68 +0,0 @@ -PREFIX beol: -PREFIX knora-api: -PREFIX xsd: - - CONSTRUCT { - ?letter knora-api:isMainResource true . - - ?letter beol:creationDate ?date . - - ?letter beol:hasText ?text . - - ?letter knora-api:hasStandoffLinkTo ?standoffLinks . - - ?letter beol:hasAuthor ?person1 . - - ?person1 beol:hasFamilyName ?name1 . - - ?person1 beol:hasGivenName ?givenName1 . - - ?person1 beol:hasIAFIdentifier ?iaf1 . - - ?letter beol:hasRecipient ?person2 . - - ?person2 beol:hasFamilyName ?name2 . - - ?person2 beol:hasGivenName ?givenName2 . - - ?person2 beol:hasIAFIdentifier ?iaf2 . - - - } WHERE { - BIND(<$resourceIri> as ?letter) - - ?letter a beol:letter . - - ?letter beol:creationDate ?date . - - ?letter beol:hasText ?text . - - OPTIONAL { - ?letter knora-api:hasStandoffLinkTo ?standoffLinks . - } - - OPTIONAL { - - ?letter beol:hasAuthor ?person1 . - - ?person1 beol:hasFamilyName ?name1 . - - ?person1 beol:hasGivenName ?givenName1 . - - ?person1 beol:hasIAFIdentifier ?iaf1 . - - } - - OPTIONAL { - - ?letter beol:hasRecipient ?person2 . - - ?person2 beol:hasFamilyName ?name2 . - - ?person2 beol:hasGivenName ?givenName2 . - - ?person2 beol:hasIAFIdentifier ?iaf2 . - - } - - } diff --git a/sipi/images/originals/0001/.gitignore b/sipi/images/originals/0001/.gitignore new file mode 100644 index 0000000000..d6b7ef32c8 --- /dev/null +++ b/sipi/images/originals/0001/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/test_data/all_data/freetest-data.ttl b/test_data/all_data/freetest-data.ttl index e87e370463..5c870aafe8 100644 --- a/test_data/all_data/freetest-data.ttl +++ b/test_data/all_data/freetest-data.ttl @@ -1,102 +1,261 @@ -@prefix xml: . -@prefix xsd: . -@prefix rdf: . -@prefix rdfs: . -@prefix owl: . -@prefix foaf: . -@prefix knora-base: . +@prefix xml: . +@prefix xsd: . +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix foaf: . +@prefix knora-base: . @prefix knora-admin: . -@prefix salsah-gui: . -@prefix freetest: . - - a freetest:FreeTest ; - knora-base:attachedToUser ; - knora-base:attachedToProject ; - knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; - knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime; - - freetest:hasText ; - freetest:hasBoolean ; - rdfs:label "a free test instance"; - knora-base:isDeleted false . - - a knora-base:TextValue; - knora-base:valueHasUUID "SZyeLLmOTcCCuS3B0VksHQ"^^xsd:string; - knora-base:isDeleted false; - knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime; - knora-base:valueHasOrder 0; - knora-base:valueHasString "test"; - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; - knora-base:attachedToUser . - - a knora-base:BooleanValue; - knora-base:valueHasUUID "IN4R19yYR0ygi3K2VEHpUQ"^^xsd:string; - knora-base:isDeleted false; - knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime; - knora-base:valueHasBoolean true; - knora-base:valueHasOrder 0; - knora-base:valueHasString "true"; - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; - knora-base:attachedToUser . +@prefix salsah-gui: . +@prefix freetest: . +@prefix standoff: . + + + a freetest:FreeTest ; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember" ; + knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime ; + freetest:hasText ; + freetest:hasTextWithStandoff ; + freetest:hasTextWithStandoff ; + freetest:hasBoolean ; + rdfs:label "a free test instance" ; + knora-base:isDeleted false . + + + a knora-base:TextValue ; + knora-base:valueHasUUID "SZyeLLmOTcCCuS3B0VksHQ"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "test" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . + + + rdf:type knora-base:TextValue ; + knora-base:attachedToUser ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2022-02-22T16:54:46.069245Z"^^xsd:dateTime ; + knora-base:valueHasMapping ; + knora-base:valueHasMaxStandoffStartIndex 3 ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasStandoff , + , + , + ; + knora-base:valueHasString "this is a text with standoff markup" ; + knora-base:valueHasUUID "BuIL3c-ZRAy_avBIo6aGxA" . + + + + rdf:type standoff:StandoffRootTag ; + knora-base:standoffTagHasEnd 36 ; + knora-base:standoffTagHasStart 0 ; + knora-base:standoffTagHasStartIndex 0 ; + knora-base:standoffTagHasUUID "QVku2XzRR--EnJXA00lnRg" . + + + rdf:type standoff:StandoffParagraphTag ; + knora-base:standoffTagHasEnd 35 ; + knora-base:standoffTagHasStart 0 ; + knora-base:standoffTagHasStartIndex 1 ; + knora-base:standoffTagHasStartParent ; + knora-base:standoffTagHasUUID "XQOBoOVsQuO9CFhy9m-O2w" . + + + rdf:type standoff:StandoffBoldTag ; + knora-base:standoffTagHasEnd 14 ; + knora-base:standoffTagHasStart 10 ; + knora-base:standoffTagHasStartIndex 2 ; + knora-base:standoffTagHasStartParent ; + knora-base:standoffTagHasUUID "gFH-NHUyRB2X12M_4eWd5Q" . + + + rdf:type standoff:StandoffItalicTag ; + knora-base:standoffTagHasEnd 28 ; + knora-base:standoffTagHasStart 20 ; + knora-base:standoffTagHasStartIndex 3 ; + knora-base:standoffTagHasStartParent ; + knora-base:standoffTagHasUUID "Y43nuPJoR1WIGXELXDP8Iw" . + + + rdf:type knora-base:XSLTransformation ; + rdfs:label "XSL Transformation for custom standoff mapping with default transformation" ; + knora-base:attachedToProject ; + knora-base:attachedToUser ; + knora-base:creationDate "2022-02-24T10:19:42.146548Z"^^xsd:dateTime ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:hasTextFileValue ; + knora-base:isDeleted false . + + + rdf:type knora-base:TextFileValue ; + knora-base:attachedToUser ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:internalFilename "Cpl5d73kOLz-FclZg2VVf6r.xsl" ; + knora-base:internalMimeType "text/xml" ; + knora-base:isDeleted false ; + knora-base:originalFilename "standoffTransformation.xsl" ; + knora-base:valueCreationDate "2022-02-24T10:19:42.146548Z"^^xsd:dateTime ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "Cpl5d73kOLz-FclZg2VVf6r.xsl" ; + knora-base:valueHasUUID "51vqUgh-SwCFtJXbwYMPhQ" . + + + + rdf:type knora-base:XMLToStandoffMapping ; + rdfs:label "A custom standoff mapping with a specified default transformation" ; + knora-base:hasMappingElement , + , + ; + knora-base:mappingHasDefaultXSLTransformation . + + + rdf:type knora-base:MappingElement ; + knora-base:mappingElementRequiresSeparator false ; + knora-base:mappingHasStandoffClass standoff:StandoffRootTag ; + knora-base:mappingHasXMLAttribute ; + knora-base:mappingHasXMLClass "noClass" ; + knora-base:mappingHasXMLNamespace "noNamespace" ; + knora-base:mappingHasXMLTagname "text" . + + + rdf:type knora-base:MappingXMLAttribute ; + knora-base:mappingHasStandoffProperty standoff:standoffRootTagHasDocumentType ; + knora-base:mappingHasXMLAttributename "documentType" ; + knora-base:mappingHasXMLNamespace "noNamespace" . + + + rdf:type knora-base:MappingElement ; + knora-base:mappingElementRequiresSeparator false ; + knora-base:mappingHasStandoffClass standoff:StandoffParagraphTag ; + knora-base:mappingHasXMLClass "noClass" ; + knora-base:mappingHasXMLNamespace "noNamespace" ; + knora-base:mappingHasXMLTagname "section" . + + + rdf:type knora-base:MappingElement ; + knora-base:mappingElementRequiresSeparator false ; + knora-base:mappingHasStandoffClass standoff:StandoffItalicTag ; + knora-base:mappingHasXMLClass "noClass" ; + knora-base:mappingHasXMLNamespace "noNamespace" ; + knora-base:mappingHasXMLTagname "italic" . + + + rdf:type knora-base:TextValue ; + knora-base:attachedToUser ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2022-02-24T11:21:27.625681Z"^^xsd:dateTime ; + knora-base:valueHasMapping ; + knora-base:valueHasMaxStandoffStartIndex 2 ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasStandoff , + , + ; + knora-base:valueHasString "\n This is a sample of standoff text. \n" ; + knora-base:valueHasUUID "FjLCKr2WRoCyZAstbIIWAQ" . + + + rdf:type standoff:StandoffRootTag ; + knora-base:standoffTagHasEnd 42 ; + knora-base:standoffTagHasStart 0 ; + knora-base:standoffTagHasStartIndex 0 ; + knora-base:standoffTagHasUUID "u-Ds5S3yQpSZgYWyoXVy0Q" . + + + rdf:type standoff:StandoffParagraphTag ; + knora-base:standoffTagHasEnd 41 ; + knora-base:standoffTagHasStart 5 ; + knora-base:standoffTagHasStartIndex 1 ; + knora-base:standoffTagHasStartParent ; + knora-base:standoffTagHasUUID "trSBCX5qQHq7PHEOnTM8IA" . + + + rdf:type standoff:StandoffItalicTag ; + knora-base:standoffTagHasEnd 22 ; + knora-base:standoffTagHasStart 16 ; + knora-base:standoffTagHasStartIndex 2 ; + knora-base:standoffTagHasStartParent ; + knora-base:standoffTagHasUUID "1_J0FTIKTMeeG9Wn40J65w" . + + + + a knora-base:BooleanValue ; + knora-base:valueHasUUID "IN4R19yYR0ygi3K2VEHpUQ"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime ; + knora-base:valueHasBoolean true ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "true" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . ## A resource of a subclass - a freetest:ShortFreeTest ; - knora-base:attachedToUser ; - knora-base:attachedToProject ; - knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; - knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime; - - freetest:hasText ; - freetest:hasBoolean ; - freetest:hasDecimal ; - rdfs:label "a short free test instance"; - knora-base:isDeleted false . - - a knora-base:TextValue; - knora-base:valueHasUUID "SZyeLLmOTcCCuS3B0VksHQ"^^xsd:string; - knora-base:isDeleted false; - knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime; - knora-base:valueHasOrder 0; - knora-base:valueHasString "test"; - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; - knora-base:attachedToUser . - - a knora-base:BooleanValue; - knora-base:valueHasUUID "IN4R19yYR0ygi3K2VEHpUQ"^^xsd:string; - knora-base:isDeleted false; - knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime; - knora-base:valueHasBoolean true; - knora-base:valueHasOrder 0; - knora-base:valueHasString "true"; - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; - knora-base:attachedToUser . - - a knora-base:DecimalValue; - knora-base:valueHasUUID "bXMwnrHvQH2DMjOFrGmNzg"^^xsd:string; - knora-base:isDeleted false; - knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime; - knora-base:valueHasDecimal "1.5"^^xsd:decimal; - knora-base:valueHasOrder 0; - knora-base:valueHasString "1.5"; - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; - knora-base:attachedToUser . - - a freetest:FreeTestResourceClass ; - knora-base:attachedToUser ; - knora-base:attachedToProject ; - knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember"; - knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime; - - freetest:hasIntegerProperty ; - rdfs:label "a free test resource class instance"; - knora-base:isDeleted false . - - a knora-base:IntValue; - knora-base:valueHasUUID "bXMwnrHvQH2DMjOFrGmNzg"^^xsd:string; - knora-base:isDeleted false; - knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime; - knora-base:valueHasInteger "1"^^xsd:integer; - knora-base:valueHasOrder 0; - knora-base:valueHasString "1"; - knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser"; - knora-base:attachedToUser . + + a freetest:ShortFreeTest ; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember" ; + knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime ; + freetest:hasText ; + freetest:hasBoolean ; + freetest:hasDecimal ; + rdfs:label "a short free test instance" ; + knora-base:isDeleted false . + + + a knora-base:TextValue ; + knora-base:valueHasUUID "SZyeLLmOTcCCuS3B0VksHQ"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "test" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . + + + a knora-base:BooleanValue ; + knora-base:valueHasUUID "IN4R19yYR0ygi3K2VEHpUQ"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime ; + knora-base:valueHasBoolean true ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "true" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . + + + a knora-base:DecimalValue ; + knora-base:valueHasUUID "bXMwnrHvQH2DMjOFrGmNzg"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime ; + knora-base:valueHasDecimal "1.5"^^xsd:decimal ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "1.5" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . + + + a freetest:FreeTestResourceClass ; + knora-base:attachedToUser ; + knora-base:attachedToProject ; + knora-base:hasPermissions "V knora-admin:UnknownUser|M knora-admin:ProjectMember" ; + knora-base:creationDate "2019-11-29T10:00:00.673298Z"^^xsd:dateTime ; + freetest:hasIntegerProperty ; + rdfs:label "a free test resource class instance" ; + knora-base:isDeleted false . + + + a knora-base:IntValue ; + knora-base:valueHasUUID "bXMwnrHvQH2DMjOFrGmNzg"^^xsd:string ; + knora-base:isDeleted false ; + knora-base:valueCreationDate "2018-05-28T15:52:03.897Z"^^xsd:dateTime ; + knora-base:valueHasInteger "1"^^xsd:integer ; + knora-base:valueHasOrder 0 ; + knora-base:valueHasString "1" ; + knora-base:hasPermissions "CR knora-admin:Creator|M knora-admin:ProjectMember|V knora-admin:KnownUser|RV knora-admin:UnknownUser" ; + knora-base:attachedToUser . diff --git a/test_data/ontologies/freetest-onto.ttl b/test_data/ontologies/freetest-onto.ttl index b252a4a854..a178b8b481 100644 --- a/test_data/ontologies/freetest-onto.ttl +++ b/test_data/ontologies/freetest-onto.ttl @@ -23,47 +23,65 @@ :hasText rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Text"@de, "Texte"@fr, "Testo"@it, "Text"@en ; + rdfs:label "Text"@de, + "Texte"@fr, + "Testo"@it, + "Text"@en ; knora-base:subjectClassConstraint :FreeTest ; knora-base:objectClassConstraint knora-base:TextValue ; salsah-gui:guiElement salsah-gui:SimpleText ; - salsah-gui:guiAttribute "size=80", "maxlength=255" . + salsah-gui:guiAttribute "size=80", + "maxlength=255" . :hasInteger rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Ganzzahl"@de, "Nombre entier"@fr, "Intero"@it, "Integer"@en ; + rdfs:label "Ganzzahl"@de, + "Nombre entier"@fr, + "Intero"@it, + "Integer"@en ; knora-base:subjectClassConstraint :FreeTest ; knora-base:objectClassConstraint knora-base:IntValue ; salsah-gui:guiElement salsah-gui:Spinbox ; - salsah-gui:guiAttribute "min=0", "max=-1" . + salsah-gui:guiAttribute "min=0", + "max=-1" . :hasIntegerProperty rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Ganzzahl"@de, "Nombre entier"@fr, "Intero"@it, "Integer"@en ; + rdfs:label "Ganzzahl"@de, + "Nombre entier"@fr, + "Intero"@it, + "Integer"@en ; knora-base:objectClassConstraint knora-base:IntValue ; salsah-gui:guiElement salsah-gui:Spinbox ; - salsah-gui:guiAttribute "min=0", "max=-1" . + salsah-gui:guiAttribute "min=0", + "max=-1" . :hasDecimal rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Dezimalzahl"@de, "Nombre décimal"@fr, "Numero decimale"@it, "Decimal number"@en ; + rdfs:label "Dezimalzahl"@de, + "Nombre décimal"@fr, + "Numero decimale"@it, + "Decimal number"@en ; knora-base:subjectClassConstraint :FreeTest ; knora-base:objectClassConstraint knora-base:DecimalValue ; salsah-gui:guiElement salsah-gui:SimpleText ; - salsah-gui:guiAttribute "size=80", "maxlength=255" . + salsah-gui:guiAttribute "size=80", + "maxlength=255" . :hasBoolean rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Boolescher Wert"@de, "Valeur booléenne"@fr, "Valore booleano"@it, + rdfs:label "Boolescher Wert"@de, + "Valeur booléenne"@fr, + "Valore booleano"@it, "Boolean value"@en ; knora-base:subjectClassConstraint :FreeTest ; knora-base:objectClassConstraint knora-base:BooleanValue ; @@ -72,100 +90,104 @@ :FreeTest - rdf:type owl:Class ; - rdfs:subClassOf knora-base:Resource, - [ rdf:type - owl:Restriction ; - owl:onProperty :hasText ; - owl:minCardinality - "1"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "1"^^xsd:nonNegativeInteger ], - [ rdf:type - owl:Restriction ; - owl:onProperty - :hasBoolean ; - owl:maxCardinality - "1"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "2"^^xsd:nonNegativeInteger ], - [ rdf:type - owl:Restriction ; - owl:onProperty - :hasDecimal ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "3"^^xsd:nonNegativeInteger ], - [ rdf:type - owl:Restriction ; - owl:onProperty - :hasInteger ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "4"^^xsd:nonNegativeInteger ], - [ rdf:type - owl:Restriction ; - owl:onProperty - :hasIntegerProperty ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "5"^^xsd:nonNegativeInteger ] ; - knora-base:resourceIcon "thing.png" ; - rdfs:label "FT de"@de, "FT fr"@fr, - "FT it"@it, "FT en"@en ; - rdfs:comment """A comment for FT."""@de . + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource, + [ rdf:type owl:Restriction ; + owl:onProperty :hasText ; + owl:minCardinality "1"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasBoolean ; + owl:maxCardinality "1"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "2"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasDecimal ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "3"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasInteger ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "4"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasIntegerProperty ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "5"^^xsd:nonNegativeInteger ] , + [ rdf:type owl:Restriction ; + owl:onProperty :hasTextWithStandoff ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "6"^^xsd:nonNegativeInteger ] ; + knora-base:resourceIcon "thing.png" ; + rdfs:label "FT de"@de, + "FT fr"@fr, + "FT it"@it, + "FT en"@en ; + rdfs:comment """A comment for FT."""@de . :ShortFreeTest rdf:type owl:Class ; rdfs:subClassOf :FreeTest ; - rdfs:label "SFT de"@de, "SFT fr"@fr, "SFT it"@it, "SFT en"@en ; + rdfs:label "SFT de"@de, + "SFT fr"@fr, + "SFT it"@it, + "SFT en"@en ; rdfs:comment """A comment for SFT."""@de . :FreeTestResourceClass rdf:type owl:Class ; - rdfs:subClassOf knora-base:Resource, [ rdf:type owl:Restriction ; - owl:onProperty :hasIntegerProperty ; - owl:minCardinality "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; - rdfs:label "FTRC de"@de, "FTRC fr"@fr, "FTRC it"@it, "FTRC en"@en ; + rdfs:subClassOf knora-base:Resource, + [ rdf:type owl:Restriction ; + owl:onProperty :hasIntegerProperty ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; + rdfs:label "FTRC de"@de, + "FTRC fr"@fr, + "FTRC it"@it, + "FTRC en"@en ; rdfs:comment """A comment for FTRC."""@de . :Author rdf:type owl:Class ; - rdfs:subClassOf knora-base:Resource, [ rdf:type owl:Restriction ; - owl:onProperty :hasName ; - owl:minCardinality "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; - rdfs:label "Autor"@de, "Author"@en ; + rdfs:subClassOf knora-base:Resource, + [ rdf:type owl:Restriction ; + owl:onProperty :hasName ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ] ; + rdfs:label "Autor"@de, + "Author"@en ; rdfs:comment """An author"""@en . :ScientificAuthor rdf:type owl:Class ; rdfs:subClassOf :Author ; - rdfs:label "Wissenschaftlicher Autor"@de, "Scientific author"@en ; + rdfs:label "Wissenschaftlicher Autor"@de, + "Scientific author"@en ; rdfs:comment """A scientific author"""@en . :hasName rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasValue ; - rdfs:label "Name"@de, "Nom"@fr, "Nome"@it, "Name"@en ; + rdfs:label "Name"@de, + "Nom"@fr, + "Nome"@it, + "Name"@en ; knora-base:objectClassConstraint knora-base:TextValue ; salsah-gui:guiElement salsah-gui:SimpleText ; - salsah-gui:guiAttribute "size=80", "maxlength=255" . + salsah-gui:guiAttribute "size=80", + "maxlength=255" . :hasAuthor rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasLinkTo ; - rdfs:label "Autor"@de, "Auteur"@fr, "Autore"@it, "Author"@en ; + rdfs:label "Autor"@de, + "Auteur"@fr, + "Autore"@it, + "Author"@en ; knora-base:subjectClassConstraint :Book ; knora-base:objectClassConstraint :Author ; salsah-gui:guiElement salsah-gui:Searchbox . @@ -173,7 +195,10 @@ :hasAuthorValue rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasLinkToValue ; - rdfs:label "Autor"@de, "Auteur"@fr, "Autore"@it, "Author"@en ; + rdfs:label "Autor"@de, + "Auteur"@fr, + "Autore"@it, + "Author"@en ; knora-base:subjectClassConstraint :Book ; knora-base:objectClassConstraint knora-base:LinkValue ; salsah-gui:guiElement salsah-gui:Searchbox . @@ -181,7 +206,8 @@ :hasScientificAuthor rdf:type owl:ObjectProperty ; rdfs:subPropertyOf :hasAuthor ; - rdfs:label "Wissenschaftlicher Autor"@de, "Scientific author"@en ; + rdfs:label "Wissenschaftlicher Autor"@de, + "Scientific author"@en ; knora-base:subjectClassConstraint :ScientificBook ; knora-base:objectClassConstraint :ScientificAuthor ; salsah-gui:guiElement salsah-gui:Searchbox . @@ -189,52 +215,48 @@ :hasScientificAuthorValue rdf:type owl:ObjectProperty ; rdfs:subPropertyOf :hasAuthorValue ; - rdfs:label "Wissenschaftlicher Autor"@de, "Scientific author"@en ; + rdfs:label "Wissenschaftlicher Autor"@de, + "Scientific author"@en ; knora-base:subjectClassConstraint :ScientificBook ; knora-base:objectClassConstraint knora-base:LinkValue ; salsah-gui:guiElement salsah-gui:Searchbox . :Book - rdf:type owl:Class ; - rdfs:subClassOf knora-base:Resource ; - rdfs:subClassOf [ rdf:type owl:Restriction ; - owl:onProperty :hasAuthor ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "1"^^xsd:nonNegativeInteger ], - [ rdf:type owl:Restriction ; - owl:onProperty :hasAuthorValue ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "2"^^xsd:nonNegativeInteger ] ; - rdfs:label "Buch"@de, "Book"@en ; - rdfs:comment """A comment for book"""@en . + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource ; + rdfs:subClassOf [ rdf:type owl:Restriction ; + owl:onProperty :hasAuthor ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasAuthorValue ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "2"^^xsd:nonNegativeInteger ] ; + rdfs:label "Buch"@de, + "Book"@en ; + rdfs:comment """A comment for book"""@en . :ScientificBook - rdf:type owl:Class ; - rdfs:subClassOf :Book ; - rdfs:subClassOf [ rdf:type owl:Restriction ; - owl:onProperty :hasScientificAuthor ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "1"^^xsd:nonNegativeInteger ], - [ rdf:type owl:Restriction ; - owl:onProperty :hasScientificAuthorValue ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "2"^^xsd:nonNegativeInteger ] ; - rdfs:label "Wissenschaftliches Buch"@de, - "Scientific book"@en ; - rdfs:comment """A comment for a scientific book"""@en . + rdf:type owl:Class ; + rdfs:subClassOf :Book ; + rdfs:subClassOf [ rdf:type owl:Restriction ; + owl:onProperty :hasScientificAuthor ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasScientificAuthorValue ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "2"^^xsd:nonNegativeInteger ] ; + rdfs:label "Wissenschaftliches Buch"@de, + "Scientific book"@en ; + rdfs:comment """A comment for a scientific book"""@en . :hasOtherCULP rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasLinkTo ; - rdfs:label "Ein anderes CULP"@de, "Une autre CULP"@fr, "Un'altra CULP"@it, + rdfs:label "Ein anderes CULP"@de, + "Une autre CULP"@fr, + "Un'altra CULP"@it, "Another CULP"@en ; knora-base:subjectClassConstraint :ClassUsingLinkProperties ; knora-base:objectClassConstraint :ClassUsingLinkProperties ; @@ -244,27 +266,36 @@ :hasOtherCULPValue rdf:type owl:ObjectProperty ; rdfs:subPropertyOf knora-base:hasLinkToValue ; - rdfs:label "Ein anderes Ding"@de, "Une autre chose"@fr, "Un'altra cosa"@it, + rdfs:label "Ein anderes Ding"@de, + "Une autre chose"@fr, + "Un'altra cosa"@it, "Another thing"@en ; knora-base:subjectClassConstraint :ClassUsingLinkProperties ; knora-base:objectClassConstraint knora-base:LinkValue ; salsah-gui:guiElement salsah-gui:Searchbox . :ClassUsingLinkProperties - rdf:type owl:Class ; - rdfs:subClassOf knora-base:Resource ; - rdfs:subClassOf [ rdf:type owl:Restriction ; - owl:onProperty :hasOtherCULP ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "3"^^xsd:nonNegativeInteger ], - [ rdf:type owl:Restriction ; - owl:onProperty :hasOtherCULPValue ; - owl:minCardinality - "0"^^xsd:nonNegativeInteger ; - salsah-gui:guiOrder - "4"^^xsd:nonNegativeInteger ] ; - rdfs:label "CULP de"@de, "CULP fr"@fr, "CULP it"@it, - "CULP en"@en ; - rdfs:comment """A comment for CULP."""@de . + rdf:type owl:Class ; + rdfs:subClassOf knora-base:Resource ; + rdfs:subClassOf [ rdf:type owl:Restriction ; + owl:onProperty :hasOtherCULP ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "3"^^xsd:nonNegativeInteger ], + [ rdf:type owl:Restriction ; + owl:onProperty :hasOtherCULPValue ; + owl:minCardinality "0"^^xsd:nonNegativeInteger ; + salsah-gui:guiOrder "4"^^xsd:nonNegativeInteger ] ; + rdfs:label "CULP de"@de, + "CULP fr"@fr, + "CULP it"@it, + "CULP en"@en ; + rdfs:comment """A comment for CULP."""@de . + +:hasTextWithStandoff + rdf:type owl:ObjectProperty ; + rdfs:comment "the text with standoff markup"@en ; + rdfs:label "the text with standoff markup"@en ; + rdfs:subPropertyOf knora-base:hasValue ; + knora-base:objectClassConstraint + knora-base:TextValue ; + salsah-gui:guiElement salsah-gui:Richtext . diff --git a/test_data/test_route/texts/freetestCustomMapping.xml b/test_data/test_route/texts/freetestCustomMapping.xml new file mode 100644 index 0000000000..69387da304 --- /dev/null +++ b/test_data/test_route/texts/freetestCustomMapping.xml @@ -0,0 +1,48 @@ + + + + + + text + noClass + noNamespace + false + + + http://www.knora.org/ontology/standoff#StandoffRootTag + + + documentType + noNamespace + http://www.knora.org/ontology/standoff#standoffRootTagHasDocumentType + + + + + + + + section + noClass + noNamespace + false + + + http://www.knora.org/ontology/standoff#StandoffParagraphTag + + + + + + italic + noClass + noNamespace + false + + + http://www.knora.org/ontology/standoff#StandoffItalicTag + + + + diff --git a/test_data/test_route/texts/freetestCustomMappingTransformation.xsl b/test_data/test_route/texts/freetestCustomMappingTransformation.xsl new file mode 100644 index 0000000000..70b4015d20 --- /dev/null +++ b/test_data/test_route/texts/freetestCustomMappingTransformation.xsl @@ -0,0 +1,25 @@ + + + + + + + +
+ +
+
+ + +

+ +

+
+ + + + + + + +
diff --git a/test_data/test_route/texts/freetestCustomMappingWithTransformation.xml b/test_data/test_route/texts/freetestCustomMappingWithTransformation.xml new file mode 100644 index 0000000000..25c09d10ff --- /dev/null +++ b/test_data/test_route/texts/freetestCustomMappingWithTransformation.xml @@ -0,0 +1,48 @@ + + + http://rdfh.ch/0001/xYSnl8dmTw2RM6KQGVqNDA + + + text + noClass + noNamespace + false + + + http://www.knora.org/ontology/standoff#StandoffRootTag + + + documentType + noNamespace + http://www.knora.org/ontology/standoff#standoffRootTagHasDocumentType + + + + + + + + section + noClass + noNamespace + false + + + http://www.knora.org/ontology/standoff#StandoffParagraphTag + + + + + + italic + noClass + noNamespace + false + + + http://www.knora.org/ontology/standoff#StandoffItalicTag + + + + diff --git a/test_data/test_route/texts/freetestXMLTextValue.xml b/test_data/test_route/texts/freetestXMLTextValue.xml new file mode 100644 index 0000000000..e3b7f7e00a --- /dev/null +++ b/test_data/test_route/texts/freetestXMLTextValue.xml @@ -0,0 +1,4 @@ + + +
This is a sample of standoff text.
+
\ No newline at end of file diff --git a/test_data/test_route/texts/mappingForHTML.xml b/test_data/test_route/texts/mappingForHTML.xml index fe3ecc3c8a..f77e894760 100644 --- a/test_data/test_route/texts/mappingForHTML.xml +++ b/test_data/test_route/texts/mappingForHTML.xml @@ -1,5 +1,5 @@ - + text noClass diff --git a/test_data/test_route/texts/mappingForLetter.xml b/test_data/test_route/texts/mappingForLetter.xml index bbbb643ff7..1770adcfcb 100644 --- a/test_data/test_route/texts/mappingForLetter.xml +++ b/test_data/test_route/texts/mappingForLetter.xml @@ -1,6 +1,6 @@ + xsi:noNamespaceSchemaLocation="../../../webapi/src/main/resources/mappingXMLToStandoff.xsd"> text diff --git a/test_data/test_route/texts/mappingForLetterWithXSLTransformation.xml b/test_data/test_route/texts/mappingForLetterWithXSLTransformation.xml index 51e235e2f4..0263f6fd0b 100644 --- a/test_data/test_route/texts/mappingForLetterWithXSLTransformation.xml +++ b/test_data/test_route/texts/mappingForLetterWithXSLTransformation.xml @@ -1,6 +1,6 @@ + xsi:noNamespaceSchemaLocation="../../../webapi/src/main/resources/mappingXMLToStandoff.xsd"> toBeDefined diff --git a/test_data/test_route/texts/mappingForStandardHTML.xml b/test_data/test_route/texts/mappingForStandardHTML.xml index 9d5593c3e0..711a13221e 100644 --- a/test_data/test_route/texts/mappingForStandardHTML.xml +++ b/test_data/test_route/texts/mappingForStandardHTML.xml @@ -1,5 +1,5 @@ - + text diff --git a/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala b/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala index 3f6e1839c0..3e944d76a2 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/store/sipimessages/SipiMessages.scala @@ -8,6 +8,8 @@ package org.knora.webapi.messages.store.sipimessages import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.StoreRequest import org.knora.webapi.messages.traits.RequestWithSender +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport +import spray.json._ /** * An abstract trait for messages that can be sent to the [[org.knora.webapi.store.iiif.IIIFManager]] @@ -110,3 +112,32 @@ case object IIIFServiceStatusOK extends IIIFServiceStatusResponse * Represents a negative response for [[IIIFServiceGetStatus]]. */ case object IIIFServiceStatusNOK extends IIIFServiceStatusResponse + +/** + * Represents the information that Sipi returns about each file that has been uploaded. + * + * @param originalFilename the original filename that was submitted to Sipi. + * @param internalFilename Sipi's internal filename for the stored temporary file. + * @param temporaryUrl the URL at which the temporary file can be accessed. + * @param fileType `image`, `text`, or `document`. + */ +case class SipiUploadResponseEntry( + originalFilename: String, + internalFilename: String, + temporaryUrl: String, + fileType: String +) + +/** + * Represents Sipi's response to a file upload request. + * + * @param uploadedFiles the information about each file that was uploaded. + */ +case class SipiUploadResponse(uploadedFiles: Seq[SipiUploadResponseEntry]) + +object SipiUploadResponseJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol { + implicit val sipiUploadResponseEntryFormat: RootJsonFormat[SipiUploadResponseEntry] = jsonFormat4( + SipiUploadResponseEntry + ) + implicit val sipiUploadResponseFormat: RootJsonFormat[SipiUploadResponse] = jsonFormat1(SipiUploadResponse) +} diff --git a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala index 079664e3ae..d5ce233911 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/v2/responder/valuemessages/ValueMessagesV2.scala @@ -1825,7 +1825,13 @@ case class TextValueContentV2( ) // the xml was converted to HTML - Map(OntologyConstants.KnoraApiV2Complex.TextValueAsHtml -> JsonLDString(xmlTransformed)) + Map( + OntologyConstants.KnoraApiV2Complex.TextValueAsHtml -> JsonLDString(xmlTransformed), + OntologyConstants.KnoraApiV2Complex.TextValueAsXml -> JsonLDString(xmlFromStandoff), + OntologyConstants.KnoraApiV2Complex.TextValueHasMapping -> JsonLDUtil.iriToJsonLDObject( + definedMappingIri + ) + ) case None => Map( @@ -1896,13 +1902,11 @@ case class TextValueContentV2( standoffTag: CreateStandoffTagV2InTriplestore => // resolve original XML ids to standoff node Iris for `StandoffTagInternalReferenceAttributeV2` val attributesWithStandoffNodeIriReferences: Seq[StandoffTagAttributeV2] = - standoffTag.standoffNode.attributes.map { attributeWithOriginalXMLID: StandoffTagAttributeV2 => - attributeWithOriginalXMLID match { - case refAttr: StandoffTagInternalReferenceAttributeV2 => - // resolve the XML id to the corresponding standoff node IRI - refAttr.copy(value = iDsToStandoffNodeIris(refAttr.value)) - case attr => attr - } + standoffTag.standoffNode.attributes.map { + case refAttr: StandoffTagInternalReferenceAttributeV2 => + // resolve the XML id to the corresponding standoff node IRI + refAttr.copy(value = iDsToStandoffNodeIris(refAttr.value)) + case attr => attr } val startParentIndex: Option[Int] = standoffTag.standoffNode.startParentIndex diff --git a/webapi/src/test/scala/org/knora/webapi/E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/E2ESpec.scala index a842650b1f..bc94bd193c 100644 --- a/webapi/src/test/scala/org/knora/webapi/E2ESpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/E2ESpec.scala @@ -14,11 +14,11 @@ import messages.store.triplestoremessages.{RdfDataObject, TriplestoreJsonProtoco import messages.util.rdf._ import settings._ import util.{FileUtil, StartupUtils} - import akka.actor.{ActorRef, ActorSystem, Props} import akka.event.LoggingAdapter import akka.http.scaladsl.Http import akka.http.scaladsl.client.RequestBuilding +import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import akka.http.scaladsl.model._ import akka.stream.Materializer import akka.testkit.{ImplicitSender, TestKit} @@ -102,7 +102,7 @@ class E2ESpec(_system: ActorSystem) appActor ! SetAllowReloadOverHTTPState(true) // start the knora service, loading data from the repository - appActor ! AppStart(ignoreRepository = true, requiresIIIFService = false) + appActor ! AppStart(ignoreRepository = true, requiresIIIFService = true) // waits until knora is up and running applicationStateRunning() diff --git a/webapi/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala index 601cf1a8a3..2b73a23e57 100644 --- a/webapi/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/ITKnoraLiveSpec.scala @@ -23,6 +23,8 @@ import org.knora.webapi.exceptions.AssertionException import org.knora.webapi.messages.StringFormatter import org.knora.webapi.messages.app.appmessages.{AppStart, AppStop, SetAllowReloadOverHTTPState} import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, TriplestoreJsonProtocol} +import org.knora.webapi.messages.store.sipimessages._ +import org.knora.webapi.messages.store.sipimessages.SipiUploadResponseJsonProtocol._ import org.knora.webapi.messages.util.rdf.{JsonLDDocument, JsonLDUtil, RdfFeatureFactory} import org.knora.webapi.settings._ import org.knora.webapi.util.StartupUtils @@ -181,37 +183,6 @@ class ITKnoraLiveSpec(_system: ActorSystem) */ protected case class InputFile(fileToUpload: FileToUpload, width: Int, height: Int) - /** - * Represents the information that Sipi returns about each file that has been uploaded. - * - * @param originalFilename the original filename that was submitted to Sipi. - * @param internalFilename Sipi's internal filename for the stored temporary file. - * @param temporaryUrl the URL at which the temporary file can be accessed. - * @param fileType `image`, `text`, or `document`. - */ - protected case class SipiUploadResponseEntry( - originalFilename: String, - internalFilename: String, - temporaryUrl: String, - fileType: String - ) - - /** - * Represents Sipi's response to a file upload request. - * - * @param uploadedFiles the information about each file that was uploaded. - */ - protected case class SipiUploadResponse(uploadedFiles: Seq[SipiUploadResponseEntry]) - - object SipiUploadResponseJsonProtocol extends SprayJsonSupport with DefaultJsonProtocol { - implicit val sipiUploadResponseEntryFormat: RootJsonFormat[SipiUploadResponseEntry] = jsonFormat4( - SipiUploadResponseEntry - ) - implicit val sipiUploadResponseFormat: RootJsonFormat[SipiUploadResponse] = jsonFormat1(SipiUploadResponse) - } - - import SipiUploadResponseJsonProtocol._ - /** * Uploads a file to Sipi and returns the information in Sipi's response. * diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/StandoffRouteV2E2ESpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/StandoffRouteV2E2ESpec.scala new file mode 100644 index 0000000000..6e17f4d9a5 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/e2e/v2/StandoffRouteV2E2ESpec.scala @@ -0,0 +1,355 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.e2e.v2 + +import java.nio.file.Paths +import akka.http.javadsl.model.StatusCodes +import akka.http.scaladsl.model.headers.BasicHttpCredentials +import akka.http.scaladsl.model.{HttpEntity, _} +import akka.http.scaladsl.unmarshalling.Unmarshal +import org.knora.webapi.messages.store.sipimessages._ +import org.knora.webapi.messages.store.sipimessages.SipiUploadResponseJsonProtocol._ +import org.knora.webapi._ +import org.knora.webapi.e2e.v2.ResponseCheckerV2.compareJSONLDForMappingCreationResponse +import org.knora.webapi.messages.OntologyConstants +import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject +import org.knora.webapi.messages.util.rdf.{JsonLDDocument, JsonLDKeywords} +import org.knora.webapi.messages.v2.routing.authenticationmessages.{AuthenticationV2JsonProtocol, LoginResponse} +import org.knora.webapi.sharedtestdata.SharedTestDataADM +import org.knora.webapi.sharedtestdata.SharedTestDataV1.ANYTHING_PROJECT_IRI +import org.knora.webapi.util.{FileUtil, MutableTestIri} +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.models.filemodels.{FileType, UploadFileRequest} +import org.knora.webapi.models.standoffmodels.DefineStandoffMapping +import org.xmlunit.builder.{DiffBuilder, Input} +import org.xmlunit.diff.Diff +import spray.json._ + +import java.net.URLEncoder +import scala.concurrent.Await +import scala.concurrent.duration._ + +/** + * End-to-end test specification for the standoff endpoint. + */ +class StandoffRouteV2E2ESpec extends E2ESpec with AuthenticationV2JsonProtocol { + private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance + + private val anythingUser = SharedTestDataADM.anythingUser1 + private val anythingUserEmail = anythingUser.email + private val password = SharedTestDataADM.testPass + + private val pathToXMLWithStandardMapping = "../test_data/test_route/texts/StandardHTML.xml" + + private val pathToLetterMapping = "../test_data/test_route/texts/mappingForLetter.xml" + + private val pathToFreetestCustomMapping = "../test_data/test_route/texts/freetestCustomMapping.xml" + private val pathToFreetestCustomMappingWithTransformation = + "../test_data/test_route/texts/freetestCustomMappingWithTransformation.xml" + private val pathToFreetestXMLTextValue = "../test_data/test_route/texts/freetestXMLTextValue.xml" + private val freetestXSLTFile = "freetestCustomMappingTransformation.xsl" + private val pathToFreetestXSLTFile = s"../test_data/test_route/texts/$freetestXSLTFile" + private val freetestCustomMappingIRI = "http://rdfh.ch/projects/0001/mappings/FreetestCustomMapping" + private val freetestCustomMappingWithTranformationIRI = + "http://rdfh.ch/projects/0001/mappings/FreetestCustomMappingWithTransformation" + private val freetestOntologyIRI = "http://0.0.0.0:3333/ontology/0001/freetest/v2#" + private val freetestTextValueIRI = new MutableTestIri + private val freetestXSLTIRI = "http://rdfh.ch/0001/xYSnl8dmTw2RM6KQGVqNDA" + + override lazy val rdfDataObjects: List[RdfDataObject] = List( + RdfDataObject(path = "test_data/all_data/incunabula-data.ttl", name = "http://www.knora.org/data/incunabula"), + RdfDataObject(path = "test_data/demo_data/images-demo-data.ttl", name = "http://www.knora.org/data/00FF/images"), + RdfDataObject(path = "test_data/all_data/anything-data.ttl", name = "http://www.knora.org/data/anything"), + RdfDataObject( + path = "test_data/ontologies/freetest-onto.ttl", + name = "http://www.knora.org/ontology/0001/freetest" + ), + RdfDataObject(path = "test_data/all_data/freetest-data.ttl", name = "http://www.knora.org/data/0001/freetest") + ) + + def createMapping(mappingPath: String, mappingName: String): HttpResponse = { + val mappingFile = Paths.get(mappingPath) + val mappingParams = DefineStandoffMapping.make(mappingName = mappingName).toJSONLD() + + val formDataMapping = Multipart.FormData( + Multipart.FormData.BodyPart( + "json", + HttpEntity(ContentTypes.`application/json`, mappingParams) + ), + Multipart.FormData.BodyPart( + "xml", + HttpEntity.fromPath(MediaTypes.`text/xml`.toContentType(HttpCharsets.`UTF-8`), mappingFile) + ) + ) + val mappingRequest = Post(baseApiUrl + "/v2/mapping", formDataMapping) ~> addCredentials( + BasicHttpCredentials(anythingUserEmail, password) + ) + singleAwaitingRequest(mappingRequest) + } + + def createResourceWithTextValue(xmlContent: String, mappingIRI: String): HttpResponse = { + val jsonLDEntity = Map( + "@type" -> "freetest:FreeTest".toJson, + "knora-api:attachedToProject" -> Map( + "@id" -> "http://rdfh.ch/projects/0001".toJson + ).toJson, + "rdfs:label" -> "obj_inst1".toJson, + "freetest:hasText" -> Map( + "@type" -> "knora-api:TextValue".toJson, + "knora-api:textValueAsXml" -> xmlContent.toJson, + "knora-api:textValueHasMapping" -> Map( + "@id" -> mappingIRI.toJson + ).toJson + ).toJson, + "@context" -> Map( + "anything" -> "http://0.0.0.0:3333/ontology/0001/anything/v2#".toJson, + "freetest" -> freetestOntologyIRI.toJson, + "rdf" -> "http://www.w3.org/1999/02/22-rdf-syntax-ns#".toJson, + "rdfs" -> "http://www.w3.org/2000/01/rdf-schema#".toJson, + "knora-api" -> "http://api.knora.org/ontology/knora-api/v2#".toJson + ).toJson + ).toJson.prettyPrint + + val resourceRequest = Post( + s"$baseApiUrl/v2/resources", + HttpEntity(RdfMediaTypes.`application/ld+json`, jsonLDEntity) + ) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password)) + singleAwaitingRequest(resourceRequest) + } + + def getTextValueAsDocument(iri: String): JsonLDDocument = { + val request = Get( + s"$baseApiUrl/v2/resources/$iri" + ) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password)) + val response: HttpResponse = singleAwaitingRequest(request) + responseToJsonLDDocument(response) + } + + "The Standoff v2 Endpoint" should { + "check if SIPI is available" in { + val request = Get(s"${settings.internalSipiBaseUrl}/server/test.html") + val res = singleAwaitingRequest(request) + assert(res.status == StatusCodes.OK) + } + + "create a text value with standard mapping" in { + val xmlContent = FileUtil.readTextFile(Paths.get(pathToXMLWithStandardMapping)) + val response = createResourceWithTextValue( + xmlContent = xmlContent, + mappingIRI = OntologyConstants.KnoraBase.StandardMapping + ) + assert(response.status == StatusCodes.OK, responseToString(response)) + val resourceResponseDocument: JsonLDDocument = responseToJsonLDDocument(response) + freetestTextValueIRI.set( + resourceResponseDocument.body.requireStringWithValidation( + JsonLDKeywords.ID, + stringFormatter.validateAndEscapeIri + ) + ) + } + + "return XML but no HTML for a resource with standard mapping" in { + val valueIRI = URLEncoder.encode(freetestTextValueIRI.get, "UTF-8") + val xmlContent = FileUtil.readTextFile(Paths.get(pathToXMLWithStandardMapping)) + + val responseDocument = getTextValueAsDocument(valueIRI) + val textValueObject = responseDocument.body.requireObject(s"${freetestOntologyIRI}hasText") + textValueObject.requireString(JsonLDKeywords.TYPE) should equal(OntologyConstants.KnoraApiV2Complex.TextValue) + textValueObject + .requireObject(OntologyConstants.KnoraApiV2Complex.TextValueHasMapping) + .requireString(JsonLDKeywords.ID) should equal(OntologyConstants.KnoraBase.StandardMapping) + val retrievedXML = textValueObject.requireString(OntologyConstants.KnoraApiV2Complex.TextValueAsXml) + val xmlDiff: Diff = DiffBuilder + .compare(Input.fromString(xmlContent)) + .withTest(Input.fromString(retrievedXML)) + .build() + xmlDiff.hasDifferences should be(false) + textValueObject.maybeString(OntologyConstants.KnoraApiV2Complex.TextValueAsHtml) should equal(None) + textValueObject.maybeString(OntologyConstants.KnoraApiV2Complex.ValueAsString) should equal(None) + } + + "create a mapping from a XML" in { + + val xmlFileToSend = Paths.get(pathToLetterMapping) + + val mappingParams = + s""" + |{ + | "knora-api:mappingHasName": "LetterMapping", + | "knora-api:attachedToProject": { + | "@id": "$ANYTHING_PROJECT_IRI" + | }, + | "rdfs:label": "letter mapping", + | "@context": { + | "rdfs": "${OntologyConstants.Rdfs.RdfsPrefixExpansion}", + | "knora-api": "${OntologyConstants.KnoraApiV2Complex.KnoraApiV2PrefixExpansion}" + | } + |} + """.stripMargin + + val formDataMapping = Multipart.FormData( + Multipart.FormData.BodyPart( + "json", + HttpEntity(ContentTypes.`application/json`, mappingParams) + ), + Multipart.FormData.BodyPart( + "xml", + HttpEntity.fromPath(MediaTypes.`text/xml`.toContentType(HttpCharsets.`UTF-8`), xmlFileToSend), + Map("filename" -> "brokenMapping.xml") + ) + ) + + // create standoff from XML + val request = Post(baseApiUrl + "/v2/mapping", formDataMapping) ~> addCredentials( + BasicHttpCredentials(anythingUserEmail, password) + ) + val response = singleAwaitingRequest(request) + val status = response.status + val text = responseToString(response) + + assert( + status == StatusCodes.OK, + s"creation of a mapping returned a non successful HTTP status code: $text" + ) + + val expectedAnswerJSONLD = + FileUtil.readTextFile(Paths.get("../test_data/standoffR2RV2/mappingCreationResponse.jsonld")) + + compareJSONLDForMappingCreationResponse( + expectedJSONLD = expectedAnswerJSONLD, + receivedJSONLD = text + ) + } + + "create a custom mapping for XML in freetest" in { + // define custom XML to standoff mapping + val mappingResponse = createMapping(pathToFreetestCustomMapping, "FreetestCustomMapping") + val mappingResponseDocument = responseToJsonLDDocument(mappingResponse) + mappingResponse.status should equal(StatusCodes.OK) + val mappingIRI = mappingResponseDocument.body.requireString("@id") + mappingIRI should equal(freetestCustomMappingIRI) + } + + "create a text value with the freetext custom mapping" in { + // create a resource with a TextValue with custom mapping + val xmlContent = FileUtil.readTextFile(Paths.get(pathToFreetestXMLTextValue)) + val response = createResourceWithTextValue( + xmlContent = xmlContent, + mappingIRI = freetestCustomMappingIRI + ) + assert(response.status == StatusCodes.OK, responseToString(response)) + val resourceResponseDocument: JsonLDDocument = responseToJsonLDDocument(response) + freetestTextValueIRI.set( + resourceResponseDocument.body.requireStringWithValidation( + JsonLDKeywords.ID, + stringFormatter.validateAndEscapeIri + ) + ) + } + + "return XML but no HTML, as there is no transformation provided" in { + val valueIRI = URLEncoder.encode(freetestTextValueIRI.get, "UTF-8") + val xmlContent = FileUtil.readTextFile(Paths.get(pathToFreetestXMLTextValue)) + + val responseDocument = getTextValueAsDocument(valueIRI) + val textValueObject = responseDocument.body.requireObject(s"${freetestOntologyIRI}hasText") + textValueObject.requireString(JsonLDKeywords.TYPE) should equal(OntologyConstants.KnoraApiV2Complex.TextValue) + textValueObject + .requireObject(OntologyConstants.KnoraApiV2Complex.TextValueHasMapping) + .requireString(JsonLDKeywords.ID) should equal(freetestCustomMappingIRI) + textValueObject.requireString(OntologyConstants.KnoraApiV2Complex.TextValueAsXml) should equal(xmlContent) + textValueObject.maybeString(OntologyConstants.KnoraApiV2Complex.TextValueAsHtml) should equal(None) + textValueObject.maybeString(OntologyConstants.KnoraApiV2Complex.ValueAsString) should equal(None) + } + + "create a custom mapping with an XSL transformation" in { + // get authentication token + val params = Map( + "email" -> "root@example.com", + "password" -> "test" + ).toJson.compactPrint + val loginRequest = Post(baseApiUrl + s"/v2/authentication", HttpEntity(ContentTypes.`application/json`, params)) + val loginResponse: HttpResponse = singleAwaitingRequest(loginRequest) + assert(loginResponse.status == StatusCodes.OK, responseToString(loginResponse)) + val loginToken = Await.result(Unmarshal(loginResponse.entity).to[LoginResponse], 1.seconds).token + + // upload XSLT file to SIPI + val sipiFormData = Multipart.FormData( + Multipart.FormData.BodyPart( + "file", + HttpEntity.fromPath(ContentTypes.`text/xml(UTF-8)`, Paths.get(pathToFreetestXSLTFile)), + Map("filename" -> freetestXSLTFile) + ) + ) + val sipiRequest = Post(s"${settings.internalSipiBaseUrl}/upload?token=$loginToken", sipiFormData) + val sipiResponse = singleAwaitingRequest(sipiRequest) + val uploadedFile = responseToString(sipiResponse).parseJson.asJsObject + .convertTo[SipiUploadResponse] + .uploadedFiles + .head + + // create FileRepresentation in API + val uploadFileJson = UploadFileRequest + .make( + fileType = FileType.TextFile, + internalFilename = uploadedFile.internalFilename, + resourceIRI = Some(freetestXSLTIRI) + ) + .toJsonLd( + className = Some("XSLTransformation") + ) + val fileRepresentationRequest = Post( + s"$baseApiUrl/v2/resources", + HttpEntity(RdfMediaTypes.`application/ld+json`, uploadFileJson) + ) ~> addCredentials(BasicHttpCredentials(anythingUserEmail, password)) + val fileRepresentationResponse = singleAwaitingRequest(fileRepresentationRequest) + assert(StatusCodes.OK == fileRepresentationResponse.status, responseToString(fileRepresentationResponse)) + val responseJsonDoc: JsonLDDocument = responseToJsonLDDocument(fileRepresentationResponse) + responseJsonDoc.body.requireString(JsonLDKeywords.ID) should equal(freetestXSLTIRI) + + // add a mapping that refers to the transformation + val mappingResponse = + createMapping(pathToFreetestCustomMappingWithTransformation, "FreetestCustomMappingWithTransformation") + val mappingResponseDocument = responseToJsonLDDocument(mappingResponse) + mappingResponse.status should equal(StatusCodes.OK) + val mappingIRI = mappingResponseDocument.body.requireString("@id") + mappingIRI should equal(freetestCustomMappingWithTranformationIRI) + } + + "create a text value with the freetext custom mapping and transformation" in { + // create a resource with a TextValue with custom mapping + val xmlContent = FileUtil.readTextFile(Paths.get(pathToFreetestXMLTextValue)) + val response = createResourceWithTextValue( + xmlContent = xmlContent, + mappingIRI = freetestCustomMappingWithTranformationIRI + ) + assert(response.status == StatusCodes.OK, responseToString(response)) + val resourceResponseDocument: JsonLDDocument = responseToJsonLDDocument(response) + freetestTextValueIRI.set( + resourceResponseDocument.body.requireStringWithValidation( + JsonLDKeywords.ID, + stringFormatter.validateAndEscapeIri + ) + ) + } + + "return XML and HTML rendering of the standoff" in { + val valueIRI = URLEncoder.encode(freetestTextValueIRI.get, "UTF-8") + val xmlContent = FileUtil.readTextFile(Paths.get(pathToFreetestXMLTextValue)) + val expectedHTML = Some("
\n

This is a sample of standoff text.

\n
") + + val responseDocument = getTextValueAsDocument(valueIRI) + val textValueObject = responseDocument.body.requireObject(s"${freetestOntologyIRI}hasText") + textValueObject.requireString(JsonLDKeywords.TYPE) should equal(OntologyConstants.KnoraApiV2Complex.TextValue) + textValueObject + .requireObject(OntologyConstants.KnoraApiV2Complex.TextValueHasMapping) + .requireString(JsonLDKeywords.ID) should equal(freetestCustomMappingWithTranformationIRI) + textValueObject.requireString(OntologyConstants.KnoraApiV2Complex.TextValueAsXml) should equal(xmlContent) + textValueObject.maybeString(OntologyConstants.KnoraApiV2Complex.TextValueAsHtml) should equal(expectedHTML) + textValueObject.maybeString(OntologyConstants.KnoraApiV2Complex.ValueAsString) should equal(None) + } + } +} diff --git a/webapi/src/test/scala/org/knora/webapi/e2e/v2/StandoffRouteV2R2RSpec.scala b/webapi/src/test/scala/org/knora/webapi/e2e/v2/StandoffRouteV2R2RSpec.scala deleted file mode 100644 index 1c84141bf6..0000000000 --- a/webapi/src/test/scala/org/knora/webapi/e2e/v2/StandoffRouteV2R2RSpec.scala +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.knora.webapi.e2e.v2 - -import java.nio.file.Paths - -import akka.actor.ActorSystem -import akka.http.javadsl.model.StatusCodes -import akka.http.scaladsl.model.headers.BasicHttpCredentials -import akka.http.scaladsl.model.{HttpEntity, _} -import akka.http.scaladsl.testkit.RouteTestTimeout -import org.knora.webapi._ -import org.knora.webapi.e2e.v2.ResponseCheckerV2.compareJSONLDForMappingCreationResponse -import org.knora.webapi.messages.OntologyConstants -import org.knora.webapi.messages.store.triplestoremessages.RdfDataObject -import org.knora.webapi.routing.v2.StandoffRouteV2 -import org.knora.webapi.sharedtestdata.SharedTestDataADM -import org.knora.webapi.sharedtestdata.SharedTestDataV1.ANYTHING_PROJECT_IRI -import org.knora.webapi.util.FileUtil - -import scala.concurrent.ExecutionContextExecutor - -/** - * End-to-end test specification for the search endpoint. This specification uses the Spray Testkit as documented - * here: http://spray.io/documentation/1.2.2/spray-testkit/ - */ -class StandoffRouteV2R2RSpec extends R2RSpec { - override def testConfigSource: String = - """ - |# akka.loglevel = "DEBUG" - |# akka.stdout-loglevel = "DEBUG" - """.stripMargin - - private val standoffPath = new StandoffRouteV2(routeData).knoraApiPath - - implicit def default(implicit system: ActorSystem): RouteTestTimeout = RouteTestTimeout(settings.defaultTimeout) - - implicit val ec: ExecutionContextExecutor = system.dispatcher - - private val anythingUser = SharedTestDataADM.anythingUser1 - private val anythingUserEmail = anythingUser.email - - private val password = SharedTestDataADM.testPass - - object RequestParams { - val pathToLetterMapping = "test_data/test_route/texts/mappingForLetter.xml" - - val pathToLetterXML = "test_data/test_route/texts/letter.xml" - - val pathToLetter2XML = "test_data/test_route/texts/letter2.xml" - - val pathToLetter3XML = "test_data/test_route/texts/letter3.xml" - - // Standard HTML is the html code that can be translated into Standoff markup with the OntologyConstants.KnoraBase.StandardMapping - val pathToStandardHTML = "test_data/test_route/texts/StandardHTML.xml" - - val pathToHTMLMapping = "test_data/test_route/texts/mappingForHTML.xml" - - val pathToHTML = "test_data/test_route/texts/HTML.xml" - - } - - override lazy val rdfDataObjects: List[RdfDataObject] = List( - RdfDataObject(path = "test_data/all_data/incunabula-data.ttl", name = "http://www.knora.org/data/incunabula"), - RdfDataObject(path = "test_data/demo_data/images-demo-data.ttl", name = "http://www.knora.org/data/00FF/images"), - RdfDataObject(path = "test_data/all_data/anything-data.ttl", name = "http://www.knora.org/data/anything") - ) - - "The Standoff v2 Endpoint" should { - "create a mapping from a XML" in { - val xmlFileToSend = Paths.get("..", RequestParams.pathToLetterMapping) - - val mappingParams = - s""" - |{ - | "knora-api:mappingHasName": "LetterMapping", - | "knora-api:attachedToProject": { - | "@id": "$ANYTHING_PROJECT_IRI" - | }, - | "rdfs:label": "letter mapping", - | "@context": { - | "rdfs": "${OntologyConstants.Rdfs.RdfsPrefixExpansion}", - | "knora-api": "${OntologyConstants.KnoraApiV2Complex.KnoraApiV2PrefixExpansion}" - | } - |} - """.stripMargin - - val formDataMapping = Multipart.FormData( - Multipart.FormData.BodyPart( - "json", - HttpEntity(ContentTypes.`application/json`, mappingParams) - ), - Multipart.FormData.BodyPart( - "xml", - HttpEntity.fromPath(MediaTypes.`text/xml`.toContentType(HttpCharsets.`UTF-8`), xmlFileToSend), - Map("filename" -> "brokenMapping.xml") - ) - ) - - // create standoff from XML - Post("/v2/mapping", formDataMapping) ~> addCredentials( - BasicHttpCredentials(anythingUserEmail, password) - ) ~> standoffPath ~> check { - - assert( - status == StatusCodes.OK, - "creation of a mapping returned a non successful HTTP status code: " + responseAs[String] - ) - - val expectedAnswerJSONLD = - FileUtil.readTextFile(Paths.get("..", "test_data/standoffR2RV2/mappingCreationResponse.jsonld")) - - compareJSONLDForMappingCreationResponse( - expectedJSONLD = expectedAnswerJSONLD, - receivedJSONLD = responseAs[String] - ) - } - } - } -} diff --git a/webapi/src/test/scala/org/knora/webapi/it/v1/DrawingsGodsV1ITSpec.scala b/webapi/src/test/scala/org/knora/webapi/it/v1/DrawingsGodsV1ITSpec.scala index 4c7321aeca..27d0371e23 100644 --- a/webapi/src/test/scala/org/knora/webapi/it/v1/DrawingsGodsV1ITSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/it/v1/DrawingsGodsV1ITSpec.scala @@ -15,6 +15,8 @@ import org.knora.webapi.ITKnoraLiveSpec import org.knora.webapi.exceptions.InvalidApiJsonException import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, TriplestoreJsonProtocol} import org.knora.webapi.messages.v2.routing.authenticationmessages.{AuthenticationV2JsonProtocol, LoginResponse} +import org.knora.webapi.messages.store.sipimessages._ +import org.knora.webapi.messages.store.sipimessages.SipiUploadResponseJsonProtocol._ import spray.json._ import scala.concurrent.Await diff --git a/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala b/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala index 0f638c6915..f781691175 100644 --- a/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/it/v1/KnoraSipiIntegrationV1ITSpec.scala @@ -16,6 +16,8 @@ import org.knora.webapi._ import org.knora.webapi.exceptions.{AssertionException, InvalidApiJsonException} import org.knora.webapi.messages.store.triplestoremessages.{RdfDataObject, TriplestoreJsonProtocol} import org.knora.webapi.messages.v2.routing.authenticationmessages.{AuthenticationV2JsonProtocol, LoginResponse} +import org.knora.webapi.messages.store.sipimessages._ +import org.knora.webapi.messages.store.sipimessages.SipiUploadResponseJsonProtocol._ import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.util.{FileUtil, MutableTestIri} import org.xmlunit.builder.{DiffBuilder, Input} diff --git a/webapi/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala b/webapi/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala index 1cb9a9ba1f..191e28a865 100644 --- a/webapi/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/it/v2/KnoraSipiIntegrationV2ITSpec.scala @@ -18,6 +18,8 @@ import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtoc import org.knora.webapi.messages.util.rdf._ import org.knora.webapi.messages.v2.routing.authenticationmessages._ import org.knora.webapi.messages.{OntologyConstants, SmartIri, StringFormatter} +import org.knora.webapi.messages.store.sipimessages._ +import org.knora.webapi.messages.store.sipimessages.SipiUploadResponseJsonProtocol._ import org.knora.webapi.models.filemodels._ import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.util.MutableTestIri @@ -66,7 +68,8 @@ class KnoraSipiIntegrationV2ITSpec private val marblesWidth = 1419 private val marblesHeight = 1001 - private val pathToMarblesWithWrongExtension = Paths.get("..", "test_data/test_route/images/marbles_with_wrong_extension.jpg") + private val pathToMarblesWithWrongExtension = + Paths.get("..", "test_data/test_route/images/marbles_with_wrong_extension.jpg") private val trp88OriginalFilename = "Trp88.tiff" private val pathToTrp88 = Paths.get("..", s"test_data/test_route/images/$trp88OriginalFilename") diff --git a/webapi/src/test/scala/org/knora/webapi/models/filemodels/FileModels.scala b/webapi/src/test/scala/org/knora/webapi/models/filemodels/FileModels.scala index 07921b8062..1ae51cf1ec 100644 --- a/webapi/src/test/scala/org/knora/webapi/models/filemodels/FileModels.scala +++ b/webapi/src/test/scala/org/knora/webapi/models/filemodels/FileModels.scala @@ -20,7 +20,8 @@ import java.util.UUID sealed abstract case class UploadFileRequest private ( fileType: FileType, internalFilename: String, - label: String + label: String, + resourceIRI: Option[String] = None ) { /** @@ -45,9 +46,13 @@ sealed abstract case class UploadFileRequest private ( case Some(v) => v case None => FileModelUtil.getDefaultClassName(fileType) } + val resorceIRIOrEmptyString = resourceIRI match { + case Some(v) => s""" "@id": "$v",\n """ + case None => "" + } s"""{ - | "@type" : "$ontologyName:$classNameWithDefaults", + | $resorceIRIOrEmptyString"@type" : "$ontologyName:$classNameWithDefaults", | "$fileValuePropertyName" : { | "@type" : "$fileValueType", | "knora-api:fileValueHasFilename" : "$internalFilename" @@ -175,12 +180,14 @@ object UploadFileRequest { def make( fileType: FileType, internalFilename: String, - label: String = "test label" + label: String = "test label", + resourceIRI: Option[String] = None ): UploadFileRequest = new UploadFileRequest( fileType = fileType, internalFilename = internalFilename, - label = label + label = label, + resourceIRI = resourceIRI ) {} } diff --git a/webapi/src/test/scala/org/knora/webapi/models/standoffmodels/StandoffModels.scala b/webapi/src/test/scala/org/knora/webapi/models/standoffmodels/StandoffModels.scala new file mode 100644 index 0000000000..dd6dd76984 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/models/standoffmodels/StandoffModels.scala @@ -0,0 +1,116 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.models.standoffmodels + +import org.knora.webapi.feature.FeatureFactoryConfig +import org.knora.webapi.messages.{OntologyConstants, StringFormatter} +import org.knora.webapi.messages.util.rdf.JsonLDKeywords +import org.knora.webapi.messages.v2.responder.standoffmessages.{ + CreateMappingRequestMetadataV2, + CreateMappingRequestV2, + CreateMappingRequestXMLV2 +} +import org.knora.webapi.sharedtestdata.SharedTestDataV1.ANYTHING_PROJECT_IRI +import spray.json._ +import spray.json.DefaultJsonProtocol._ +import org.knora.webapi.messages.IriConversions._ +import org.knora.webapi.messages.admin.responder.usersmessages.UserADM + +import java.util.UUID + +sealed abstract case class DefineStandoffMapping private ( + mappingName: String, + projectIRI: String, + label: String +) { + private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance + + /** + * Create a JSON-LD serialization of the request. This can be used for e2e tests. + * + * @return JSON-LD serialization of the request that can be processed by the API V2. + */ + def toJSONLD(): String = + Map( + "knora-api:mappingHasName" -> mappingName.toJson, + "knora-api:attachedToProject" -> Map( + JsonLDKeywords.ID -> projectIRI + ).toJson, + "rdfs:label" -> label.toJson, + JsonLDKeywords.CONTEXT -> Map( + "rdfs" -> OntologyConstants.Rdfs.RdfsPrefixExpansion, + "knora-api" -> OntologyConstants.KnoraApiV2Complex.KnoraApiV2PrefixExpansion + ).toJson + ).toJson.prettyPrint + + /** + * Create a [[CreateMappingRequestV2]] message representation of the request. This can be used in unit tests. + * + * @param xml the mapping XML. + * @param featureFactoryConfig the [[FeatureFactoryConfig]]. + * @param user the user issuing the request. + * @return a [[CreateMappingRequestV2]] message representation of the request that can be processed by an Akka actor. + */ + def toMessage( + xml: String, + featureFactoryConfig: FeatureFactoryConfig, + user: UserADM + ): CreateMappingRequestV2 = { + val mappingMetadata = CreateMappingRequestMetadataV2( + label = label, + projectIri = projectIRI.toSmartIri, + mappingName = mappingName + ) + CreateMappingRequestV2( + metadata = mappingMetadata, + xml = CreateMappingRequestXMLV2(xml), + featureFactoryConfig = featureFactoryConfig, + requestingUser = user, + apiRequestID = UUID.randomUUID() + ) + } + +} + +/** + * Helper object for creating a custom standoff mapping. + * + * Can be instantiated by calling `DefineStandoffMapping.make()`. + * + * To generate a JSON-LD request, call `.toJsonLd`. + * + * To generate a [[CreateMappingRequestV2]] message, call `.toMessage` + */ +object DefineStandoffMapping { + + /** + * Smart constructor for instantiating a [[DefineStandoffMapping]] request. + * + * @param mappingName the name of the mapping. + * @param projectIRI the IRI of the project to which the mapping gets attached. Optional. + * If not provided, the "anything" project will be used. + * @param label the rdfs:label of the mapping. Optional. + * @return a [[DefineStandoffMapping]] object. + */ + def make( + mappingName: String, + projectIRI: Option[String] = None, + label: Option[String] = None + ): DefineStandoffMapping = + new DefineStandoffMapping( + mappingName = mappingName, + projectIRI = projectIRI match { + case Some(iri) => iri + case None => ANYTHING_PROJECT_IRI + }, + label = label match { + case Some(v) => v + case None => "custom mapping" + } + ) {} +} + +object XXX {} diff --git a/webapi/src/test/scala/org/knora/webapi/models/standoffmodels/StandoffModelsSpec.scala b/webapi/src/test/scala/org/knora/webapi/models/standoffmodels/StandoffModelsSpec.scala new file mode 100644 index 0000000000..5fc5c49dda --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/models/standoffmodels/StandoffModelsSpec.scala @@ -0,0 +1,99 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.models.standoffmodels + +import org.knora.webapi.CoreSpec +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.sharedtestdata.SharedTestDataV1.{ANYTHING_PROJECT_IRI, INCUNABULA_PROJECT_IRI} +import spray.json._ +import spray.json.DefaultJsonProtocol._ + +class StandoffModelsSpec extends CoreSpec { + implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance + + "StandoffModels," when { + + "defining a custom standoff mapping," should { + + "create a valid representation of the mapping with default values" in { + val mappingName = "customMapping" + val mapping = DefineStandoffMapping.make(mappingName) + + mapping.mappingName should equal(mappingName) + mapping.projectIRI should equal(ANYTHING_PROJECT_IRI) + mapping.label should equal("custom mapping") + } + + "create a valid representation of the mapping with custom values" in { + val mappingName = "customMapping" + val projectIRI = INCUNABULA_PROJECT_IRI + val customLabel = "this is a custom mapping with a custom label" + val mapping = DefineStandoffMapping.make( + mappingName = mappingName, + projectIRI = Some(projectIRI), + label = Some(customLabel) + ) + + mapping.mappingName should equal(mappingName) + mapping.projectIRI should equal(projectIRI) + mapping.label should equal(customLabel) + } + } + + "serializing to JSON-LD," should { + "create a valid serialization of a standoff mapping request with default values" in { + val mappingName = "customMapping" + val mapping = DefineStandoffMapping.make(mappingName) + val json = mapping.toJSONLD().parseJson + + val expectedJSON = + s""" + |{ + | "knora-api:mappingHasName": "$mappingName", + | "rdfs:label": "custom mapping", + | "knora-api:attachedToProject": { + | "@id": "http://rdfh.ch/projects/0001" + | }, + | "@context": { + | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + | "knora-api": "http://api.knora.org/ontology/knora-api/v2#" + | } + |} + |""".stripMargin.parseJson + + json should equal(expectedJSON) + } + "create a valid serialization of a standoff mapping request with custom values" in { + val mappingName = "customMapping" + val projectIRI = INCUNABULA_PROJECT_IRI + val customLabel = "this is a custom mapping with a custom label" + val mapping = DefineStandoffMapping.make( + mappingName = mappingName, + projectIRI = Some(projectIRI), + label = Some(customLabel) + ) + val json = mapping.toJSONLD().parseJson + + val expectedJSON = + s""" + |{ + | "knora-api:mappingHasName": "$mappingName", + | "rdfs:label": "$customLabel", + | "knora-api:attachedToProject": { + | "@id": "$projectIRI" + | }, + | "@context": { + | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + | "knora-api": "http://api.knora.org/ontology/knora-api/v2#" + | } + |} + |""".stripMargin.parseJson + + json should equal(expectedJSON) + } + } + } +} diff --git a/webapi/src/test/scala/org/knora/webapi/responders/v2/StandoffResponderV2Spec.scala b/webapi/src/test/scala/org/knora/webapi/responders/v2/StandoffResponderV2Spec.scala new file mode 100644 index 0000000000..1a9e3f4914 --- /dev/null +++ b/webapi/src/test/scala/org/knora/webapi/responders/v2/StandoffResponderV2Spec.scala @@ -0,0 +1,122 @@ +/* + * Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.knora.webapi.responders.v2 + +import akka.pattern.ask +import akka.testkit.ImplicitSender +import akka.util.Timeout +import org.knora.webapi._ +import org.knora.webapi.exceptions._ +import org.knora.webapi.messages.store.triplestoremessages._ +import org.knora.webapi.messages.v2.responder.standoffmessages._ +import org.knora.webapi.messages.StringFormatter +import org.knora.webapi.models.standoffmodels.DefineStandoffMapping +import org.knora.webapi.sharedtestdata.SharedTestDataADM + +import scala.concurrent.{Await, Future} +import scala.concurrent.duration._ + +/** + * Tests [[StandoffResponderV2]]. + */ +class StandoffResponderV2Spec extends CoreSpec() with ImplicitSender { + private implicit val stringFormatter: StringFormatter = StringFormatter.getGeneralInstance + + // The default timeout for receiving reply messages from actors. + private val timeout = 30.seconds + + private def getMapping(iri: String): SparqlConstructResponse = { + + val getMappingSparql = org.knora.webapi.messages.twirl.queries.sparql.v2.txt + .getMapping( + triplestore = settings.triplestoreType, + mappingIri = iri + ) + .toString() + + implicit val timeout: Timeout = Duration(10, SECONDS) + val resF: Future[SparqlConstructResponse] = (storeManager ? SparqlConstructRequest( + sparql = getMappingSparql, + featureFactoryConfig = defaultFeatureFactoryConfig + )).mapTo[SparqlConstructResponse] + Await.result(resF, 10.seconds) + } + + "The standoff responder" should { + "create a standoff mapping" in { + val mappingName = "customMapping" + val mapping = DefineStandoffMapping.make(mappingName) + val xmlContent = + s""" + | + | + | + | + | text + | noClass + | noNamespace + | false + | + | + | http://www.knora.org/ontology/standoff#StandoffRootTag + | + | + | documentType + | noNamespace + | http://www.knora.org/ontology/standoff#standoffRootTagHasDocumentType + | + | + | + | + | + | + | + | section + | noClass + | noNamespace + | false + | + | + | http://www.knora.org/ontology/standoff#StandoffParagraphTag + | + | + | + | + | + | italic + | noClass + | noNamespace + | false + | + | + | http://www.knora.org/ontology/standoff#StandoffItalicTag + | + | + | + | + |""".stripMargin + val message = mapping.toMessage( + xml = xmlContent, + featureFactoryConfig = defaultFeatureFactoryConfig, + user = SharedTestDataADM.rootUser + ) + responderManager ! message + val response = expectMsgPF(timeout) { + case res: CreateMappingResponseV2 => res + case _ => throw AssertionException("Could not create a mapping") + } + + val expectedMappingIRI = f"${mapping.projectIRI}/mappings/$mappingName" + response.mappingIri should equal(expectedMappingIRI) + val mappingFromDB: SparqlConstructResponse = getMapping(response.mappingIri) + println(mappingFromDB) + mappingFromDB.statements should not be Map.empty + mappingFromDB.statements.get(expectedMappingIRI) should not be Map.empty + } + + } +}