Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 22 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ void create_example_ro_crate() {
// --------------------------------------------------------------------------
// Add description to the root metadata entity (ro-crate-metadata.json) as
// this is currently missing
//
// Shows two methods for updating entities already in a crate A) and B)

// Get the root metadata entity from the crate and set the description
Entity root = crate.getEntity("ro-crate-metadata.json");
// A) Get a reference to allow mutability
Entity& root = crate.getEntity("ro-crate-metadata.json");
root.set("description", "RO-Crate Metadata File Descriptor (this file)");

// Add name, description to the root data entity (./)
Entity rootData = crate.getEntity("./");
rootData.set("name", "Example RO-Crate");
rootData.set("description", "The RO-Crate Root Data Entity");
// B) Directly edit in the crate
crate.getEntity("./").set("name", "Example RO-Crate");
crate.getEntity("./").set("description", "The RO-Crate Root Data Entity");

// --------------------------------------------------------------------------
// Create the person, which is a contextual entity, and add it to the crate
Expand All @@ -63,11 +66,11 @@ void create_example_ro_crate() {
data1.set("author", alice);
data1.set("contentLocation", catalinaPark);
crate.addEntity("data1.txt", data1);
rootData.set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1
crate.getEntity("./").set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1

Entity data2({"File"});
crate.addEntity("data2.txt", data2);
rootData.set("hasPart", data2);
crate.getEntity("./").set("hasPart", data2);

// --------------------------------------------------------------------------
// Write out
Expand Down Expand Up @@ -166,50 +169,33 @@ relationships between entities.
When an entity is created, it is not automatically added to the RO-Crate. The user
must explicitly add the entity to the RO-Crate using the `addEntity` method.

When an entity is added to the RO-Crate, it is stored in a map of entities.
When an entity is added to the RO-Crate, a copy is stored in a map of entities.

For maximum flexiblity, an already added entity may still be updated on the
original entity object. The RO-Crate shares the same entity object, so any changes
made to the original entity will be reflected in the RO-Crate.

Worked example:
Updates to the copy will not be reflected in the RO-Crate. If you want to update
an entity, you must retrieve it. See below for an example:

```cpp
ROCrate crate;

Entity alice({"Person"});
alice.set("name", "Alice");

crate.addEntity("#alice", alice);

/* Crate state:
{
"#alice": {
"type": ["Person"],
"name": "Alice"
}
}
*/

// Add an additional property to the crate entity
Entity crateAlice = crate.getEntity("#alice");
crateAlice.set("description", "One of hopefully many Contextual Entities");
// Update the entity, use & to allow modifications
Entity& aliceCopy = crate.getEntity("#alice");
aliceCopy.set("name", "Alice Smith");

// Add an additional property to the original entity object
alice.set("occupation", "Software Engineer");

/* Crate state:
/* Output
{
"#alice": {
"type": ["Person"],
"name": "Alice",
"description": "One of hopefully many Contextual Entities",
"occupation": "Software Engineer"
}
"@id": "#alice",
"@type": [
"Person"
],
"name": "Alice Smith"
}
*/
```


## Reference

Reference documentation generated by [doxide](doxide.org) is available [here](http://esciencelab.org.uk/ro-crate-cpp/).
37 changes: 21 additions & 16 deletions include/ro-crate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#include <nlohmann/json.hpp>

#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>
#include <map>
Expand Down Expand Up @@ -79,11 +78,10 @@ namespace rocrate {
* @throw std::invalid_argument if the id is empty.
*/
void assignId(const std::string& id);
std::shared_ptr<Properties> properties_;
Properties properties_;
};

inline Entity::Entity(std::vector<std::string> types)
: properties_(std::make_shared<Properties>()) {
inline Entity::Entity(std::vector<std::string> types) {

// Validate types (reject empty)
if ( types.empty() ) {
Expand All @@ -94,7 +92,7 @@ namespace rocrate {
std::vector<PropertyValue> typeValues;
for (const auto& type : types)
typeValues.push_back({type, ValueType::Literal});
properties_->emplace("@type", typeValues);
properties_.emplace("@type", typeValues);
}

inline void Entity::set(Property property, Value value, ValueType valueType) {
Expand All @@ -108,7 +106,7 @@ namespace rocrate {
"Use ROCrate::addEntity to assign an ID.");

// Add the value to the property in the properties map
(*properties_)[property].push_back({value, valueType});
properties_[property].push_back({value, valueType});
}

inline void Entity::set(Property property, const Entity& entity) {
Expand All @@ -117,13 +115,20 @@ namespace rocrate {
throw std::invalid_argument("Property name cannot be empty.");
}

// Check if the entity has an '@id' property set
const auto id = entity.properties_->find("@id");
if (id == entity.properties_->end() || id->second.empty())
throw std::runtime_error("Entity does not have an '@id' property set.");
// Check if the entity has an '@id' property
auto it = entity.properties_.find("@id");
if ( it == entity.properties_.end()) {
throw std::runtime_error("Referenced entity must have an '@id' property.");
}

// Add JSON reference to the entity's '@id' to the property in the properties map
set(property, id->second.front().value, ValueType::Reference);
// Get the ID and check it isn't empty
const auto& idValues = it->second;
if (idValues.empty()) {
throw std::runtime_error("Referenced entity must have a non-empty '@id' property.");
}

// Add the reference to the property in the properties map
set(property, idValues[0].value, ValueType::Reference);
}

inline void Entity::assignId(const std::string& id) {
Expand All @@ -133,7 +138,7 @@ namespace rocrate {
}

// Assign the '@id' property to the entity's properties map
(*properties_)["@id"] = {{id, ValueType::Literal}};
properties_["@id"] = {{id, ValueType::Literal}};
}

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -169,7 +174,7 @@ namespace rocrate {
void addEntity(const std::string& id, Entity& entity);

/**
* Retrieves an entity from the RO-Crate's entity register by its id.
* Retrieves an entity reference from the RO-Crate's entity register by its id.
*
* @param id The identifier of the entity to retrieve.
* @return A reference to the entity with the specified id.
Expand Down Expand Up @@ -213,14 +218,14 @@ namespace rocrate {
"https://w3id.org/ro/crate/1.1",
ValueType::Reference
);
addEntity("ro-crate-metadata.json", rootEntity);

// Create the root dataset entity
Entity datasetEntity({"Dataset"});
addEntity("./", datasetEntity);

// Add the root dataset entity to the root metadata entity
rootEntity.set("about", datasetEntity);
addEntity("ro-crate-metadata.json", rootEntity);
}

inline void ROCrate::addEntity(const std::string& id, Entity& entity) {
Expand Down Expand Up @@ -301,7 +306,7 @@ namespace rocrate {
nlohmann::json serialized;
serialized["@id"] = id;

for (const auto& [property, values] : *entity.properties_) {
for (const auto& [property, values] : entity.properties_) {
if (property == "@id") {
continue;
}
Expand Down
17 changes: 10 additions & 7 deletions tests/example.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ void create_example_ro_crate() {
// --------------------------------------------------------------------------
// Add description to the root metadata entity (ro-crate-metadata.json) as
// this is currently missing
//
// Shows two methods for updating entities already in a crate A) and B)

// Get the root metadata entity from the crate and set the description
Entity root = crate.getEntity("ro-crate-metadata.json");
// A) Get a reference to allow mutability
Entity& root = crate.getEntity("ro-crate-metadata.json");
root.set("description", "RO-Crate Metadata File Descriptor (this file)");

// Add name, description to the root data entity (./)
Entity rootData = crate.getEntity("./");
rootData.set("name", "Example RO-Crate");
rootData.set("description", "The RO-Crate Root Data Entity");

// B) Directly edit in the crate
crate.getEntity("./").set("name", "Example RO-Crate");
crate.getEntity("./").set("description", "The RO-Crate Root Data Entity");
// --------------------------------------------------------------------------
// Create the person, which is a contextual entity, and add it to the crate

Expand All @@ -46,11 +49,11 @@ void create_example_ro_crate() {
data1.set("author", alice);
data1.set("contentLocation", catalinaPark);
crate.addEntity("data1.txt", data1);
rootData.set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1
crate.getEntity("./").set("hasPart", data1); // Ensure that the root data entity has a hasPart relationship to data1

Entity data2({"File"});
crate.addEntity("data2.txt", data2);
rootData.set("hasPart", data2);
crate.getEntity("./").set("hasPart", data2);

// --------------------------------------------------------------------------
// Write out
Expand Down
8 changes: 4 additions & 4 deletions tests/integration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ TEST_CASE("Minimal RO-Crate", "[integration]")
ROCrate crate;

// Add metadata to root data entity
Entity rootData = crate.getEntity("./");
Entity& rootData = crate.getEntity("./");
rootData.set("identifier", "https://doi.org/10.4225/59/59672c09f4a4b");
rootData.set("datePublished", "2017");
rootData.set("name", "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...");
Expand Down Expand Up @@ -47,12 +47,12 @@ TEST_CASE("Example with file, author, location", "[integration]")
ROCrate crate;

// Add description to the root metadata entity (ro-crate-metadata.json)
Entity root = crate.getEntity("ro-crate-metadata.json");
Entity& root = crate.getEntity("ro-crate-metadata.json");
root.set("description", "RO-Crate Metadata File Descriptor (this file)");
REQUIRE_NOTHROW(crate.getEntity("ro-crate-metadata.json"));

// Add name, description to the root data entity (./)
Entity rootData = crate.getEntity("./");
Entity& rootData = crate.getEntity("./");
rootData.set("name", "Example RO-Crate");
rootData.set("description", "The RO-Crate Root Data Entity");
REQUIRE_NOTHROW(crate.getEntity("./"));
Expand Down Expand Up @@ -116,7 +116,7 @@ TEST_CASE("Example with web resources", "[integration]")
crate.addEntity("https://zenodo.org/record/3541888/files/ro-crate-1.0.0.pdf", roCrateSpec);

// Add to root data entity
Entity rootData = crate.getEntity("./");
Entity& rootData = crate.getEntity("./");
rootData.set("hasPart", surveyResponses);
rootData.set("hasPart", roCrateSpec);

Expand Down
12 changes: 0 additions & 12 deletions tests/unit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,6 @@ TEST_CASE("RO-Crate initilises with valid root metadata and dataset entities", "
REQUIRE_NOTHROW(crate.getEntity("./"));
}

TEST_CASE("Crate and builder observe shared entity updates", "[unit]") {
rocrate::ROCrate crate;
Entity alice({"Person"});

crate.addEntity("#alice", alice);

alice.set("name", "Alice");
crate.getEntity("#alice").set("description", "Updated");

SUCCEED("No exceptions thrown, can't currently assert on entity values as they are not exposed in the API");
}

TEST_CASE("Add entity rejects entities with duplicate IDs", "[unit]") {
// Create a crate and add an entity
rocrate::ROCrate crate;
Expand Down
Loading