#002: SHACL-driven entity editor with Wikidata-style forms #2

Open
opened 2026-04-05 12:58:39 +00:00 by daniel · 1 comment
Owner

Blocked by

Summary

Implement a form-based RDF entity editor driven by SHACL shapes. Users select a shape (entity type), and the form is generated from the shape's property constraints. The interaction model follows Wikidata's entity editor: mandatory properties appear by default, optional properties can be added via a "+" button, and properties can have qualifiers (nested property-value pairs).

Motivation

Currently, data in Concon datasets can only be edited via raw SPARQL Update. A form-based editor would allow non-technical users to create and edit entities without writing SPARQL, guided by the constraints defined in SHACL shapes.

Design

Entity model

  • Each entity is identified by an IRI and typed as concon:Entity.
  • Entities are stored as RDF triples in regular Concon datasets.
  • Properties are RDF predicates; values are objects (literals or IRIs).

Stated vs non-stated properties

A property-value pair (s, p, o) is either:

  • Stated: the triple (s, p, o) is present in the graph.
  • Non-stated: the triple is not in the graph.

The form shows both possibilities. The user toggles whether a property value is stated or not. Non-stated values are kept only in the UI state (or in a separate draft/staging mechanism) but are never asserted in the RDF graph.

SHACL shapes

  • Multiple SHACL files can be defined per dataset.
  • Each SHACL file is associated with either the default graph or a named graph in the dataset.
  • At most one SHACL file per graph (no overlapping shapes for the same graph).
  • SHACL shapes are stored in the dataset itself or in a dedicated configuration store (TBD).

Form generation from shapes

For each sh:NodeShape:

SHACL constraint Form behavior
sh:property A field in the form
sh:minCount > 1= Mandatory (shown by default)
sh:minCount = 0 Optional (add via "+" button)
sh:maxCount Limits number of values
sh:datatype Input type: text, number, date, URL, etc.
sh:class Autocomplete/dropdown for linked entities
sh:in Dropdown with allowed values
sh:name Label for the form field
sh:description Help text / placeholder
sh:order Field ordering in the form
sh:group Fieldset grouping
sh:pattern Client-side validation

Qualifiers

Properties can have qualifiers — additional context for a statement (e.g., "population: 1,000,000" qualified by "as of: 2024").

Reification approach (to be decided)

Two options under consideration:

  1. RDF 1.2 reification (rdf:reifies): supported in Oxigraph 0.5.x behind the rdf-12 feature flag. Cleaner semantics, follows the evolving W3C standard. Requires upgrading from Oxigraph 0.4.x to 0.5.x.

  2. Statement node pattern (Wikidata model): uses intermediate nodes. Works with any RDF store, no special features needed. More verbose but well-proven.

Decision deferred — depends on Oxigraph upgrade feasibility and RDF 1.2 spec stability.

Form rendering

Client-side JavaScript rendering (Foundation 6 + vanilla JS or a small library). The SHACL shape is serialized as JSON and sent to the browser. The form is generated dynamically.

Interaction model (inspired by Wikidata):

  • Mandatory fields appear immediately when creating an entity.
  • Optional fields are listed in a collapsible section; clicking "+" adds the field to the form.
  • Each property value can be toggled as stated/non-stated.
  • Qualifier fields appear as sub-forms under a property value.
  • Save submits a SPARQL Update constructed from the form state.

Technical approach

SHACL parsing: Rudof library

Use the Rudof Rust library (by Jose Emilio Labra) for SHACL parsing.

Key crates:

  • shacl_ast — AST types: ShaclSchema, NodeShape, PropertyShape, Component
  • shacl_rdf — parse SHACL from Turtle/RDF
  • rudof_lib — high-level API

The ShaclSchema provides an iterator over shapes. Each NodeShape has property_shapes (references to PropertyShape objects) and each PropertyShape has a path (the RDF predicate) and components (constraints like MinCount, Datatype, Class, In, etc.).

RDF storage

Entities stored as triples in regular Concon datasets (Oxigraph stores). SPARQL Update used for create/update/delete operations.

Oxigraph upgrade consideration

Concon currently uses Oxigraph 0.4.x. Upgrading to 0.5.x would enable RDF 1.2 support (rdf:reifies) but involves substantial API changes.

Pros of upgrading to 0.5.x

  • RDF 1.2 support via rdf-12 feature flag
  • Faster updates and bulk loading
  • Reduced storage footprint
  • Automatic database migration from 0.4.x
  • Active maintenance (0.4.x only gets critical fixes)

Cons of upgrading to 0.5.x

  • Every Store.query() and Store.update() call must be rewritten (new SparqlEvaluator fluent API replaces QueryOptions)
  • One-way database migration (cannot revert to 0.4.x)
  • RDF 1.2 spec is still a W3C working draft (syntax may change)
  • Requires Rust 1.87+ and C++20 compiler (Debian 13 has both)
  • RDF-star << s p o >> syntax replaced by RDF 1.2 <<( s p o )>>

Decision

Pending. The upgrade is recommended but should be a separate issue given the scope of API changes.

SHACL file storage

Each dataset has an associated SHACL store (a second Oxigraph dataset). In this SHACL store:

  • Each named graph contains one SHACL file (shapes graph).
  • The default graph stores the mapping between dataset graphs and SHACL files: which shapes graph applies to which graph in the dataset.

Constraint: at most one SHACL file (shapes graph) per dataset graph.

Example mapping in the default graph of the SHACL store:

@prefix concon: <https://concon.2mia.org/ontology#> .

<urn:shacl:molecules> concon:shapesFor <urn:graph:molecules> ;
    concon:shapesGraph <urn:shacl:molecules> .

Stated and non-stated triples

Uses RDF-star (or RDF 1.2 quoted triples) to annotate triple status:

  • Non-stated: the triple (s, p, o) is NOT in the graph. Only the annotation exists:

    << s p o >> a concon:NonStatedTriple .
    
  • Stated: the triple (s, p, o) IS in the graph, plus an annotation:

    s p o .
    << s p o >> a concon:StatedTriple .
    

The concon:StatedTriple annotation is redundant (the triple is already asserted) but enables efficient queries to find all explicitly stated triples via the annotation pattern.

Entity IRI generation

Default: user-defined IRIs.

Extension: shapes can be associated with IRI generation functions via a SHACL extension property:

ex:PersonShape a sh:NodeShape ;
    concon:iriGenerator concon:UUIDGenerator .

Built-in generators:

  • concon:UUIDGenerator — generates <base-iri>/{uuid}
  • concon:SlugGenerator — generates <base-iri>/{slug} from a designated property (e.g., rdfs:label)
  • Custom generators can be defined (TBD).

Form rendering approach

Hybrid server-side + AJAX:

  • Form structure rendered server-side from SHACL shapes (minijinja templates), consistent with the rest of Concon.
  • Individual property saves use AJAX calls to the dataset's SPARQL Update endpoint (/_ds/{dataset}/_update). Each save constructs a small SPARQL INSERT/DELETE — no page reload.
  • Interactive features (adding optional properties, toggling stated/non-stated) handled by JavaScript.

Entity search for object properties

When a property has sh:class (range is another entity type), the value input becomes an autocomplete field.

  • Search endpoint: /_api/search?q=term&dataset=name&class=IRI
  • Queries the dataset for entities matching the search term (by rdfs:label, dcterms:title, or IRI substring).
  • Results scoped to the current dataset only.
  • Autocomplete dropdown shows entity label and IRI.

Cross-dataset entity references are deferred to a future issue (#003 Federated entity search).

SHACL store: per-dataset

Each dataset has its own SHACL store (a separate Oxigraph store). This provides clean isolation — shapes for one dataset don't interfere with another.

Storage: data/shacl/{dataset-name}/ alongside data/datasets/{dataset-name}/.

Access: via a SPARQL endpoint at /{team}/_ds/{dataset}/_shacl/_query (GET/POST) and /{team}/_ds/{dataset}/_shacl/_update (POST). Users read and edit SHACL shapes through SPARQL. A dedicated SHACL editor UI is deferred to a future issue (#004 SHACL editor).

Tests

Unit tests

#[test]
fn test_parse_shacl_node_shape() {
    // Load a simple SHACL Turtle file with one NodeShape
    // Verify: shape name, number of property shapes
    let ttl = r#"
        @prefix sh: <http://www.w3.org/ns/shacl#> .
        @prefix ex: <http://example.org/> .
        ex:PersonShape a sh:NodeShape ;
            sh:targetClass ex:Person ;
            sh:property ex:nameShape .
        ex:nameShape a sh:PropertyShape ;
            sh:path ex:name ;
            sh:minCount 1 ;
            sh:datatype xsd:string .
    "#;
    // Parse with Rudof, assert 1 NodeShape with 1 mandatory property
}

#[test]
fn test_form_fields_from_shape() {
    // Given a parsed NodeShape, generate form field descriptors
    // Mandatory field (minCount >= 1) should have required = true
    // Optional field (minCount 0) should have required = false
    // sh:datatype xsd:integer -> input type "number"
    // sh:class -> input type "entity-autocomplete"
    // sh:in -> input type "select"
}

#[test]
fn test_stated_triple_annotation() {
    // Insert a stated triple with annotation
    // s p o . <<( s p o )>> a concon:StatedTriple .
    // Query: verify both the triple and annotation exist
}

#[test]
fn test_non_stated_triple_annotation() {
    // Insert only the annotation, NOT the triple
    // <<( s p o )>> a concon:NonStatedTriple .
    // Query: verify annotation exists but triple does NOT
}

#[test]
fn test_entity_search() {
    // Create entities with rdfs:label in a dataset
    // Search with partial term
    // Verify matching entities returned, non-matching excluded
}

#[test]
fn test_iri_generator_uuid() {
    // Shape with concon:iriGenerator concon:UUIDGenerator
    // Generate IRI, verify it matches UUID pattern
}

Integration tests

#[tokio::test]
async fn test_shacl_endpoint_query() {
    // Create dataset, load SHACL into its SHACL store
    // Query /_ds/{dataset}/_shacl/_query
    // Verify shapes returned
}

#[tokio::test]
async fn test_entity_form_rendering() {
    // Create dataset with SHACL shape
    // Request entity creation form
    // Verify HTML contains expected form fields
}

#[tokio::test]
async fn test_entity_save_via_ajax() {
    // Create dataset with SHACL shape
    // POST property value to SPARQL Update endpoint
    // Query dataset, verify triple inserted
}

#[tokio::test]
async fn test_entity_search_endpoint() {
    // Create dataset, insert entities with labels
    // GET /_api/search?q=term&dataset=name
    // Verify JSON results contain matching entities
}

Manual tests (on dev.kg.degu.cl)

  1. Load a SHACL file into a dataset's SHACL store via SPARQL
  2. Open the entity editor form — verify fields match the shape
  3. Fill mandatory fields, save — verify triples in dataset
  4. Add an optional property via "+" — verify it appears
  5. Toggle stated/non-stated — verify triple added/removed
  6. Test entity autocomplete for sh:class properties
  7. Add a qualifier to a property value

No further open questions

References

# Blocked by - [\#001 Upgrade Oxigraph to 0.5.x](001-oxigraph-upgrade-0.5.org) (needed for RDF 1.2 quoted triples) # Summary Implement a form-based RDF entity editor driven by SHACL shapes. Users select a shape (entity type), and the form is generated from the shape's property constraints. The interaction model follows Wikidata's entity editor: mandatory properties appear by default, optional properties can be added via a "+" button, and properties can have qualifiers (nested property-value pairs). # Motivation Currently, data in Concon datasets can only be edited via raw SPARQL Update. A form-based editor would allow non-technical users to create and edit entities without writing SPARQL, guided by the constraints defined in SHACL shapes. # Design ## Entity model - Each entity is identified by an IRI and typed as `concon:Entity`. - Entities are stored as RDF triples in regular Concon datasets. - Properties are RDF predicates; values are objects (literals or IRIs). ## Stated vs non-stated properties A property-value pair `(s, p, o)` is either: - **Stated**: the triple `(s, p, o)` is present in the graph. - **Non-stated**: the triple is not in the graph. The form shows both possibilities. The user toggles whether a property value is stated or not. Non-stated values are kept only in the UI state (or in a separate draft/staging mechanism) but are never asserted in the RDF graph. ## SHACL shapes - Multiple SHACL files can be defined per dataset. - Each SHACL file is associated with either the default graph or a named graph in the dataset. - At most one SHACL file per graph (no overlapping shapes for the same graph). - SHACL shapes are stored in the dataset itself or in a dedicated configuration store (TBD). ## Form generation from shapes For each `sh:NodeShape`: | SHACL constraint | Form behavior | |--------------------|-------------------------------------------| | `sh:property` | A field in the form | | `sh:minCount >` 1= | Mandatory (shown by default) | | `sh:minCount = 0` | Optional (add via "+" button) | | `sh:maxCount` | Limits number of values | | `sh:datatype` | Input type: text, number, date, URL, etc. | | `sh:class` | Autocomplete/dropdown for linked entities | | `sh:in` | Dropdown with allowed values | | `sh:name` | Label for the form field | | `sh:description` | Help text / placeholder | | `sh:order` | Field ordering in the form | | `sh:group` | Fieldset grouping | | `sh:pattern` | Client-side validation | ## Qualifiers Properties can have qualifiers — additional context for a statement (e.g., "population: 1,000,000" qualified by "as of: 2024"). ### Reification approach (to be decided) Two options under consideration: 1. **RDF 1.2 reification** (`rdf:reifies`): supported in Oxigraph 0.5.x behind the `rdf-12` feature flag. Cleaner semantics, follows the evolving W3C standard. Requires upgrading from Oxigraph 0.4.x to 0.5.x. 2. **Statement node pattern** (Wikidata model): uses intermediate nodes. Works with any RDF store, no special features needed. More verbose but well-proven. Decision deferred — depends on Oxigraph upgrade feasibility and RDF 1.2 spec stability. ## Form rendering Client-side JavaScript rendering (Foundation 6 + vanilla JS or a small library). The SHACL shape is serialized as JSON and sent to the browser. The form is generated dynamically. Interaction model (inspired by Wikidata): - Mandatory fields appear immediately when creating an entity. - Optional fields are listed in a collapsible section; clicking "+" adds the field to the form. - Each property value can be toggled as stated/non-stated. - Qualifier fields appear as sub-forms under a property value. - Save submits a SPARQL Update constructed from the form state. # Technical approach ## SHACL parsing: Rudof library Use the [Rudof](https://github.com/rudof-project/rudof) Rust library (by Jose Emilio Labra) for SHACL parsing. Key crates: - `shacl_ast` — AST types: `ShaclSchema`, `NodeShape`, `PropertyShape`, `Component` - `shacl_rdf` — parse SHACL from Turtle/RDF - `rudof_lib` — high-level API The `ShaclSchema` provides an iterator over shapes. Each `NodeShape` has `property_shapes` (references to `PropertyShape` objects) and each `PropertyShape` has a `path` (the RDF predicate) and `components` (constraints like `MinCount`, `Datatype`, `Class`, `In`, etc.). ## RDF storage Entities stored as triples in regular Concon datasets (Oxigraph stores). SPARQL Update used for create/update/delete operations. ## Oxigraph upgrade consideration Concon currently uses Oxigraph 0.4.x. Upgrading to 0.5.x would enable RDF 1.2 support (`rdf:reifies`) but involves substantial API changes. ### Pros of upgrading to 0.5.x - RDF 1.2 support via `rdf-12` feature flag - Faster updates and bulk loading - Reduced storage footprint - Automatic database migration from 0.4.x - Active maintenance (0.4.x only gets critical fixes) ### Cons of upgrading to 0.5.x - Every `Store.query()` and `Store.update()` call must be rewritten (new `SparqlEvaluator` fluent API replaces `QueryOptions`) - One-way database migration (cannot revert to 0.4.x) - RDF 1.2 spec is still a W3C working draft (syntax may change) - Requires Rust 1.87+ and C++20 compiler (Debian 13 has both) - RDF-star `<< s p o >>` syntax replaced by RDF 1.2 `<<( s p o )>>` ### Decision Pending. The upgrade is recommended but should be a separate issue given the scope of API changes. ## SHACL file storage Each dataset has an associated SHACL store (a second Oxigraph dataset). In this SHACL store: - Each named graph contains one SHACL file (shapes graph). - The default graph stores the mapping between dataset graphs and SHACL files: which shapes graph applies to which graph in the dataset. Constraint: at most one SHACL file (shapes graph) per dataset graph. Example mapping in the default graph of the SHACL store: ``` turtle @prefix concon: <https://concon.2mia.org/ontology#> . <urn:shacl:molecules> concon:shapesFor <urn:graph:molecules> ; concon:shapesGraph <urn:shacl:molecules> . ``` ## Stated and non-stated triples Uses RDF-star (or RDF 1.2 quoted triples) to annotate triple status: - **Non-stated**: the triple `(s, p, o)` is NOT in the graph. Only the annotation exists: ``` turtle << s p o >> a concon:NonStatedTriple . ``` - **Stated**: the triple `(s, p, o)` IS in the graph, plus an annotation: ``` turtle s p o . << s p o >> a concon:StatedTriple . ``` The `concon:StatedTriple` annotation is redundant (the triple is already asserted) but enables efficient queries to find all explicitly stated triples via the annotation pattern. ## Entity IRI generation Default: user-defined IRIs. Extension: shapes can be associated with IRI generation functions via a SHACL extension property: ``` turtle ex:PersonShape a sh:NodeShape ; concon:iriGenerator concon:UUIDGenerator . ``` Built-in generators: - `concon:UUIDGenerator` — generates `<base-iri>/{uuid}` - `concon:SlugGenerator` — generates `<base-iri>/{slug}` from a designated property (e.g., `rdfs:label`) - Custom generators can be defined (TBD). ## Form rendering approach Hybrid server-side + AJAX: - Form structure rendered server-side from SHACL shapes (minijinja templates), consistent with the rest of Concon. - Individual property saves use AJAX calls to the dataset's SPARQL Update endpoint (`/_ds/{dataset}/_update`). Each save constructs a small SPARQL INSERT/DELETE — no page reload. - Interactive features (adding optional properties, toggling stated/non-stated) handled by JavaScript. ## Entity search for object properties When a property has `sh:class` (range is another entity type), the value input becomes an autocomplete field. - Search endpoint: `/_api/search?q=term&dataset=name&class=IRI` - Queries the dataset for entities matching the search term (by `rdfs:label`, `dcterms:title`, or IRI substring). - Results scoped to the current dataset only. - Autocomplete dropdown shows entity label and IRI. Cross-dataset entity references are deferred to a future issue ([\#003 Federated entity search](003-federated-entity-search.org)). ## SHACL store: per-dataset Each dataset has its own SHACL store (a separate Oxigraph store). This provides clean isolation — shapes for one dataset don't interfere with another. Storage: `data/shacl/{dataset-name}/` alongside `data/datasets/{dataset-name}/`. Access: via a SPARQL endpoint at `/{team}/_ds/{dataset}/_shacl/_query` (GET/POST) and `/{team}/_ds/{dataset}/_shacl/_update` (POST). Users read and edit SHACL shapes through SPARQL. A dedicated SHACL editor UI is deferred to a future issue ([\#004 SHACL editor](004-shacl-editor.org)). # Tests ## Unit tests ``` rust #[test] fn test_parse_shacl_node_shape() { // Load a simple SHACL Turtle file with one NodeShape // Verify: shape name, number of property shapes let ttl = r#" @prefix sh: <http://www.w3.org/ns/shacl#> . @prefix ex: <http://example.org/> . ex:PersonShape a sh:NodeShape ; sh:targetClass ex:Person ; sh:property ex:nameShape . ex:nameShape a sh:PropertyShape ; sh:path ex:name ; sh:minCount 1 ; sh:datatype xsd:string . "#; // Parse with Rudof, assert 1 NodeShape with 1 mandatory property } #[test] fn test_form_fields_from_shape() { // Given a parsed NodeShape, generate form field descriptors // Mandatory field (minCount >= 1) should have required = true // Optional field (minCount 0) should have required = false // sh:datatype xsd:integer -> input type "number" // sh:class -> input type "entity-autocomplete" // sh:in -> input type "select" } #[test] fn test_stated_triple_annotation() { // Insert a stated triple with annotation // s p o . <<( s p o )>> a concon:StatedTriple . // Query: verify both the triple and annotation exist } #[test] fn test_non_stated_triple_annotation() { // Insert only the annotation, NOT the triple // <<( s p o )>> a concon:NonStatedTriple . // Query: verify annotation exists but triple does NOT } #[test] fn test_entity_search() { // Create entities with rdfs:label in a dataset // Search with partial term // Verify matching entities returned, non-matching excluded } #[test] fn test_iri_generator_uuid() { // Shape with concon:iriGenerator concon:UUIDGenerator // Generate IRI, verify it matches UUID pattern } ``` ## Integration tests ``` rust #[tokio::test] async fn test_shacl_endpoint_query() { // Create dataset, load SHACL into its SHACL store // Query /_ds/{dataset}/_shacl/_query // Verify shapes returned } #[tokio::test] async fn test_entity_form_rendering() { // Create dataset with SHACL shape // Request entity creation form // Verify HTML contains expected form fields } #[tokio::test] async fn test_entity_save_via_ajax() { // Create dataset with SHACL shape // POST property value to SPARQL Update endpoint // Query dataset, verify triple inserted } #[tokio::test] async fn test_entity_search_endpoint() { // Create dataset, insert entities with labels // GET /_api/search?q=term&dataset=name // Verify JSON results contain matching entities } ``` ## Manual tests (on dev.kg.degu.cl) 1. Load a SHACL file into a dataset's SHACL store via SPARQL 2. Open the entity editor form — verify fields match the shape 3. Fill mandatory fields, save — verify triples in dataset 4. Add an optional property via "+" — verify it appears 5. Toggle stated/non-stated — verify triple added/removed 6. Test entity autocomplete for sh:class properties 7. Add a qualifier to a property value # No further open questions # References - [Rudof — Rust library for SHACL/ShEx](https://github.com/rudof-project/rudof) - [W3C SHACL specification](https://www.w3.org/TR/shacl/) - [W3C RDF 1.2 Concepts (working draft)](https://www.w3.org/TR/rdf12-concepts/) - [Wikidata data model](https://www.wikidata.org/wiki/Wikidata:Introduction) - [Oxigraph — Rust RDF store](https://github.com/oxigraph/oxigraph)
Author
Owner

Summary

A form-based RDF entity editor driven by SHACL shapes has been implemented. Users select an entity type (SHACL NodeShape), and a form is generated from the shape's property constraints. Mandatory fields appear by default, optional fields can be added via a "+" button, and each property value can be saved individually via AJAX.

Review fixes (second commit)

All findings from the maintainer review were addressed:

Critical: SPARQL injection fixes

  • search_entities(): added is_valid_iri() validation that rejects IRIs containing >, whitespace, braces, <, or control characters. Invalid class parameters return an empty result set.
  • saveField() JS: added isValidIri() regex validation that checks for URL/URN format before interpolating into SPARQL strings.

Major: qualifiers

Added "+ Qualifier" button per property field. Each qualifier has a predicate IRI and value input. Qualifiers are stored as RDF 1.2 reification annotations via the _entities/_reify backend endpoint:

<< <entity> <predicate> "value" >> <qualifier-pred> "qualifier-value" .

Major: non-stated triple persistence

Added POST /_ds/{dataset}/_entities/_reify backend endpoint that uses Turtle I/O (store.load_from_reader()) to store RDF 1.2 reification annotations, since the SPARQL parser does not support << s p o >> syntax. Supports both concon:StatedTriple and concon:NonStatedTriple annotation types.

Major: dataset name bug

The template now receives both dataset_name (display name) and qualified_name (full path like "materials/pot"). The JavaScript uses qualified_name for API calls.

Medium: shapesjson XSS

Replaced inline '{{ shapes_json|safe }}' with a typed <script type"application/json">= element, eliminating the single quote breakage.

Additional: existing entity values

When selecting an entity from search results, the form now loads existing property values from the dataset via a SPARQL query.

Files created

File Purpose
src/shacl.rs SHACL parsing + entity search
templates/entity_editor.html Entity editor UI

Files modified

File Changes
src/lib.rs Register shacl module
src/main.rs Register shacl module
src/store.rs SHACL stores in StoreManager
src/routes/content.rs Compound view parsing + dispatch
src/routes/web.rs entity_editor() handler
src/routes/sparql.rs sparql_update_post_on_store()
src/routes/datasets.rs /_api/search endpoint

Tests

48 tests pass (33 existing + 15 new). cargo fmt and cargo clippy produce zero warnings/errors.

Branch

issue/002-shacl-driven-entity-editor (2 commits on top of issue/001)

# Summary A form-based RDF entity editor driven by SHACL shapes has been implemented. Users select an entity type (SHACL NodeShape), and a form is generated from the shape's property constraints. Mandatory fields appear by default, optional fields can be added via a "+" button, and each property value can be saved individually via AJAX. # Review fixes (second commit) All findings from the maintainer review were addressed: ## Critical: SPARQL injection fixes - `search_entities()`: added `is_valid_iri()` validation that rejects IRIs containing `>`, whitespace, braces, `<`, or control characters. Invalid class parameters return an empty result set. - `saveField()` JS: added `isValidIri()` regex validation that checks for URL/URN format before interpolating into SPARQL strings. ## Major: qualifiers Added "+ Qualifier" button per property field. Each qualifier has a predicate IRI and value input. Qualifiers are stored as RDF 1.2 reification annotations via the `_entities/_reify` backend endpoint: ``` example << <entity> <predicate> "value" >> <qualifier-pred> "qualifier-value" . ``` ## Major: non-stated triple persistence Added `POST /_ds/{dataset}/_entities/_reify` backend endpoint that uses Turtle I/O (`store.load_from_reader()`) to store RDF 1.2 reification annotations, since the SPARQL parser does not support `<< s p o >>` syntax. Supports both `concon:StatedTriple` and `concon:NonStatedTriple` annotation types. ## Major: dataset name bug The template now receives both `dataset_name` (display name) and `qualified_name` (full path like `"materials/pot"`). The JavaScript uses `qualified_name` for API calls. ## Medium: shapes<sub>json</sub> XSS Replaced inline `'{{ shapes_json|safe }}'` with a typed `<script type`"application/json"\>= element, eliminating the single quote breakage. ## Additional: existing entity values When selecting an entity from search results, the form now loads existing property values from the dataset via a SPARQL query. # Files created | File | Purpose | |--------------------------------|-------------------------------| | `src/shacl.rs` | SHACL parsing + entity search | | `templates/entity_editor.html` | Entity editor UI | # Files modified | File | Changes | |--------------------------|----------------------------------| | `src/lib.rs` | Register `shacl` module | | `src/main.rs` | Register `shacl` module | | `src/store.rs` | SHACL stores in StoreManager | | `src/routes/content.rs` | Compound view parsing + dispatch | | `src/routes/web.rs` | `entity_editor()` handler | | `src/routes/sparql.rs` | `sparql_update_post_on_store()` | | `src/routes/datasets.rs` | `/_api/search` endpoint | # Tests 48 tests pass (33 existing + 15 new). `cargo fmt` and `cargo clippy` produce zero warnings/errors. # Branch `issue/002-shacl-driven-entity-editor` (2 commits on top of issue/001)
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
daniel/concon#2
No description provided.