Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
1 change: 1 addition & 0 deletions .github/workflows/website-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
-DSOURCEMETA_CORE_JSON:BOOL=OFF
-DSOURCEMETA_CORE_JSONL:BOOL=OFF
-DSOURCEMETA_CORE_JSONPOINTER:BOOL=OFF
-DSOURCEMETA_CORE_JSONLD:BOOL=OFF
-DSOURCEMETA_CORE_YAML:BOOL=OFF
-DSOURCEMETA_CORE_JSONRPC:BOOL=OFF
-DSOURCEMETA_CORE_MCP:BOOL=OFF
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/website-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
-DSOURCEMETA_CORE_JSON:BOOL=OFF
-DSOURCEMETA_CORE_JSONL:BOOL=OFF
-DSOURCEMETA_CORE_JSONPOINTER:BOOL=OFF
-DSOURCEMETA_CORE_JSONLD:BOOL=OFF
-DSOURCEMETA_CORE_YAML:BOOL=OFF
-DSOURCEMETA_CORE_JSONRPC:BOOL=OFF
-DSOURCEMETA_CORE_MCP:BOOL=OFF
Expand Down
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ option(SOURCEMETA_CORE_URI "Build the Sourcemeta Core URI library" ON)
option(SOURCEMETA_CORE_URITEMPLATE "Build the Sourcemeta Core URI Template library" ON)
option(SOURCEMETA_CORE_JSON "Build the Sourcemeta Core JSON library" ON)
option(SOURCEMETA_CORE_JSONPOINTER "Build the Sourcemeta Core JSON Pointer library" ON)
option(SOURCEMETA_CORE_JSONLD "Build the Sourcemeta Core JSON-LD library" ON)
option(SOURCEMETA_CORE_JSONL "Build the Sourcemeta Core JSONL library" ON)
option(SOURCEMETA_CORE_YAML "Build the Sourcemeta Core YAML library" ON)
option(SOURCEMETA_CORE_JSONRPC "Build the Sourcemeta Core JSON-RPC library" ON)
Expand Down Expand Up @@ -171,6 +172,10 @@ if(SOURCEMETA_CORE_JSONPOINTER)
add_subdirectory(src/core/jsonpointer)
endif()

if(SOURCEMETA_CORE_JSONLD)
add_subdirectory(src/core/jsonld)
endif()

if(SOURCEMETA_CORE_GZIP)
find_package(LibDeflate REQUIRED)
add_subdirectory(src/core/gzip)
Expand Down Expand Up @@ -338,6 +343,10 @@ if(SOURCEMETA_CORE_TESTS)
add_subdirectory(test/jsonpointer)
endif()

if(SOURCEMETA_CORE_JSONLD)
add_subdirectory(test/jsonld)
endif()

if(SOURCEMETA_CORE_GZIP)
add_subdirectory(test/gzip)
endif()
Expand Down
15 changes: 15 additions & 0 deletions config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ if(NOT SOURCEMETA_CORE_COMPONENTS)
list(APPEND SOURCEMETA_CORE_COMPONENTS json)
list(APPEND SOURCEMETA_CORE_COMPONENTS jsonl)
list(APPEND SOURCEMETA_CORE_COMPONENTS jsonpointer)
list(APPEND SOURCEMETA_CORE_COMPONENTS jsonld)
list(APPEND SOURCEMETA_CORE_COMPONENTS yaml)
list(APPEND SOURCEMETA_CORE_COMPONENTS jsonrpc)
list(APPEND SOURCEMETA_CORE_COMPONENTS mcp)
Expand Down Expand Up @@ -136,6 +137,20 @@ foreach(component ${SOURCEMETA_CORE_COMPONENTS})
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_unicode.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
elseif(component STREQUAL "jsonld")
find_dependency(PCRE2 CONFIG)
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_preprocessor.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_numeric.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_io.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_unicode.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_ip.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_regex.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_text.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_json.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_uri.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonpointer.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_langtag.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_jsonld.cmake")
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
elseif(component STREQUAL "yaml")
find_dependency(PCRE2 CONFIG)
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_regex.cmake")
Expand Down
21 changes: 21 additions & 0 deletions src/core/jsonld/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME jsonld
PRIVATE_HEADERS error.h
SOURCES jsonld.cc
jsonld_iri_expansion.cc jsonld_create_term_definition.cc
jsonld_context_processing.cc jsonld_value_expansion.cc jsonld_expansion.cc
jsonld_algorithms.h jsonld_keywords.h)

if(SOURCEMETA_CORE_INSTALL)
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME jsonld)
endif()

target_link_libraries(sourcemeta_core_jsonld
PUBLIC sourcemeta::core::json)
target_link_libraries(sourcemeta_core_jsonld
PUBLIC sourcemeta::core::jsonpointer)
target_link_libraries(sourcemeta_core_jsonld
PRIVATE sourcemeta::core::uri)
target_link_libraries(sourcemeta_core_jsonld
PRIVATE sourcemeta::core::langtag)
target_link_libraries(sourcemeta_core_jsonld
PRIVATE sourcemeta::core::text)
88 changes: 88 additions & 0 deletions src/core/jsonld/include/sourcemeta/core/jsonld.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#ifndef SOURCEMETA_CORE_JSONLD_H_
#define SOURCEMETA_CORE_JSONLD_H_

#ifndef SOURCEMETA_CORE_JSONLD_EXPORT
#include <sourcemeta/core/jsonld_export.h>
#endif

#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonld_error.h>

#include <cstdint> // std::uint8_t
#include <functional> // std::function
#include <optional> // std::optional

/// @defgroup jsonld JSON-LD
/// @brief A JSON-LD 1.1 processor, with support for the JSON-LD 1.0 processing
/// mode.
///
/// This functionality is included as follows:
///
/// ```cpp
/// #include <sourcemeta/core/jsonld.h>
/// ```

namespace sourcemeta::core {

/// @ingroup jsonld
/// A resolver callback for loading remote JSON-LD contexts referenced during
/// expansion. Given an absolute IRI, it returns the referenced document, or no
/// value if it cannot be resolved.
using JSONLDResolver = std::function<std::optional<JSON>(JSON::StringView)>;

/// @ingroup jsonld
/// The JSON-LD processing mode
enum class JSONLDVersion : std::uint8_t { V1_0, V1_1 };

/// @ingroup jsonld
///
/// Expand a JSON-LD document into its expanded form, resolving relative
/// references against the given base IRI and loading any remote context through
/// the given resolver. The result is always a JSON array. For example:
///
/// ```cpp
/// #include <sourcemeta/core/json.h>
/// #include <sourcemeta/core/jsonld.h>
/// #include <iostream>
///
/// const auto document{sourcemeta::core::parse_json(R"({
/// "@context": { "name": "https://schema.org/name" },
/// "name": "Sourcemeta"
/// })")};
/// const auto expanded{sourcemeta::core::jsonld_expand(document)};
/// sourcemeta::core::prettify(expanded, std::cout);
/// std::cout << std::endl;
/// ```
SOURCEMETA_CORE_JSONLD_EXPORT
auto jsonld_expand(const JSON &input, const JSON::StringView base_iri = "",
const JSONLDResolver &resolver = {},
const JSONLDVersion version = JSONLDVersion::V1_1) -> JSON;

/// @ingroup jsonld
///
/// Expand a JSON-LD document, applying the given expansion context before the
/// document's own context, as if it had been prepended to the input. For
/// example:
///
/// ```cpp
/// #include <sourcemeta/core/json.h>
/// #include <sourcemeta/core/jsonld.h>
/// #include <iostream>
///
/// const auto document{sourcemeta::core::parse_json(
/// R"({ "name": "Sourcemeta" })")};
/// const auto context{sourcemeta::core::parse_json(
/// R"({ "name": "https://schema.org/name" })")};
/// const auto expanded{sourcemeta::core::jsonld_expand(document, context)};
/// sourcemeta::core::prettify(expanded, std::cout);
/// std::cout << std::endl;
/// ```
SOURCEMETA_CORE_JSONLD_EXPORT
auto jsonld_expand(const JSON &input, const JSON &expand_context,
const JSON::StringView base_iri = "",
const JSONLDResolver &resolver = {},
const JSONLDVersion version = JSONLDVersion::V1_1) -> JSON;

} // namespace sourcemeta::core

#endif
68 changes: 68 additions & 0 deletions src/core/jsonld/include/sourcemeta/core/jsonld_error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#ifndef SOURCEMETA_CORE_JSONLD_ERROR_H_
#define SOURCEMETA_CORE_JSONLD_ERROR_H_

#ifndef SOURCEMETA_CORE_JSONLD_EXPORT
#include <sourcemeta/core/jsonld_export.h>
#endif

#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonpointer.h>

#include <exception> // std::exception
#include <initializer_list> // std::initializer_list
#include <utility> // std::move

namespace sourcemeta::core {

// Exporting symbols that depends on the standard C++ library is considered
// safe.
// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
#if defined(_MSC_VER)
#pragma warning(disable : 4251 4275)
#endif

/// @ingroup jsonld
/// An error that represents a JSON-LD processing failure. The message is one of
/// the error codes defined by the JSON-LD 1.1 API specification, and the
/// pointer locates the offending position in the input document
class SOURCEMETA_CORE_JSONLD_EXPORT JSONLDError : public std::exception {
public:
JSONLDError(const char *code, Pointer pointer)
Comment thread
jviotti marked this conversation as resolved.
: code_{code}, pointer_{std::move(pointer)} {}

// Locate the error at a weak pointer, materialising an owned pointer.
JSONLDError(const char *code, const WeakPointer &pointer)
: code_{code}, pointer_{to_pointer(pointer)} {}

// Locate the error at a weak pointer extended with the given trailing
// property tokens.
JSONLDError(const char *code, const WeakPointer &pointer,
const std::initializer_list<JSON::StringView> children)
: code_{code}, pointer_{to_pointer(pointer)} {
for (const auto child : children) {
this->pointer_.push_back(JSON::String{child});
}
}

[[nodiscard]] auto what() const noexcept -> const char * override {
return this->code_;
}

/// Get the JSON Pointer to the position in the input document that caused the
/// error
[[nodiscard]] auto pointer() const noexcept -> const Pointer & {
return this->pointer_;
}

private:
const char *code_;
Pointer pointer_;
};

#if defined(_MSC_VER)
#pragma warning(default : 4251 4275)
#endif

} // namespace sourcemeta::core

#endif
88 changes: 88 additions & 0 deletions src/core/jsonld/jsonld.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonld.h>
#include <sourcemeta/core/jsonpointer.h>

#include "jsonld_algorithms.h"
#include "jsonld_keywords.h"

#include <optional> // std::nullopt
#include <utility> // std::move

namespace sourcemeta::core {

// Run the expansion algorithm on a top-level input document and normalise the
// result into the expanded document form (JSON-LD 1.1 API Section 5.1).
auto run_expansion(ExpansionState &state, ActiveContext &active_context,
const JSON &input) -> JSON {
auto expanded{
expand(state, active_context, std::nullopt, input, empty_weak_pointer)};

// A top-level map containing only @graph is replaced by its value.
if (expanded.is_object() && expanded.object_size() == 1 &&
expanded.defines(KEYWORD_GRAPH, KEYWORD_GRAPH_HASH)) {
expanded = expanded.at(KEYWORD_GRAPH, KEYWORD_GRAPH_HASH);
}

if (expanded.is_object()) {
if (expanded.empty() || (expanded.object_size() == 1 &&
expanded.defines(KEYWORD_ID, KEYWORD_ID_HASH))) {
return JSON::make_array();
}
auto result{JSON::make_array()};
result.push_back(std::move(expanded));
return result;
}

if (!expanded.is_array()) {
return JSON::make_array();
}

return expanded;
}

// Set up the shared expansion state and initial active context from the public
// entry-point arguments.
auto initialise_expansion(const JSONLDResolver &resolver,
const JSON::StringView base_iri,
const JSONLDVersion version, ExpansionState &state,
ActiveContext &active_context) -> void {
state.resolver = &resolver;
state.processing_1_0 = version == JSONLDVersion::V1_0;
if (!base_iri.empty()) {
active_context.base = JSON::String{base_iri};
state.document_base = JSON::String{base_iri};
}
}

} // namespace sourcemeta::core

namespace sourcemeta::core {

auto jsonld_expand(const JSON &input, const JSON::StringView base_iri,
const JSONLDResolver &resolver, const JSONLDVersion version)
-> JSON {
ExpansionState state;
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
ActiveContext active_context;
initialise_expansion(resolver, base_iri, version, state, active_context);
return run_expansion(state, active_context, input);
}

auto jsonld_expand(const JSON &input, const JSON &expand_context,
const JSON::StringView base_iri,
const JSONLDResolver &resolver, const JSONLDVersion version)
-> JSON {
ExpansionState state;
ActiveContext active_context;
initialise_expansion(resolver, base_iri, version, state, active_context);
const auto &context{
expand_context.is_object() &&
expand_context.defines(KEYWORD_CONTEXT, KEYWORD_CONTEXT_HASH)
? expand_context.at(KEYWORD_CONTEXT, KEYWORD_CONTEXT_HASH)
: expand_context};
// The external expansion context is not part of the input document, so its
// errors carry an empty pointer.
process_context(state, active_context, context, empty_weak_pointer);
return run_expansion(state, active_context, input);
}

} // namespace sourcemeta::core
Loading
Loading