diff --git a/.gitignore b/.gitignore index f998a83..51328d6 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ linkeddatahub/.DS_Store docs/docs.trix docs/files.xml docs/html +docs/timestamps.xml diff --git a/check-links.sh b/check-links.sh index 9912349..48f04bf 100755 --- a/check-links.sh +++ b/check-links.sh @@ -15,7 +15,16 @@ print(result[len('http://x/'):]) while IFS= read -r -d '' ttl_file; do rel="${ttl_file#"$REPO_DIR"/}" - url_path="${rel%.ttl}/" + + # A .ttl is served at / — except root.ttl, which install.sh PUTs + # at the application base itself rather than at /root/. Resolving a root.ttl's + # relative links against /root/ would send them one level too deep. + if [[ "$(basename "$rel")" == "root.ttl" ]]; then + url_path="$(dirname "$rel")/" + [[ "$url_path" == "./" ]] && url_path="" + else + url_path="${rel%.ttl}/" + fi while IFS= read -r url; do [[ "$url" =~ ^https?:// ]] && continue diff --git a/demo/northwind-traders/admin/model/ns.ttl b/demo/northwind-traders/admin/model/ns.ttl index c46c722..e3f4e0d 100644 --- a/demo/northwind-traders/admin/model/ns.ttl +++ b/demo/northwind-traders/admin/model/ns.ttl @@ -1,5 +1,6 @@ @prefix : <#> . @prefix ldh: . +@prefix ac: . @prefix rdfs: . @prefix owl: . @prefix sp: . @@ -20,7 +21,7 @@ schema:Order a owl:Class ; rdfs:isDefinedBy : . schema:City a owl:Class ; - rdfs:label "City" ; + rdfs:label "Territory" ; rdfs:isDefinedBy : . schema:PostalAddress a owl:Class ; @@ -28,11 +29,11 @@ schema:PostalAddress a owl:Class ; rdfs:isDefinedBy : . schema:ProductGroup a owl:Class ; - rdfs:label "Product group" ; + rdfs:label "Category" ; rdfs:isDefinedBy : . schema:Corporation a owl:Class ; - rdfs:label "Corporation" ; + rdfs:label "Company" ; rdfs:isDefinedBy : . schema:ContactPoint a owl:Class ; @@ -40,7 +41,33 @@ schema:ContactPoint a owl:Class ; rdfs:isDefinedBy : . schema:ParcelDelivery a owl:Class ; - rdfs:label "Parcel delivery" ; + rdfs:label "Delivery" ; + rdfs:isDefinedBy : . + +schema:DeliveryEvent a owl:Class ; + rdfs:label "Shipment" ; + rdfs:isDefinedBy : . + +schema:Offer a owl:Class ; + rdfs:label "Offer" ; + rdfs:isDefinedBy : . + +schema:PriceSpecification a owl:Class ; + rdfs:label "Price specification" ; + rdfs:isDefinedBy : . + +schema:DeliveryChargeSpecification a owl:Class ; + rdfs:label "Delivery charge" ; + rdfs:subClassOf schema:PriceSpecification ; + rdfs:isDefinedBy : . + +schema:UnitPriceSpecification a owl:Class ; + rdfs:label "Unit price" ; + rdfs:subClassOf schema:PriceSpecification ; + rdfs:isDefinedBy : . + +schema:DeliveryMethod a owl:Class ; + rdfs:label "Delivery method" ; rdfs:isDefinedBy : . schema:OrderItem a owl:Class ; @@ -52,19 +79,19 @@ schema:Product a owl:Class ; rdfs:isDefinedBy : . schema:Place a owl:Class ; - rdfs:label "Place" ; + rdfs:label "Region" ; rdfs:isDefinedBy : . # properties schema:broker a owl:ObjectProperty ; - rdfs:label "Broker"; + rdfs:label "Sales rep"; rdfs:domain schema:Order ; rdfs:range schema:Person ; rdfs:isDefinedBy : . schema:areaServed a owl:ObjectProperty ; - rdfs:label "Area served"; + rdfs:label "Territory"; rdfs:domain schema:Person ; rdfs:range schema:City ; rdfs:isDefinedBy : . @@ -94,11 +121,58 @@ schema:customer a owl:ObjectProperty ; rdfs:isDefinedBy : . schema:orderDelivery a owl:ObjectProperty ; - rdfs:label "Order delivery" ; + rdfs:label "Delivery" ; rdfs:domain schema:Order ; rdfs:range schema:ParcelDelivery ; rdfs:isDefinedBy : . +schema:acceptedOffer a owl:ObjectProperty ; + rdfs:label "Accepted offer" ; + rdfs:domain schema:Order ; + rdfs:range schema:Offer ; + rdfs:isDefinedBy : . + +schema:offers a owl:ObjectProperty ; + rdfs:label "Offer" ; + rdfs:domain schema:Product ; + rdfs:range schema:Offer ; + rdfs:isDefinedBy : . + +schema:priceType a owl:ObjectProperty ; + rdfs:label "Price type" ; + rdfs:domain schema:UnitPriceSpecification ; + rdfs:isDefinedBy : . + +schema:itemOffered a owl:ObjectProperty ; + rdfs:label "Product" ; + rdfs:domain schema:Offer ; + rdfs:range schema:Product ; + rdfs:isDefinedBy : . + +schema:priceSpecification a owl:ObjectProperty ; + rdfs:label "Price specification" ; + rdfs:domain schema:Offer ; + rdfs:range schema:PriceSpecification ; + rdfs:isDefinedBy : . + +schema:appliesToDeliveryMethod a owl:ObjectProperty ; + rdfs:label "Delivery method" ; + rdfs:domain schema:DeliveryChargeSpecification ; + rdfs:range schema:DeliveryMethod ; + rdfs:isDefinedBy : . + +schema:deliveryStatus a owl:ObjectProperty ; + rdfs:label "Shipment" ; + rdfs:domain schema:ParcelDelivery ; + rdfs:range schema:DeliveryEvent ; + rdfs:isDefinedBy : . + +schema:partOfOrder a owl:ObjectProperty ; + rdfs:label "Order" ; + rdfs:domain schema:ParcelDelivery ; + rdfs:range schema:Order ; + rdfs:isDefinedBy : . + schema:deliveryAddress a owl:ObjectProperty ; rdfs:label "Delivery address" ; rdfs:domain schema:ParcelDelivery ; @@ -117,20 +191,24 @@ schema:category a owl:ObjectProperty ; rdfs:isDefinedBy : . schema:containedInPlace a owl:ObjectProperty ; - rdfs:label "Contained in place" ; + rdfs:label "Region" ; rdfs:domain schema:City ; rdfs:range schema:Place ; rdfs:isDefinedBy : . schema:sponsor a owl:ObjectProperty ; - rdfs:label "Sponsor" ; + rdfs:label "Reports to" ; rdfs:domain schema:Person ; rdfs:range schema:Person ; rdfs:isDefinedBy : . schema:orderedItem a owl:ObjectProperty ; rdfs:label "Ordered item" ; - rdfs:range schema:Product ; + rdfs:isDefinedBy : . + +schema:orderStatus a owl:ObjectProperty ; + rdfs:label "Order status" ; + rdfs:domain schema:Order ; rdfs:isDefinedBy : . schema:name a owl:DatatypeProperty ; @@ -146,7 +224,7 @@ schema:description a owl:DatatypeProperty ; rdfs:isDefinedBy : . schema:legalName a owl:DatatypeProperty ; - rdfs:label "Legal name" ; + rdfs:label "Company name" ; rdfs:domain schema:Corporation ; rdfs:isDefinedBy : . @@ -209,26 +287,32 @@ schema:birthDate a owl:DatatypeProperty ; rdfs:isDefinedBy : . schema:orderQuantity a owl:DatatypeProperty ; - rdfs:label "Order quantity" ; - rdfs:domain schema:OrderItem ; - rdfs:isDefinedBy : . - -schema:discount a owl:DatatypeProperty ; - rdfs:label "Discount" ; + rdfs:label "Quantity" ; rdfs:domain schema:OrderItem ; rdfs:isDefinedBy : . +# left without an rdfs:domain on purpose: schema:price is carried both by the order +# lines (as schema:Offer) and by the freight charge (as schema:DeliveryChargeSpecification) schema:price a owl:DatatypeProperty ; rdfs:label "Price" ; rdfs:isDefinedBy : . +schema:priceCurrency a owl:DatatypeProperty ; + rdfs:label "Currency" ; + rdfs:isDefinedBy : . + +schema:startDate a owl:DatatypeProperty ; + rdfs:label "Shipped" ; + rdfs:domain schema:DeliveryEvent ; + rdfs:isDefinedBy : . + schema:orderDate a owl:DatatypeProperty ; rdfs:label "Order date" ; rdfs:domain schema:Order ; rdfs:isDefinedBy : . schema:expectedArrivalUntil a owl:DatatypeProperty ; - rdfs:label "Expected arrival until" ; + rdfs:label "Required by" ; rdfs:domain schema:ParcelDelivery ; rdfs:isDefinedBy : . @@ -263,6 +347,8 @@ schema:broker ldh:inverseView :OrdersHandledByEmployee . :OrdersHandledByEmployee a ldh:View ; dct:title "Orders handled by this employee" ; spin:query :SelectOrdersHandledByEmployee ; + ac:mode ac:TableMode ; + ldh:showWhenEmpty false ; rdfs:isDefinedBy : . :SelectOrdersHandledByEmployee a sp:Select ; @@ -273,9 +359,12 @@ PREFIX schema: SELECT DISTINCT ?order WHERE { GRAPH ?graph - { ?order schema:broker $about } + { ?order schema:broker $about ; + schema:orderDate ?orderDate ; + schema:orderStatus ?status + } } -ORDER BY DESC(?order) +ORDER BY DESC(?orderDate) """ ; rdfs:isDefinedBy : . @@ -288,6 +377,8 @@ schema:customer ldh:inverseView :OrdersFromCustomer . :OrdersFromCustomer a ldh:View ; dct:title "Orders from this customer" ; spin:query :SelectOrdersFromCustomer ; + ac:mode ac:TableMode ; + ldh:showWhenEmpty false ; rdfs:isDefinedBy : . :SelectOrdersFromCustomer a sp:Select ; @@ -298,9 +389,12 @@ PREFIX schema: SELECT DISTINCT ?order WHERE { GRAPH ?graph - { ?order schema:customer $about } + { ?order schema:customer $about ; + schema:orderDate ?orderDate ; + schema:orderStatus ?status + } } -ORDER BY DESC(?order) +ORDER BY DESC(?orderDate) """ ; rdfs:isDefinedBy : . @@ -313,6 +407,8 @@ schema:provider ldh:inverseView :ProductsFromSupplier . :ProductsFromSupplier a ldh:View ; dct:title "Products supplied by this supplier" ; spin:query :SelectProductsFromSupplier ; + ac:mode ac:TableMode ; + ldh:showWhenEmpty false ; rdfs:isDefinedBy : . :SelectProductsFromSupplier a sp:Select ; @@ -323,12 +419,45 @@ PREFIX schema: SELECT DISTINCT ?product WHERE { GRAPH ?graph - { ?product schema:provider $about } + { ?product a schema:Product ; + schema:provider $about ; + schema:category ?category ; + schema:offers/schema:price ?price + } } ORDER BY ?product """ ; rdfs:isDefinedBy : . +# orders shipped by this shipper + +schema:provider ldh:inverseView :OrdersShippedByShipper . + +:OrdersShippedByShipper a ldh:View ; + dct:title "Orders shipped by this shipper" ; + spin:query :SelectOrdersShippedByShipper ; + ac:mode ac:TableMode ; + ldh:showWhenEmpty false ; + rdfs:isDefinedBy : . + +:SelectOrdersShippedByShipper a sp:Select ; + rdfs:label "Select orders shipped by shipper" ; + sp:text """ +PREFIX schema: + +SELECT DISTINCT ?order +WHERE + { GRAPH ?graph + { ?order schema:orderDelivery ?delivery ; + schema:orderDate ?orderDate ; + schema:orderStatus ?status . + ?delivery schema:provider $about + } + } +ORDER BY DESC(?orderDate) +""" ; + rdfs:isDefinedBy : . + # containedInPlace # cities in this region @@ -338,6 +467,8 @@ schema:containedInPlace ldh:inverseView :CitiesInRegion . :CitiesInRegion a ldh:View ; dct:title "Cities in this region" ; spin:query :SelectCitiesInRegion ; + ac:mode ac:TableMode ; + ldh:showWhenEmpty false ; rdfs:isDefinedBy : . :SelectCitiesInRegion a sp:Select ; @@ -356,6 +487,34 @@ ORDER BY ?city # orderedItem +# line items of this order (forward view — the master-detail "detail gallery") + +schema:orderedItem ldh:view :OrderLineItems . + +:OrderLineItems a ldh:View ; + dct:title "Line items" ; + spin:query :SelectOrderLineItems ; + ac:mode ac:TableMode ; + rdfs:isDefinedBy : . + +:SelectOrderLineItems a sp:Select ; + rdfs:label "Select line items of order" ; + sp:text """ +PREFIX schema: + +SELECT DISTINCT ?item +WHERE + { GRAPH ?graph + { $about schema:orderedItem ?item . + ?item schema:orderedItem ?product ; + schema:orderQuantity ?quantity ; + schema:price ?lineTotal + } + } +ORDER BY ?item +""" ; + rdfs:isDefinedBy : . + # orders containing this product schema:orderedItem ldh:inverseView :OrdersContainingProduct . @@ -363,6 +522,8 @@ schema:orderedItem ldh:inverseView :OrdersContainingProduct . :OrdersContainingProduct a ldh:View ; dct:title "Orders containing this product" ; spin:query :SelectOrdersContainingProduct ; + ac:mode ac:TableMode ; + ldh:showWhenEmpty false ; rdfs:isDefinedBy : . :SelectOrdersContainingProduct a sp:Select ; @@ -373,10 +534,12 @@ PREFIX schema: SELECT DISTINCT ?order WHERE { GRAPH ?graph - { ?order schema:orderedItem ?orderItem . + { ?order schema:orderedItem ?orderItem ; + schema:orderDate ?orderDate ; + schema:orderStatus ?status . ?orderItem schema:orderedItem $about } } -ORDER BY DESC(?order) +ORDER BY DESC(?orderDate) """ ; rdfs:isDefinedBy : . diff --git a/demo/northwind-traders/categories.ttl b/demo/northwind-traders/categories.ttl index 04e071d..e4fb038 100644 --- a/demo/northwind-traders/categories.ttl +++ b/demo/northwind-traders/categories.ttl @@ -10,21 +10,35 @@ <> a dh:Container ; dct:title "Categories" ; rdf:_1 <#category-intro> ; - rdf:_2 <#category-revenue-block> ; - rdf:_3 <#category-distribution> ; - rdf:_4 <#products-per-category-block> ; - rdf:_5 <#select-categories> . + rdf:_2 <#select-categories> ; + rdf:_3 <#revenue-intro> ; + rdf:_4 <#category-revenue-block> ; + rdf:_5 <#category-distribution> ; + rdf:_6 <#products-per-category-block> . # Intro XHTML <#category-intro> a ldh:XHTML ; rdf:value """
-

Category analysis

-

Product categories represent different market segments with distinct characteristics and performance. - Analyzing category-level metrics helps identify portfolio strengths, diversification opportunities, and inventory priorities.

+
+

Product categories

+

The segments the catalogue is organised into — beverages, condiments, produce and the rest. + Open one to see the products it holds.

+
+
"""^^rdf:XMLLiteral . + + # Revenue intro + <#revenue-intro> a ldh:XHTML ; + rdf:value """
+
+

Where the money is

+

Each category is a market segment with its own performance. Revenue first, then the spread of + products behind it.

+
"""^^rdf:XMLLiteral . # Category revenue chart (wrapped in ldh:Object per ContentMode contract) <#category-revenue-block> a ldh:Object ; + dct:title "Revenue by category" ; rdf:value <#category-revenue> . <#category-revenue> a ldh:ResultSetChart ; @@ -43,9 +57,7 @@ WHERE { GRAPH ?orderGraph { ?order schema:orderedItem ?orderItem . ?orderItem schema:orderedItem ?product ; - schema:orderQuantity ?quantity ; - schema:price ?price . - BIND(?quantity * ?price AS ?sale) + schema:price ?sale . } GRAPH ?productGraph { ?product schema:category ?category . @@ -60,13 +72,16 @@ ORDER BY DESC(?revenue)""" . # Category distribution intro <#category-distribution> a ldh:XHTML ; rdf:value """
-

Product distribution

-

Understanding how products are distributed across categories reveals portfolio balance and potential gaps. - Categories with more products may indicate core competencies or simply broader product lines.

+
+

Portfolio balance

+

Revenue and range don't always line up. A category can earn well on few lines, or spread wide + and earn little — the gap between the two charts is where the portfolio questions are.

+
"""^^rdf:XMLLiteral . # Products per category chart (wrapped in ldh:Object per ContentMode contract) <#products-per-category-block> a ldh:Object ; + dct:title "Products per category" ; rdf:value <#products-per-category> . <#products-per-category> a ldh:ResultSetChart ; @@ -95,6 +110,7 @@ ORDER BY DESC(?productCount)""" . # Object block (references the view) <#select-categories> a ldh:Object ; + dct:title "All categories" ; rdf:value <#select-categories-view> . # View block (references the query, uses GridMode) diff --git a/demo/northwind-traders/customers.ttl b/demo/northwind-traders/customers.ttl index 27e421f..ba3d43d 100644 --- a/demo/northwind-traders/customers.ttl +++ b/demo/northwind-traders/customers.ttl @@ -10,22 +10,25 @@ <> a dh:Container ; dct:title "Customers" ; rdf:_1 <#customers-intro> ; - rdf:_2 <#top-customers-block> ; - rdf:_3 <#geographic-distribution> ; - rdf:_4 <#customers-by-country-block> ; - rdf:_5 <#customer-insights> ; - rdf:_6 <#select-customers> . + rdf:_2 <#select-customers> ; + rdf:_3 <#customer-insights> ; + rdf:_4 <#top-customers-block> ; + rdf:_5 <#geographic-distribution> ; + rdf:_6 <#customers-by-country-block> . # Intro XHTML <#customers-intro> a ldh:XHTML ; rdf:value """
-

Customer insights

-

Understand customer value, geographic distribution, and purchasing patterns. Identifying top customers - and market concentration helps prioritize account management and target growth strategies.

+
+

The account book

+

Every company Northwind sells to, with its contact details and address. Open an account to see + its full order history.

+
"""^^rdf:XMLLiteral . # Top customers chart (wrapped in ldh:Object per ContentMode contract) <#top-customers-block> a ldh:Object ; + dct:title "Top 10 customers by revenue" ; rdf:value <#top-customers> . <#top-customers> a ldh:ResultSetChart ; @@ -44,9 +47,7 @@ WHERE { GRAPH ?orderGraph { ?order schema:customer ?customer ; schema:orderedItem ?orderItem . - ?orderItem schema:orderQuantity ?quantity ; - schema:price ?price . - BIND(?quantity * ?price AS ?sale) + ?orderItem schema:price ?sale . } GRAPH ?customerGraph { ?customer schema:legalName ?companyName . @@ -59,13 +60,16 @@ LIMIT 10""" . # Geographic distribution intro <#geographic-distribution> a ldh:XHTML ; rdf:value """
-

Geographic distribution

-

Our customer base spans multiple countries, with varying levels of market penetration. - Understanding geographic concentration helps guide expansion efforts and resource allocation.

+
+

Geographic distribution

+

The customer base spans many countries at very different depths. Where the accounts cluster is + where the account managers should be.

+
"""^^rdf:XMLLiteral . # Customers by country chart (wrapped in ldh:Object per ContentMode contract) <#customers-by-country-block> a ldh:Object ; + dct:title "Customers by country" ; rdf:value <#customers-by-country> . <#customers-by-country> a ldh:ResultSetChart ; @@ -94,13 +98,15 @@ ORDER BY DESC(?customerCount)""" . # Customer insights summary <#customer-insights> a ldh:XHTML ; rdf:value """
-

Customer portfolio

-

Browse individual customer records below to view detailed order history, contact information, - and relationship details. Each customer profile provides insights into purchasing behavior and account status.

+
+

Account value

+

Not every account is worth the same. Revenue concentrates in a handful of them.

+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-customers> a ldh:Object ; + dct:title "All customers" ; rdf:value <#select-customers-view> . # View block (references the query, uses TableMode) diff --git a/demo/northwind-traders/customers/customers.rq b/demo/northwind-traders/customers/customers.rq index 20c4dc1..5f17799 100644 --- a/demo/northwind-traders/customers/customers.rq +++ b/demo/northwind-traders/customers/customers.rq @@ -52,13 +52,17 @@ WHERE <#contactTitle> ?contactTitle ; <#address> ?address ; <#city> ?city ; - <#postalCode> ?postalCode ; <#country> ?country ; <#phone> ?phone . OPTIONAL { ?customer_row <#region> ?region } + # Ireland had no postcodes until Eircode in 2015, so HUNGO in Cork has none; + # requiring one here dropped the customer and left its 19 orders pointing at nothing + OPTIONAL { + ?customer_row <#postalCode> ?postalCode + } OPTIONAL { ?customer_row <#fax> ?fax } diff --git a/demo/northwind-traders/employees.ttl b/demo/northwind-traders/employees.ttl index ec672cc..ffe4ca5 100644 --- a/demo/northwind-traders/employees.ttl +++ b/demo/northwind-traders/employees.ttl @@ -10,19 +10,33 @@ <> a dh:Container ; dct:title "Employees" ; rdf:_1 <#employee-intro> ; - rdf:_2 <#sales-by-employee-block> ; - rdf:_3 <#select-employees> . + rdf:_2 <#select-employees> ; + rdf:_3 <#performance-intro> ; + rdf:_4 <#sales-by-employee-block> . # Intro XHTML <#employee-intro> a ldh:XHTML ; rdf:value """
-

Employee performance

-

Track employee sales performance and order processing metrics. Understanding individual - contributions helps recognize top performers, identify coaching opportunities, and optimize territory assignments.

+
+

The team

+

Everyone on the Northwind payroll, who they report to and the territories they cover. Open a + record to see the orders they booked.

+
+
"""^^rdf:XMLLiteral . + + # Performance intro + <#performance-intro> a ldh:XHTML ; + rdf:value """
+
+

Sales performance

+

Revenue booked per sales rep — the basis for recognising top performers and rebalancing + territory assignments.

+
"""^^rdf:XMLLiteral . # Sales by employee chart (wrapped in ldh:Object per ContentMode contract) <#sales-by-employee-block> a ldh:Object ; + dct:title "Sales by employee" ; rdf:value <#sales-by-employee> . <#sales-by-employee> a ldh:ResultSetChart ; @@ -41,9 +55,7 @@ WHERE { GRAPH ?orderGraph { ?order schema:broker ?employee ; schema:orderedItem ?orderItem . - ?orderItem schema:orderQuantity ?quantity ; - schema:price ?price . - BIND(?quantity * ?price AS ?sale) + ?orderItem schema:price ?sale . } GRAPH ?employeeGraph { ?employee schema:givenName ?givenName ; @@ -56,6 +68,7 @@ ORDER BY DESC(?totalSales)""" . # Object block (references the view) <#select-employees> a ldh:Object ; + dct:title "All employees" ; rdf:value <#select-employees-view> . # View block (references the query, uses GridMode) diff --git a/demo/northwind-traders/orders.ttl b/demo/northwind-traders/orders.ttl index e9f32e5..e5c570d 100644 --- a/demo/northwind-traders/orders.ttl +++ b/demo/northwind-traders/orders.ttl @@ -10,15 +10,32 @@ <> a dh:Container ; dct:title "Orders" ; rdf:_1 <#orders-intro> ; - rdf:_2 <#orders-over-time-block> ; - rdf:_3 <#geographic-intro> ; - rdf:_4 <#orders-by-country-block> ; - rdf:_5 <#order-trends-summary> ; - rdf:_6 <#sales-by-region-per-year-block> ; - rdf:_7 <#select-orders> . + rdf:_2 <#select-orders> ; + rdf:_3 <#analytics-intro> ; + rdf:_4 <#orders-over-time-block> ; + rdf:_5 <#fulfilment-intro> ; + rdf:_6 <#days-to-ship-block> ; + rdf:_7 <#on-time-block> ; + rdf:_8 <#carrier-intro> ; + rdf:_9 <#late-by-carrier-block> ; + rdf:_10 <#revenue-by-carrier-block> . + + # Intro XHTML + <#orders-intro> a ldh:XHTML ; + rdf:value """
+
+

The order book

+

Every order links a customer, the sales rep who booked it, the shipper that delivered it and the + products it contains. Orders are listed newest first with their status — sort any column or + filter with the facets. Open one for its full master–detail view: line items with their + quantities and line totals, the catalogue price each was struck against, the delivery + and its address, and the people and companies involved.

+
+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-orders> a ldh:Object ; + dct:title "All orders" ; rdf:value <#select-orders-view> . # View block (references the query, uses TableMode) @@ -38,21 +55,24 @@ schema:identifier ?orderID ; schema:orderDate ?orderDate ; schema:customer ?customer ; - schema:broker ?employee + schema:broker ?employee ; + schema:orderStatus ?status } } - ORDER BY ?orderID""" . + ORDER BY DESC(?orderDate)""" . - # Intro XHTML - <#orders-intro> a ldh:XHTML ; + # Analytics intro + <#analytics-intro> a ldh:XHTML ; rdf:value """
-

Order analytics

-

Track order volume, geographic distribution, and temporal trends. Understanding order patterns - helps optimize inventory, predict demand, and identify growth opportunities across different markets.

+
+

Order analytics

+

Beyond the individual records, how has order volume developed over time?

+
"""^^rdf:XMLLiteral . # Orders over time chart (wrapped in ldh:Object per ContentMode contract) <#orders-over-time-block> a ldh:Object ; + dct:title "Orders per month" ; rdf:value <#orders-over-time> . <#orders-over-time> a ldh:ResultSetChart ; @@ -77,86 +97,195 @@ WHERE { GROUP BY ?month ORDER BY ?month""" . - # Geographic intro - <#geographic-intro> a ldh:XHTML ; + # Fulfilment intro + <#fulfilment-intro> a ldh:XHTML ; rdf:value """
-

Geographic distribution

-

Orders span multiple countries and regions. Analyzing geographic patterns reveals market penetration - and helps identify expansion opportunities or regional challenges.

+
+

From order to doorstep

+

Every order records three dates: the day it was placed, the day the customer needed it + by, and — once the parcel actually leaves the warehouse — the day it was dispatched. + Two questions fall out of those. How quickly does Northwind get an order out of the + door, and how often does it beat the date it promised? Orders still waiting to ship + are left out of the second chart, since they have no outcome yet.

+
"""^^rdf:XMLLiteral . - # Orders by country chart (wrapped in ldh:Object per ContentMode contract) - <#orders-by-country-block> a ldh:Object ; - rdf:value <#orders-by-country> . + # Days from order to dispatch + <#days-to-ship-block> a ldh:Object ; + dct:title "Days to ship" ; + rdf:value <#days-to-ship> . - <#orders-by-country> a ldh:ResultSetChart ; - dct:title "Orders by country" ; - spin:query <#orders-by-country-query> ; - ldh:chartType ; - ldh:categoryVarName "country" ; - ldh:seriesVarName "orderCount" . + <#days-to-ship> a ldh:ResultSetChart ; + dct:title "Days to ship" ; + spin:query <#days-to-ship-query> ; + ldh:chartType ; + ldh:categoryVarName "month" ; + ldh:seriesVarName "daysToShip" . - <#orders-by-country-query> a sp:Select ; - dct:title "Orders by country" ; + <#days-to-ship-query> a sp:Select ; + dct:title "Days to ship by month" ; sp:text """PREFIX schema: +PREFIX xsd: -SELECT ?country (COUNT(DISTINCT ?order) AS ?orderCount) +SELECT ?month (ROUND(AVG(?days) * 10) / 10 AS ?daysToShip) WHERE { GRAPH ?orderGraph { - ?order schema:orderDelivery ?delivery . - ?delivery schema:deliveryAddress ?address . - ?address schema:addressCountry ?country . + ?order a schema:Order ; + schema:orderDate ?orderDate ; + schema:orderDelivery ?delivery . + # the dispatch is a DeliveryEvent leg of the parcel's journey, so orders that + # never shipped simply have no event and drop out of the average + ?delivery schema:deliveryStatus ?shipment . + ?shipment schema:startDate ?shippedDate . + BIND(SUBSTR(STR(?orderDate), 1, 7) AS ?month) + BIND((?shippedDate - ?orderDate) / xsd:dayTimeDuration("P1D") AS ?days) + } + # The order book stops part-way through its final month, which would render as a + # cliff rather than a trend. Drop that month — derived from the data, not pinned + # to a date, so it stays correct if the dataset grows. + { + SELECT (SUBSTR(STR(MAX(?anyDate)), 1, 7) AS ?partialMonth) + WHERE { + GRAPH ?g { ?anyOrder a schema:Order ; schema:orderDate ?anyDate } + } + } + FILTER (?month < ?partialMonth) +} +GROUP BY ?month +ORDER BY ?month""" . + + # On-time shipping rate + <#on-time-block> a ldh:Object ; + dct:title "Shipped on time" ; + rdf:value <#on-time> . + + <#on-time> a ldh:ResultSetChart ; + dct:title "Shipped on time" ; + spin:query <#on-time-query> ; + ldh:chartType ; + ldh:categoryVarName "month" ; + ldh:seriesVarName "onTimePercent" . + + <#on-time-query> a sp:Select ; + dct:title "On-time rate by month" ; + sp:text """PREFIX schema: + +SELECT ?month (ROUND(SUM(IF(?status = schema:OrderDelivered, 1, 0)) * 1000 / COUNT(?order)) / 10 AS ?onTimePercent) +WHERE { + GRAPH ?orderGraph { + ?order a schema:Order ; + schema:orderDate ?orderDate ; + schema:orderStatus ?status . + BIND(SUBSTR(STR(?orderDate), 1, 7) AS ?month) + } + # an order still awaiting dispatch has no outcome yet - counting it as a miss would + # drag the rate down for the months at the end of the book + FILTER (?status != schema:OrderProcessing) + # The order book stops part-way through its final month, which would render as a + # cliff rather than a trend. Drop that month — derived from the data, not pinned + # to a date, so it stays correct if the dataset grows. + { + SELECT (SUBSTR(STR(MAX(?anyDate)), 1, 7) AS ?partialMonth) + WHERE { + GRAPH ?g { ?anyOrder a schema:Order ; schema:orderDate ?anyDate } + } } + FILTER (?month < ?partialMonth) } -GROUP BY ?country -ORDER BY DESC(?orderCount) -LIMIT 10""" . - - # Chart block (wrapped in ldh:Object per ContentMode contract) - <#sales-by-region-per-year-block> a ldh:Object ; - rdf:value <#sales-by-region-per-year> . - - <#sales-by-region-per-year> a ldh:ResultSetChart ; - dct:title "Sales by region per year" ; - spin:query <#sales-by-regions-by-year-query> ; - ldh:chartType ; - ldh:categoryVarName "year" ; - ldh:seriesVarName "regionName" ; - ldh:seriesVarName "totalSales" . - - # Order trends summary - <#order-trends-summary> a ldh:XHTML ; +GROUP BY ?month +ORDER BY ?month""" . + + # Carrier intro + <#carrier-intro> a ldh:XHTML ; rdf:value """
-

Regional performance

-

The table below shows sales performance broken down by region and year, enabling comparison - of regional growth patterns and seasonal variations.

+
+

Who carries the freight

+

Three carriers deliver every order, which makes them worth comparing on two + axes. How often does a carrier miss the date the customer was promised, and + how much revenue does each one move month to month? The first is a question + about reliability, the second about how the shipping mix has shifted.

+
"""^^rdf:XMLLiteral . - # Chart SELECT query - <#sales-by-regions-by-year-query> a sp:Select ; - dct:title "Sales by region per year" ; - sp:text """PREFIX schema: -PREFIX xsd: - -SELECT DISTINCT ?year ?regionName (SUM(?sale) AS ?totalSales) -WHERE - { GRAPH ?orderGraph - { ?order schema:orderDate ?orderDate ; - schema:broker ?employee ; - schema:orderedItem ?orderItem - BIND(year(xsd:dateTime(?orderDate)) AS ?year) - ?orderItem schema:orderedItem ?product ; - schema:orderQuantity ?quantity ; - schema:price ?price - BIND(( ?quantity * ?price ) AS ?sale) - } - GRAPH ?employeeGraph - { ?employee schema:areaServed ?territory } - GRAPH ?territoryGraph - { ?territory schema:containedInPlace ?region } - GRAPH ?regionGraph - { ?region schema:name ?regionName } - } -GROUP BY ?year ?regionName -ORDER BY DESC(?year) ?regionName -LIMIT 100""" . + # Late shipments by carrier + <#late-by-carrier-block> a ldh:Object ; + dct:title "Late shipments by carrier" ; + rdf:value <#late-by-carrier> . + + <#late-by-carrier> a ldh:ResultSetChart ; + dct:title "Late shipments by carrier" ; + spin:query <#late-by-carrier-query> ; + ldh:chartType ; + ldh:categoryVarName "carrier" ; + ldh:seriesVarName "latePercent" . + + <#late-by-carrier-query> a sp:Select ; + dct:title "Late shipments by carrier" ; + sp:text """PREFIX schema: + +SELECT ?carrier (ROUND(SUM(IF(?status = schema:OrderProblem, 1, 0)) * 1000 / COUNT(?order)) / 10 AS ?latePercent) +WHERE { + GRAPH ?orderGraph { + ?order a schema:Order ; + schema:orderStatus ?status ; + schema:orderDelivery ?delivery . + ?delivery schema:provider ?shipper . + } + GRAPH ?shipperGraph { + ?shipper schema:legalName ?carrier . + } + # not yet dispatched is not the same as delivered late + FILTER (?status != schema:OrderProcessing) +} +GROUP BY ?carrier +ORDER BY DESC(?latePercent)""" . + + # Revenue by carrier - the one multi-series chart in the app. Each ldh:seriesVarName + # names its own measure column (wide format); the query pivots with conditional + # aggregation. Three carriers is a small fixed set, so naming them here is tolerable - + # a fourth would silently not appear. + <#revenue-by-carrier-block> a ldh:Object ; + dct:title "Revenue by carrier" ; + rdf:value <#revenue-by-carrier> . + + <#revenue-by-carrier> a ldh:ResultSetChart ; + dct:title "Revenue by carrier" ; + spin:query <#revenue-by-carrier-query> ; + ldh:chartType ; + ldh:categoryVarName "month" ; + ldh:seriesVarName "speedyExpress" ; + ldh:seriesVarName "unitedPackage" ; + ldh:seriesVarName "federalShipping" . + + <#revenue-by-carrier-query> a sp:Select ; + dct:title "Revenue by carrier" ; + sp:text """PREFIX schema: + +SELECT ?month + (ROUND(SUM(IF(?carrier = "Speedy Express", ?lineTotal, 0))) AS ?speedyExpress) + (ROUND(SUM(IF(?carrier = "United Package", ?lineTotal, 0))) AS ?unitedPackage) + (ROUND(SUM(IF(?carrier = "Federal Shipping", ?lineTotal, 0))) AS ?federalShipping) +WHERE { + GRAPH ?orderGraph { + ?order a schema:Order ; + schema:orderDate ?orderDate ; + schema:orderDelivery ?delivery ; + schema:orderedItem ?orderItem . + ?orderItem schema:price ?lineTotal . + ?delivery schema:provider ?shipper . + } + GRAPH ?shipperGraph { + ?shipper schema:legalName ?carrier . + } + BIND(SUBSTR(STR(?orderDate), 1, 7) AS ?month) + # Drop the final, incomplete month - derived from the data rather than pinned to a date. + { + SELECT (SUBSTR(STR(MAX(?anyDate)), 1, 7) AS ?partialMonth) + WHERE { + GRAPH ?g { ?anyOrder a schema:Order ; schema:orderDate ?anyDate } + } + } + FILTER (?month < ?partialMonth) +} +GROUP BY ?month +ORDER BY ?month""" . diff --git a/demo/northwind-traders/orders/order_details.rq b/demo/northwind-traders/orders/order_details.rq index 30771e9..4533114 100644 --- a/demo/northwind-traders/orders/order_details.rq +++ b/demo/northwind-traders/orders/order_details.rq @@ -7,13 +7,32 @@ CONSTRUCT { GRAPH ?graph { + ?graph foaf:topic ?orderItem, ?listPrice . + ?order schema:orderedItem ?orderItem . - ?orderItem a schema:OrderItem ; + # schema:OrderItem carries no price in schema.org - its only properties are + # orderedItem, orderQuantity, orderItemNumber, orderItemStatus and orderDelivery. + # Prices belong to schema:Offer, so the line is both: the item that was ordered + # and the offer that was accepted for it. schema:Offer's own schema:price is the + # price of the offer as a whole, i.e. what this line came to. + ?orderItem a schema:OrderItem, schema:Offer ; schema:orderedItem ?product ; + schema:itemOffered ?product ; schema:orderQuantity ?quantity ; - schema:discount ?discount ; - schema:price ?price . + schema:price ?lineTotal ; + schema:priceCurrency "USD" ; + schema:priceSpecification ?listPrice . + + # Northwind's per-line discount rate has no home in schema.org - schema:discount + # is an Order-level property. Nothing is lost by dropping it: the undiscounted + # unit price below, the quantity above and the line total pin the rate exactly, + # and unlike a stored rate they cannot disagree with each other. + ?listPrice a schema:UnitPriceSpecification ; + dct:title "List price" ; + schema:priceType schema:ListPrice ; + schema:price ?price ; + schema:priceCurrency "USD" . } } WHERE @@ -27,8 +46,10 @@ WHERE BIND(uri(concat(str($base), "orders/", encode_for_uri(?orderID), "/")) AS ?graph) BIND(uri(concat(str(?graph), "#this")) AS ?order) BIND(uri(concat(str(?graph), "#", STRUUID())) AS ?orderItem) + BIND(uri(concat(str(?orderItem), "-list-price")) AS ?listPrice) BIND(uri(concat(str($base), "products/", encode_for_uri(?productID), "/#this")) AS ?product) BIND (STRDT(?unitPrice, xsd:float) AS ?price) BIND (STRDT(?quantityString, xsd:integer) AS ?quantity) BIND (STRDT(?discountString, xsd:float) AS ?discount) + BIND (STRDT(str(round(?quantity * ?price * (1 - ?discount) * 100) / 100), xsd:float) AS ?lineTotal) } diff --git a/demo/northwind-traders/orders/orders.rq b/demo/northwind-traders/orders/orders.rq index 98fbab5..9db16ff 100644 --- a/demo/northwind-traders/orders/orders.rq +++ b/demo/northwind-traders/orders/orders.rq @@ -9,7 +9,8 @@ CONSTRUCT { ?graph dct:title ?orderID ; foaf:primaryTopic ?order ; - foaf:topic ?orderDelivery, ?deliveryAddress . + foaf:topic ?orderDelivery, ?deliveryAddress, ?shipment, + ?shippingOffer, ?freightCharge . ?order a schema:Order ; schema:identifier ?orderID ; @@ -17,22 +18,48 @@ CONSTRUCT schema:customer ?customer ; schema:broker ?employee ; schema:orderDate ?orderDate ; + schema:orderStatus ?status ; + schema:acceptedOffer ?shippingOffer ; schema:orderDelivery ?orderDelivery . ?orderDelivery a schema:ParcelDelivery ; foaf:page ?graph ; + schema:partOfOrder ?order ; schema:expectedArrivalUntil ?requiredDate ; - # ?shippedDate ; + schema:deliveryStatus ?shipment ; schema:deliveryAddress ?deliveryAddress ; schema:provider ?shipper . + # schema:ParcelDelivery has no price property either. A delivery charge is a + # schema:DeliveryChargeSpecification, and the only way into a PriceSpecification + # is schema:priceSpecification from an Offer - so the freight the customer + # accepted is spelled out as exactly that. + ?shippingOffer a schema:Offer ; + foaf:page ?graph ; + dct:title "Shipping" ; + schema:priceSpecification ?freightCharge . + + ?freightCharge a schema:DeliveryChargeSpecification ; + foaf:page ?graph ; + dct:title "Freight" ; + schema:price ?freight ; + schema:priceCurrency "USD" ; + schema:appliesToDeliveryMethod schema:ParcelService . + + # schema:ParcelDelivery has no date of its own for "when did this leave the warehouse". + # The vocabulary models the journey as a series of schema:DeliveryEvent legs hanging + # off schema:deliveryStatus; Northwind knows exactly one leg, the dispatch. + ?shipment a schema:DeliveryEvent ; + foaf:page ?graph ; + dct:title ?shipmentTitle ; + schema:startDate ?shippedDate . + ?deliveryAddress a schema:PostalAddress ; foaf:page ?graph ; schema:addressCountry ?shipCountry ; schema:addressLocality ?shipCity ; schema:postalCode ?shipPostalCode ; schema:streetAddress ?shipAddress ; - schema:location ?deliveryLocation ; schema:addressRegion ?shipRegion . } } @@ -48,26 +75,42 @@ WHERE <#shipName> ?shipName ; <#shipAddress> ?shipAddress ; <#shipCity> ?shipCity ; - <#shipPostalCode> ?shipPostalCode ; <#shipCountry> ?shipCountry . + # ?shipment is bound inside the OPTIONAL so that the 21 orders that were never + # dispatched get no DeliveryEvent at all, rather than an empty one. It is built from + # $base (a constant, substituted before execution) and a re-matched ?orderID rather + # than from ?graph: an OPTIONAL group is evaluated standalone, so variables BINDed in + # the enclosing group are not in scope inside it. OPTIONAL { - ?order_row <#shippedDate> ?shippedDateString + ?order_row <#orderID> ?orderID ; + <#shippedDate> ?shippedDateString . BIND(strdt(?shippedDateString, xsd:date) AS ?shippedDate) + BIND(uri(concat(str($base), "orders/", encode_for_uri(?orderID), "/#shipment")) AS ?shipment) + BIND(concat("Shipped ", ?shippedDateString) AS ?shipmentTitle) } OPTIONAL { ?order_row <#shipRegion> ?shipRegion } + # 19 orders carry no postal code; requiring one here dropped them from the import + OPTIONAL { + ?order_row <#shipPostalCode> ?shipPostalCode + } BIND(uri(concat(str($base), "orders/")) AS ?container) BIND(uri(concat(str(?container), encode_for_uri(?orderID), "/")) AS ?graph) BIND(uri(concat(str(?graph), "#this")) AS ?order) BIND(uri(concat(str(?graph), "#delivery")) AS ?orderDelivery) BIND(uri(concat(str(?graph), "#delivery-address")) AS ?deliveryAddress) + BIND(uri(concat(str(?graph), "#shipping")) AS ?shippingOffer) + BIND(uri(concat(str(?graph), "#freight")) AS ?freightCharge) BIND(uri(concat(str($base), "employees/", encode_for_uri(?employeeID), "/#this")) AS ?employee) BIND(uri(concat(str($base), "customers/", encode_for_uri(?customerID), "/#this")) AS ?customer) BIND(uri(concat(str($base), "shippers/", encode_for_uri(?shipVia), "/#this")) AS ?shipper) BIND(strdt(?orderDateString, xsd:date) AS ?orderDate) BIND(strdt(?requiredDateString, xsd:date) AS ?requiredDate) BIND(strdt(?freightString, xsd:float) AS ?freight) + BIND(IF(BOUND(?shippedDate), + IF(?shippedDate > ?requiredDate, schema:OrderProblem, schema:OrderDelivered), + schema:OrderProcessing) AS ?status) } diff --git a/demo/northwind-traders/products.ttl b/demo/northwind-traders/products.ttl index 84ba577..3397a25 100644 --- a/demo/northwind-traders/products.ttl +++ b/demo/northwind-traders/products.ttl @@ -10,24 +10,35 @@ <> a dh:Container ; dct:title "Products" ; rdf:_1 <#products-intro> ; - rdf:_2 <#top-selling-products-block> ; - rdf:_3 <#category-analysis-intro> ; - rdf:_4 <#revenue-by-category-block> ; - rdf:_5 <#products-by-supplier-block> ; - rdf:_6 <#supplier-context> ; - rdf:_7 <#select-products> . + rdf:_2 <#select-products> ; + rdf:_3 <#performance-intro> ; + rdf:_4 <#top-selling-products-block> ; + rdf:_5 <#supplier-context> ; + rdf:_6 <#products-by-supplier-block> . # Intro XHTML <#products-intro> a ldh:XHTML ; rdf:value """
-

Product analytics

-

Analyze product performance, category distribution, and supplier relationships. - The Northwind catalog includes products across multiple categories from various suppliers, - each with unique pricing and inventory characteristics.

+
+

The catalogue

+

Every product Northwind sells, with its category, supplier and unit price. Open one to see the + orders it appears in and the supplier behind it.

+
+
"""^^rdf:XMLLiteral . + + # Performance intro + <#performance-intro> a ldh:XHTML ; + rdf:value """
+
+

Product performance

+

Which lines actually carry the revenue? Category-level breakdowns live under + Categories.

+
"""^^rdf:XMLLiteral . # Chart block (wrapped in ldh:Object per ContentMode contract) <#top-selling-products-block> a ldh:Object ; + dct:title "Top selling products" ; rdf:value <#top-selling-products> . <#top-selling-products> a ldh:ResultSetChart ; @@ -47,9 +58,7 @@ WHERE { GRAPH ?orderGraph { ?order schema:orderedItem ?orderItem . ?orderItem schema:orderedItem ?product ; - schema:orderQuantity ?quantity ; - schema:price ?price - BIND (?quantity * ?price AS ?sale) + schema:price ?sale } GRAPH ?productGraph { ?product schema:name ?productName } @@ -58,50 +67,9 @@ GROUP BY ?product ?productName ORDER BY DESC(?totalSales) LIMIT 5""" . - # Category analysis intro - <#category-analysis-intro> a ldh:XHTML ; - rdf:value """
-

Category performance

-

Understanding how products and revenue are distributed across categories helps identify - market strengths and opportunities. The following charts break down revenue and product count by category.

-
"""^^rdf:XMLLiteral . - - # Revenue by category chart (wrapped in ldh:Object per ContentMode contract) - <#revenue-by-category-block> a ldh:Object ; - rdf:value <#revenue-by-category> . - - <#revenue-by-category> a ldh:ResultSetChart ; - dct:title "Revenue by category" ; - spin:query <#category-revenue-query> ; - ldh:chartType ; - ldh:categoryVarName "categoryName" ; - ldh:seriesVarName "revenue" . - - <#category-revenue-query> a sp:Select ; - dct:title "Category revenue" ; - sp:text """PREFIX schema: - -SELECT ?categoryName (SUM(?sale) AS ?revenue) -WHERE { - GRAPH ?orderGraph { - ?order schema:orderedItem ?orderItem . - ?orderItem schema:orderedItem ?product ; - schema:orderQuantity ?quantity ; - schema:price ?price . - BIND(?quantity * ?price AS ?sale) - } - GRAPH ?productGraph { - ?product schema:category ?category . - } - GRAPH ?categoryGraph { - ?category schema:name ?categoryName . - } -} -GROUP BY ?category ?categoryName -ORDER BY DESC(?revenue)""" . - # Products by supplier chart (wrapped in ldh:Object per ContentMode contract) <#products-by-supplier-block> a ldh:Object ; + dct:title "Products by supplier" ; rdf:value <#products-by-supplier> . <#products-by-supplier> a ldh:ResultSetChart ; @@ -122,7 +90,7 @@ WHERE { schema:provider ?supplier . } GRAPH ?supplierGraph { - ?supplier schema:name ?supplierName . + ?supplier schema:legalName ?supplierName . } } GROUP BY ?supplier ?supplierName @@ -132,13 +100,16 @@ LIMIT 10""" . # Supplier context <#supplier-context> a ldh:XHTML ; rdf:value """
-

Supplier relationships

-

Northwind Traders works with suppliers worldwide to source products. The distribution of products - across suppliers shows which partnerships are most productive and may indicate supply chain dependencies.

+
+

Supplier relationships

+

Northwind sources from suppliers worldwide. How the catalogue spreads across them shows which + partnerships carry the range — and where the supply chain concentrates.

+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-products> a ldh:Object ; + dct:title "All products" ; rdf:value <#select-products-view> . # View block (references the query, uses TableMode) diff --git a/demo/northwind-traders/products/products.rq b/demo/northwind-traders/products/products.rq index 1f482db..fa0000a 100644 --- a/demo/northwind-traders/products/products.rq +++ b/demo/northwind-traders/products/products.rq @@ -8,7 +8,8 @@ CONSTRUCT GRAPH ?graph { ?graph dct:title ?productName ; - foaf:primaryTopic ?product . + foaf:primaryTopic ?product ; + foaf:topic ?offer . ?product a schema:Product ; schema:identifier ?productID ; @@ -17,11 +18,21 @@ CONSTRUCT schema:provider ?supplier ; schema:category ?category ; schema:description ?quantityPerUnit ; - schema:price ?unitPrice . + schema:offers ?offer . # ?unitsInStock ; # ?unitsOnOrder ; # ?reorderLevel ; # ?discontinued - schema:Discontinued + + # schema:Product has no price of its own - schema:price belongs to schema:Offer, + # which a product reaches through schema:offers. This is the catalogue price; + # what a given order line was actually billed at lives on that line's own offer. + ?offer a schema:Offer ; + foaf:page ?graph ; + dct:title "List price" ; + schema:itemOffered ?product ; + schema:price ?unitPrice ; + schema:priceCurrency "USD" . } } WHERE @@ -40,6 +51,7 @@ WHERE BIND (uri(concat(str($base), "products/")) AS ?container) BIND(uri(concat(str(?container), encode_for_uri(?productID), "/")) AS ?graph) BIND(uri(concat(str(?graph), "#this")) AS ?product) + BIND(uri(concat(str(?graph), "#offer")) AS ?offer) BIND(uri(concat(str($base), "suppliers/", encode_for_uri(?supplierID), "/#this")) AS ?supplier) BIND(uri(concat(str($base), "categories/", encode_for_uri(?categoryID), "/#this")) AS ?category) BIND (STRDT(?unitPriceString, xsd:float) AS ?unitPrice) diff --git a/demo/northwind-traders/regions.ttl b/demo/northwind-traders/regions.ttl index f9f8000..250531e 100644 --- a/demo/northwind-traders/regions.ttl +++ b/demo/northwind-traders/regions.ttl @@ -8,10 +8,22 @@ # Main container document <> a dh:Container ; dct:title "Regions" ; - rdf:_1 <#select-regions> . + rdf:_1 <#regions-intro> ; + rdf:_2 <#select-regions> . + + # Intro XHTML + <#regions-intro> a ldh:XHTML ; + rdf:value """
+
+

Sales regions

+

The top level of the sales geography. Each region contains the + territories that reps are assigned to.

+
+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-regions> a ldh:Object ; + dct:title "All regions" ; rdf:value <#select-regions-view> . # View block (references the query, no mode specified - uses default) diff --git a/demo/northwind-traders/root.ttl b/demo/northwind-traders/root.ttl index 4bb26a9..6a041eb 100644 --- a/demo/northwind-traders/root.ttl +++ b/demo/northwind-traders/root.ttl @@ -12,35 +12,28 @@ <> a def:Root ; dct:title "Northwind Traders" ; dct:description "Knowledge Graph representation of the Northwind Traders sample database" ; - rdf:_1 <#page-header> ; - rdf:_2 <#overview-intro> ; - rdf:_3 <#sales-trend-block> ; - rdf:_4 <#revenue-by-country-block> ; - rdf:_5 <#top-selling-products> ; - rdf:_6 <#top-manager-header> ; - rdf:_7 <#top-manager> ; - rdf:_8 <#navigation-prompt> ; - rdf:_9 <#select-children> . - -<#page-header> a ldh:XHTML ; - rdf:value """
- -

Features include CSV data imports, SPARQL-based charts, faceted search and related results (parallax navigation) as well as - rich documents composed of structured content backed by RDF Knowledge Graph data.

-

This application was created on LinkedDataHub using the low code paradigm: no programming was required, only scripts that - invoke LinkedDataHub's CLI commands.

-
"""^^rdf:XMLLiteral . + rdf:_1 <#overview-intro> ; + rdf:_2 <#sales-trend-block> ; + rdf:_3 <#margin-intro> ; + rdf:_4 <#discount-rate-block> ; + rdf:_5 <#revenue-by-country-block> ; + rdf:_6 <#top-selling-products> ; + rdf:_7 <#top-manager-header> ; + rdf:_8 <#top-manager> ; + rdf:_9 <#navigation-prompt> ; + rdf:_10 <#select-children> . <#overview-intro> a ldh:XHTML ; rdf:value """
-

Executive dashboard

-

Explore key business metrics including sales trends, geographic distribution, and product performance. All visualizations are generated from live SPARQL queries over the RDF Knowledge Graph.

+
+

Executive dashboard

+

Sales performance at a glance — how revenue is trending, what the discounting costs, + where the markets are and which products carry the mix.

+
"""^^rdf:XMLLiteral . <#sales-trend-block> a ldh:Object ; + dct:title "Monthly sales trend" ; rdf:value <#sales-trend> . <#sales-trend> a ldh:ResultSetChart ; @@ -60,16 +53,83 @@ WHERE { ?order a schema:Order ; schema:orderDate ?orderDate ; schema:orderedItem ?orderItem . + ?orderItem schema:price ?sale . + BIND(SUBSTR(STR(?orderDate), 1, 7) AS ?month) + } + # The order book stops part-way through its final month, which would render as a + # cliff rather than a trend. Drop that month — derived from the data, not pinned + # to a date, so it stays correct if the dataset grows. + { + SELECT (SUBSTR(STR(MAX(?anyDate)), 1, 7) AS ?partialMonth) + WHERE { + GRAPH ?g { ?anyOrder a schema:Order ; schema:orderDate ?anyDate } + } + } + FILTER (?month < ?partialMonth) +} +GROUP BY ?month +ORDER BY ?month""" . + +<#margin-intro> a ldh:XHTML ; + rdf:value """
+
+

What the deals cost

+

Revenue is what was billed, not what was listed. Every order line keeps the catalogue + price it started from alongside the price it actually went out at, so the gap between + them is what the sales rep gave away to win the deal. Measured against gross, that gap + swings between roughly 4% and 11% month to month — a commercial lever in its own + right, and one that never shows up in a revenue chart.

+
+
"""^^rdf:XMLLiteral . + +<#discount-rate-block> a ldh:Object ; + dct:title "Discount rate" ; + rdf:value <#discount-rate> . + +<#discount-rate> a ldh:ResultSetChart ; + dct:title "Discount rate" ; + spin:query <#discount-by-month-query> ; + ldh:chartType ; + ldh:categoryVarName "month" ; + ldh:seriesVarName "discountPercent" . + +<#discount-by-month-query> a sp:Select ; + dct:title "Discount rate by month" ; + sp:text """PREFIX schema: + +SELECT ?month (ROUND(SUM(?discount) * 1000 / SUM(?gross)) / 10 AS ?discountPercent) +WHERE { + GRAPH ?orderGraph { + ?order a schema:Order ; + schema:orderDate ?orderDate ; + schema:orderedItem ?orderItem . + # the line's own price is net of discount; the catalogue price it was struck + # against hangs off it as a ListPrice specification ?orderItem schema:orderQuantity ?quantity ; - schema:price ?price . - BIND(?quantity * ?price AS ?sale) + schema:price ?net ; + schema:priceSpecification ?listPrice . + ?listPrice schema:priceType schema:ListPrice ; + schema:price ?unitPrice . BIND(SUBSTR(STR(?orderDate), 1, 7) AS ?month) + BIND(?quantity * ?unitPrice AS ?gross) + BIND(?gross - ?net AS ?discount) + } + # The order book stops part-way through its final month, which would render as a + # cliff rather than a trend. Drop that month — derived from the data, not pinned + # to a date, so it stays correct if the dataset grows. + { + SELECT (SUBSTR(STR(MAX(?anyDate)), 1, 7) AS ?partialMonth) + WHERE { + GRAPH ?g { ?anyOrder a schema:Order ; schema:orderDate ?anyDate } + } } + FILTER (?month < ?partialMonth) } GROUP BY ?month ORDER BY ?month""" . <#revenue-by-country-block> a ldh:Object ; + dct:title "Revenue by country" ; rdf:value <#revenue-by-country> . <#revenue-by-country> a ldh:ResultSetChart ; @@ -88,11 +148,9 @@ WHERE { GRAPH ?orderGraph { ?order schema:orderedItem ?orderItem ; schema:orderDelivery ?delivery . - ?orderItem schema:orderQuantity ?quantity ; - schema:price ?price . + ?orderItem schema:price ?sale . ?delivery schema:deliveryAddress ?address . ?address schema:addressCountry ?country . - BIND(?quantity * ?price AS ?sale) } } GROUP BY ?country @@ -100,22 +158,29 @@ ORDER BY DESC(?revenue) LIMIT 10""" . <#top-selling-products> a ldh:Object ; + dct:title "Top selling products" ; rdf:value . <#top-manager-header> a ldh:XHTML ; rdf:value """
-

Top manager

-

Meet the top manager of Northwind Traders.

+
+

Sales leadership

+

The Vice President of Sales, with the territories, reports and order history attached to the role.

+
"""^^rdf:XMLLiteral . <#top-manager> a ldh:Object ; + dct:title "Vice President, Sales" ; rdf:value . <#navigation-prompt> a ldh:XHTML ; rdf:value """
-

Explore more

-

Dive deeper into detailed analytics by exploring the containers below. Each section provides comprehensive insights into products, orders, customers, employees, and more.

+
+

Browse the business

+

Open Orders for the order book and its master–detail line items, or go straight to products, customers, employees and the reference data behind them.

+
"""^^rdf:XMLLiteral . <#select-children> a ldh:Object ; + dct:title "Sections" ; rdf:value ldh:ChildrenView . diff --git a/demo/northwind-traders/shippers.ttl b/demo/northwind-traders/shippers.ttl index 2a5b1b9..26ca03e 100644 --- a/demo/northwind-traders/shippers.ttl +++ b/demo/northwind-traders/shippers.ttl @@ -8,10 +8,21 @@ # Main container document <> a dh:Container ; dct:title "Shippers" ; - rdf:_1 <#select-shippers> . + rdf:_1 <#shippers-intro> ; + rdf:_2 <#select-shippers> . + + # Intro XHTML + <#shippers-intro> a ldh:XHTML ; + rdf:value """
+
+

Getting it there

+

The carriers that deliver Northwind's orders. Open one to see every delivery it handled.

+
+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-shippers> a ldh:Object ; + dct:title "All shippers" ; rdf:value <#select-shippers-view> . # View block (references the query, no mode specified - uses default) diff --git a/demo/northwind-traders/suppliers.ttl b/demo/northwind-traders/suppliers.ttl index 2f5b755..e8b2d7c 100644 --- a/demo/northwind-traders/suppliers.ttl +++ b/demo/northwind-traders/suppliers.ttl @@ -9,10 +9,22 @@ # Main container document <> a dh:Container ; dct:title "Suppliers" ; - rdf:_1 <#select-suppliers> . + rdf:_1 <#suppliers-intro> ; + rdf:_2 <#select-suppliers> . + + # Intro XHTML + <#suppliers-intro> a ldh:XHTML ; + rdf:value """
+
+

Where the goods come from

+

The companies Northwind buys from. Open a supplier to see everything it supplies to the + catalogue.

+
+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-suppliers> a ldh:Object ; + dct:title "All suppliers" ; rdf:value <#select-suppliers-view> . # View block (references the query, uses TableMode) diff --git a/demo/northwind-traders/territories.ttl b/demo/northwind-traders/territories.ttl index cb1575f..0489504 100644 --- a/demo/northwind-traders/territories.ttl +++ b/demo/northwind-traders/territories.ttl @@ -9,10 +9,22 @@ # Main container document <> a dh:Container ; dct:title "Territories" ; - rdf:_1 <#select-territories> . + rdf:_1 <#territories-intro> ; + rdf:_2 <#select-territories> . + + # Intro XHTML + <#territories-intro> a ldh:XHTML ; + rdf:value """
+
+

Sales territories

+

The territories reps cover, plotted on the map and grouped into + regions.

+
+
"""^^rdf:XMLLiteral . # Object block (references the view) <#select-territories> a ldh:Object ; + dct:title "All territories" ; rdf:value <#select-territories-view> . # View block (references the query, uses MapMode) diff --git a/demo/unesco-thesaurus/layout.xsl b/demo/unesco-thesaurus/layout.xsl deleted file mode 100644 index bea9ee2..0000000 --- a/demo/unesco-thesaurus/layout.xsl +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/about.ttl b/docs/about.ttl index c696636..0c94ade 100644 --- a/docs/about.ttl +++ b/docs/about.ttl @@ -2,7 +2,7 @@ @prefix rdf: . @prefix dh: . @prefix dct: . - + <> a dh:Item ; dct:title "About LinkedDataHub" ; dct:description "An RDF-native Knowledge Graph application platform" ; @@ -13,8 +13,9 @@

LinkedDataHub is a Knowledge Graph application platform by AtomGraph that fully exploits the federated features of RDF and SPARQL. It can also be used as an RDF-native content management platform.

-

LinkedDataHub is an open source project that has its roots as a Linked Data publishing framework. However, since the 3.x release we have focused on Linked Data consumption, as we consider +

LinkedDataHub is an open source project that has its roots as a Linked Data publishing framework. Over time we have focused on Linked Data consumption, as we consider publishing a solved problem but continue to see a shortage of user-friendly consumption tools.

+

A single LinkedDataHub instance hosts multiple dataspaces, each identified by its own (sub)domain and consisting of a pair of applications: the end-user application and its administration application on the admin. subdomain.

Low code application platform

LinkedDataHub is also a low code RDF Knowledge Graph application platform. It offers comprehensive development and management features:

@@ -22,10 +23,13 @@
  • rich UI component library that can be customized without writing program code
  • SPARQL-based templates for instance creation and editing forms (constructors)
  • SPIN and SHACL constraints for validation of incoming data
  • -
  • authentication using WebID and OpenID Connect
  • +
  • authentication using WebID and OpenID Connect (Google and ORCID)
  • access control using WAC authorizations
  • +
  • declarative packages that plug vocabulary support and custom UI into an application with a single triple
  • +
  • Git-backed document versioning with the standard Memento protocol for accessing the history
  • +
  • the ldh command line interface that automates the same HTTP API the UI uses
  • -

    Checkout the user guide on application building.

    +

    Follow the tutorial to build your first app, then see the user guide on application building for the extension layers.

    Federated knowledge management and collaboration system

    @@ -34,15 +38,16 @@

    As an RDF-native CMS, LinkedDataHub provides a number of features for end-users:

    @@ -50,8 +55,8 @@

    Architecturally LinkedDataHub is a read-write RDF Graph Store combined with a rich Linked Data/SPARQL client. LinkedDataHub does not persist RDF data itself but rather serves it from, and stores it in, a backing triplestore, which by default is the Apache Jena Fuseki.

    Every document in LinkedDataHub's dataspace is also a named graph in the Graph Store and has both RDF and HTML representations. The client is implemented using XSLT 3.0, a standard, declarative data transformation language. The same stylesheets run in two environments: on the server (Saxon-HE) to produce the initial HTML response, and in the browser (Saxon-JS 3 with Interactive XSLT) to render the layout and drive all subsequent navigation without full page reloads. It can connect to any Linked Data resource or SPARQL 1.1 endpoint.

    -

    Since version 3.x LinkedDataHub does not use the Linked Data Templates anymore. However they can still be used to publish Linked Data from SPARQL endpoints +

    LinkedDataHub no longer uses Linked Data Templates. However they can still be used to publish Linked Data from SPARQL endpoints using Processor.

    You can find the changelog here.

    -"""^^rdf:XMLLiteral . \ No newline at end of file +"""^^rdf:XMLLiteral . diff --git a/docs/get-started.ttl b/docs/get-started.ttl index 41b67da..17d8e98 100644 --- a/docs/get-started.ttl +++ b/docs/get-started.ttl @@ -14,6 +14,7 @@

    All the basics of LinkedDataHub. From installation to customizing the model and user interface.

    This guide will show how a LinkedDataHub application can be used to manage domain-specific RDF classes and instances. As an example, we will use SKOS concepts and concept schemes.

    Note that most management actions can also be performed using the CLI (Command Line Interface). Where applicable, the UI and CLI instructions are shown side by side. The UNESCO Thesaurus demo app demonstrates how SKOS vocabularies can be managed in LinkedDataHub.

    +

    New to LinkedDataHub? The tutorial builds a complete application, from an empty dataspace to a published app.

    Setup

    Setup is only required if you plan to run your own instance of LinkedDataHub. It consists of few steps, which involve creating a configuration file and running a @@ -24,7 +25,7 @@

    Get an account

    With LinkedDataHub, you obtain a WebID and use its client certificate for authentication. You can obtain the WebID either by setting up your own instance or signing up on - an existing instance. Alternatively, you can authenticate using your social login.

    + an existing instance. Alternatively, you can authenticate using your Google or ORCID login.

    Read instructions on getting an account.

    @@ -62,6 +63,10 @@

    User interface changes are done by adding and overriding templates in XSLT stylesheets.

    Follow a tutorial to change the layout: create an XSLT stylesheet and override a template.

    +
    +

    Where to next

    +

    The user guide covers the everyday tasks in more depth: editing content in place, managing dataspaces, installing packages, and working with version history. The reference describes every feature in full.

    +
    """^^rdf:XMLLiteral . <#select-children> a ldh:Object ; diff --git a/docs/get-started/get-an-account.ttl b/docs/get-started/get-an-account.ttl index bd5cf59..34421a0 100644 --- a/docs/get-started/get-an-account.ttl +++ b/docs/get-started/get-an-account.ttl @@ -49,8 +49,8 @@ -

    You'll need a PEM version of the certificate for use with the command line interface scripts. During setup, it is stored under - ssl/owner/cert.pem. If you got the certificate by email, you need to convert the PKCS12 file to PEM using OpenSSL.

    +

    The command line interface reads the PKCS12 (.p12) file directly, so no conversion is needed. During setup it is stored under ssl/owner/keystore.p12. + curl wants a PEM version instead — it is stored under ssl/owner/cert.pem, and a certificate received by email can be converted from PKCS12 to PEM using OpenSSL.

    Unlike most LinkedDataHub resources, your WebID profile will have public access as required by the protocol. Your email address will be hidden however.

    diff --git a/docs/get-started/request-access.ttl b/docs/get-started/request-access.ttl index bda8ddc..fc37d51 100644 --- a/docs/get-started/request-access.ttl +++ b/docs/get-started/request-access.ttl @@ -17,7 +17,7 @@

    Fortunately, LinkedDataHub makes it easy to notify the owner by issuing an access request. Navigate to the document you want to gain access to, click on the Request access button and submit the form that appears, as shown below. You can choose the desired access modes.

    - +

    Are you in? Then continue the get started guide or take a look at the UI overview.

    diff --git a/docs/get-started/setup.ttl b/docs/get-started/setup.ttl index 77b6a2a..88eb56e 100644 --- a/docs/get-started/setup.ttl +++ b/docs/get-started/setup.ttl @@ -16,7 +16,10 @@

    Start LinkedDataHub

    -

    This section assumes you will be running on your local machine, i.e. localhost. If you intend to run it on a different host, change the system base URI.

    +

    This section assumes you will be running on your local machine, i.e. localhost. If you intend to run it on a different host, change the system base URI.

    +
    +

    The administration application of each dataspace is served on the admin. subdomain — for the default setup that is https://admin.localhost:4443/. Browsers resolve *.localhost automatically and the generated server certificate covers the subdomain, so the local setup works out of the box. On any other host, make sure admin.<host> resolves in DNS and is covered by the server's TLS certificate (e.g. a wildcard certificate).

    +

    Prerequisites:

    • bash shell. It should be included by default on Linux. On Windows you can install the Windows Subsystem for Linux.
    • @@ -61,10 +64,11 @@ OWNER_COUNTRY_NAME=DK
    • (Optional) If you need to configure Bearer token authentication for SPARQL services, create secrets/credentials.trig with your service credentials. See the configuration reference for details.
    • Run this from command line: -
      docker-compose up --build
      +
      make up -- --build
      + (make up forwards its arguments to docker-compose up; the -- is needed before arguments starting with -.) LinkedDataHub will start and mount the following sub-folders:
        -
      1. data where the backend triplestores will persist RDF data
      2. +
      3. fuseki where the backend triplestores will persist RDF data
      4. datasets where the owner and secretary agent metadata is persisted
      5. ssl/owner and ssl/secretary where the owner and secretary SSL certificates and WebID public keys are persisted
      6. uploads where LinkedDataHub stores content-hashed file uploads
      7. @@ -78,7 +82,8 @@ OWNER_COUNTRY_NAME=DK

        Reset

        If you need to start fresh and wipe the existing setup, you can do that using:

        -
        sudo rm -rf data datasets uploads ssl && docker-compose down -v
        +
        make drop
        +

        It stops the services, removes the Docker volumes, and wipes the local datasets, fuseki, ssl and uploads folders.

        This will remove persisted RDF data, SSL keys, and uploaded files as well as the Docker volumes.

        diff --git a/docs/reference/administration/acl.ttl b/docs/reference/administration/acl.ttl index 86ddbf3..5865b82 100644 --- a/docs/reference/administration/acl.ttl +++ b/docs/reference/administration/acl.ttl @@ -50,21 +50,7 @@

        Agents

        An agent is a person or a software agent that can be authorized to have certain modes of access to certain applications.

        -
        -

        Groups

        A group is a named group of agents to which an authorization can @@ -127,6 +113,9 @@

        Public access authorization allows access for non-authenticated agents.

        +
        +

        The owner's and secretary's authorizations are stored under stable URIs (acl/authorizations/owner-webid/ and acl/authorizations/secretary-webid/), so reloading the admin dataset does not create duplicates.

        +

        Requests

        If access is denied due to missing authorization, the agent can ask for it by issuing a request to the application's @@ -137,4 +126,36 @@

        +
        +

        Management

        +
        +

        Command line interface

        + + + + + + + + + + + + + + + + + + + + + + + + + +
        ActionCLI command
        Create groupldh admin acl create-group
        Create authorizationldh admin acl create-authorization
        Add agent to groupldh admin acl add-agent-to-group
        Make a document publicldh admin acl make-public
        +
        +
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/administration/ontologies.ttl b/docs/reference/administration/ontologies.ttl index 53809de..b0644d8 100644 --- a/docs/reference/administration/ontologies.ttl +++ b/docs/reference/administration/ontologies.ttl @@ -15,8 +15,11 @@

    Ontologies are sets of domain concepts. The domain can span both documents (information resources) and abstract/physical things (non-information resources).

    Ontologies can import other ontologies, both user-defined - and system ones provided by LinkedDataHub. The imports are retrieved during application initialization, and the application's namespace ontology - becomes a transitive union, i.e. is merged with its imports and imports of the imports etc.

    + and system ones provided by LinkedDataHub. The application's namespace ontology is resolved as a native owl:imports union graph — + the transitive closure of its imports — which is cached per ontology URI. No inference is materialized: the terms are served exactly as declared.

    +

    Package ontologies join the application's imports closure automatically. They are derived from the application's + ldh:import declarations at ontology load time and declared as owl:imports of the namespace ontology, so they resolve + like any other import.

    Best practice: avoid using owl:imports with uploaded files

    Do not use owl:imports declarations that point to paths under uploads/. This can cause circular dependency deadlocks @@ -110,6 +113,8 @@ WHERE {} gets skolemized to a URI when submitted to the server.

    Note that classes inherit constructors from superclasses in runtime. Subclasses do not have to redefine constructor properties already found in superclass constructors, only additional properties.

    +

    Constructor instances are instantiated client-side: the constructors of the resource's types (including the superclass closure) are expanded + onto a single instance. For that to work, a constructor has to have an empty WHERE clause, as in the example above.

    Constraints

    @@ -123,6 +128,62 @@ WHERE {}

    Properties

    -

    LinkedDataHub allows definition of new properties.

    +

    LinkedDataHub allows definition of new properties. Like classes, properties are declared in the application's namespace ontology. + The expected values of a property on a class are described either with a SPIN property constraint (a mandatory-property check) + or with an OWL restriction (owl:allValuesFrom on the class), both of which the create/edit forms enforce.

    +
    +
    +

    Importing external vocabularies

    +

    The Import ontology action loads an external vocabulary and derives instance constructors for its classes. + Only the derived annotation ontology is persisted — the generated constructors plus an owl:imports of the canonical vocabulary + URI — and it is wired into the application's namespace ontology. The vocabulary itself resolves through its canonical URI as part of the imports closure.

    +
    +
    +

    Management

    +
    +

    Command line interface

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ActionCLI command
    Create ontologyldh admin ontologies create-ontology
    Import external vocabularyldh admin ontologies import-ontology
    Add ontology importldh admin add-ontology-import
    Clear ontology from cacheldh admin clear-ontology
    Create classldh admin ontologies add-class
    Add constructorldh admin ontologies add-constructor
    Add property constraintldh admin ontologies add-property-constraint
    Add restrictionldh admin ontologies add-restriction
    +
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/administration/packages.ttl b/docs/reference/administration/packages.ttl index b014acc..242e58b 100644 --- a/docs/reference/administration/packages.ttl +++ b/docs/reference/administration/packages.ttl @@ -12,10 +12,10 @@ rdf:value """

    Packages are reusable bundles of ontologies and stylesheets that provide vocabulary support with custom rendering for specific RDF vocabularies.

    -

    Version: Packages were introduced in LinkedDataHub 5.2.

    +

    Version: Packages were introduced in LinkedDataHub 5.2. Since 5.10 an application installs one by declaring a single ldh:import triple in its settings, replacing the packages/install and packages/uninstall endpoints and the file system mutations they performed.

    -

    Note: Packages are declarative only (RDF + XSLT). They contain no Java code and integrate at installation-time, not runtime.

    +

    Note: Packages are declarative only (RDF + XSLT). They contain no Java code, and they are composed into the application at request time rather than copied into it.

    What are packages?

    @@ -102,72 +102,44 @@ ns:SelectNarrowerConcepts a sp:Select ; </xsl:template>

    Available system modes include:

      -
    • bs2:* — Bootstrap 2.3.2 components (PropertyList, Form, etc.)
    • +
    • bs2:* — layout components (PropertyList, Form, etc.; the namespace is historically Bootstrap 2.3.2-based)
    • xhtml:* — XHTML elements (Style, Script, etc.)
    • Additional modes documented in the Stylesheets reference

    Installing packages

    -

    Installation requires Control access to the administration application. See the step-by-step installation guide for detailed instructions.

    -

    Installation will fail if these files do not exist.

    -

    Installation process

    -

    When you install a package, the system performs the following steps:

    +

    An application imports a package with a single triple in its settings:

    +
    <urn:linkeddatahub:apps/end-user> ldh:import <https://packages.linkeddatahub.com/skos/#this> .
    +

    That declaration is the installation — nothing is copied into the dataspace or onto the file system. Packages are installed into the end-user application of a dataspace, and writing the triple requires write access to its settings document. See the step-by-step installation guide for the user interface and command line instructions.

    +

    What the declaration does

    +

    From the next request onwards, the server resolves the declaration:

      -
    1. Fetches package metadata from the package URI
    2. -
    3. Hashes the package ontology URI using SHA-1 to create a unique document slug
    4. -
    5. Downloads package ontology (ns.ttl) and PUTs it as a document to ${admin_base}ontologies/{hash}/ where {hash} is the SHA-1 hash of the ontology URI
    6. -
    7. Adds owl:imports from the namespace ontology to the package ontology in the namespace graph (${admin_base}ontologies/namespace/)
    8. -
    9. Clears and reloads the namespace ontology from cache to pick up the new imports
    10. -
    11. Downloads package stylesheet (layout.xsl) and saves it to /static/{package-path}/layout.xsl where {package-path} is derived from the package URI (e.g., com/linkeddatahub/packages/skos/ for https://packages.linkeddatahub.com/skos/)
    12. -
    13. Updates master stylesheet at /static/xsl/layout.xsl by adding import: -
      <xsl:import href="../com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/layout.xsl"/>  <!-- System -->
      -<xsl:import href="../com/linkeddatahub/packages/skos/layout.xsl"/>  <!-- Package (added) -->
      -
    14. +
    15. Resolves the package description from the package URI. Bundled descriptions and cached graphs are read from the graph repository, other URIs are dereferenced over HTTP.
    16. +
    17. Adds the package ontology (ldt:ontology) to the application's ontology imports closure — it is declared as an owl:imports of the namespace ontology — so the package's classes, constructors, constraints and views become available on the ns endpoint and in the UI.
    18. +
    19. Composes the package stylesheet (ac:stylesheet) into the application stylesheet by appending an xsl:import after the existing imports, so package templates override the system ones.
    -
    -

    Important: After installing or uninstalling a package, you must restart the Docker service for XSLT stylesheet changes to take effect:

    -
    docker-compose restart linkeddatahub
    -

    Do not use --force-recreate as that would overwrite the stylesheet file changes.

    -
    +

    Packages are applied in the order of their URIs. A package carrying only an ontology or only a stylesheet is applied in that respect alone, and one whose description cannot be resolved is skipped. If the composed stylesheet fails to compile — an unreachable package stylesheet URL, for instance — the application falls back to its own stylesheet.

    Uninstalling packages

    -

    Packages can be safely uninstalled, which removes:

    -
      -
    • Package ontology imports from the application
    • -
    • Package-specific data and resources
    • -
    • Associated queries and stylesheets
    • -
    +

    Removing the ldh:import triple uninstalls the package: from the next request onwards its ontology is out of the imports closure and its stylesheet is no longer composed in.

    -

    Note: Uninstalling a package does not remove user-created data that uses the package's vocabulary.

    +

    Note: Uninstalling a package does not remove user-created data that uses the package's vocabulary. That data stays in the dataspace but may not display or function correctly without the package.

    Architecture

    -

    Installation-time vs runtime

    -

    Packages use installation-time composition, NOT runtime composition:

    +

    Runtime composition

    +

    Packages are composed at request time out of the declaration, not integrated into the application ahead of it:

      -
    • ✅ Package content is integrated during installation (via JAX-RS endpoints)
    • -
    • ✅ Ontology and XSLT are pre-composed before being loaded
    • -
    • ✅ No runtime overhead
    • -
    • ❌ No dynamic package loading at request time
    • +
    • The dataspace settings hold one ldh:import triple per package and nothing else
    • +
    • The ontology closure and the stylesheet imports are assembled from the resolved package descriptions
    • +
    • Installing and uninstalling take effect on the next request — no restart, no file system mutation
    -

    HTTP endpoints

    -

    Package installation and uninstallation is performed via system endpoints on the admin application. See packages/install and packages/uninstall in the HTTP API reference.

    -

    File system structure

    -

    After installing the SKOS package:

    -
    webapp/
    -├── static/
    -│   ├── com/
    -│   │   └── linkeddatahub/
    -│   │       └── packages/
    -│   │           └── skos/
    -│   │               └── layout.xsl          # Package stylesheet
    -│   └── xsl/
    -│       ├── layout.xsl                      # End-user master stylesheet
    -│       └── admin/
    -│           └── layout.xsl                  # Admin master stylesheet
    +
    +

    Packages installed with releases before 5.10 were webapp file mutations and do not carry over. Re-declare them with ldh:import.

    +

    Creating custom packages

    @@ -202,12 +174,17 @@ schema:knows ldh:view :PersonKnows . ac:stylesheet <https://raw.githubusercontent.com/you/repo/master/packages/schema.org/layout.xsl> .

    Ensure the metadata contains ldt:ontology and ac:stylesheet properties pointing to the package resources.

    Test installation

    -

    Use the CLI to test your package installation:

    -
    install-package.sh \\
    -  -b "https://localhost:4443/" \\
    -  -f ssl/owner/cert.pem \\
    +    

    Use the CLI to test your package installation — declaring the ldh:import triple in the application's settings document is the installation:

    +
    ldh patch \\
    +  -f ssl/owner/keystore.p12 \\
       -p "$cert_password" \\
    -  --package "https://packages.linkeddatahub.com/schema.org/#this"
    + "https://localhost:4443/settings" <<EOF +INSERT +{ + <urn:linkeddatahub:apps/end-user> <https://w3id.org/atomgraph/linkeddatahub#import> <https://packages.linkeddatahub.com/schema.org/#this> . +} +WHERE {} +EOF

    Available packages

    diff --git a/docs/reference/command-line-interface.ttl b/docs/reference/command-line-interface.ttl index def280c..17f6904 100644 --- a/docs/reference/command-line-interface.ttl +++ b/docs/reference/command-line-interface.ttl @@ -5,29 +5,49 @@ <> a dh:Item ; dct:title "Command line interface" ; - dct:description "CLI scripts can be used perform all actions available in the UI" ; + dct:description "The ldh CLI can be used to perform all actions available in the UI" ; rdf:_1 <#content> . <#content> a ldh:XHTML ; rdf:value """
    -

    CLI scripts can be used perform all actions available in the UI

    -

    LinkedDataHub CLI wraps the HTTP API into a set of shell scripts with convenient parameters. The scripts should run on any Unix-based system. - They can be used for testing, automation, scheduled execution and such. It is usually much quicker to perform actions using CLI rather than the +

    The ldh CLI can be used to perform all actions available in the UI

    +

    LinkedDataHub CLI wraps the HTTP API into a single executable with convenient parameters. + It can be used for testing, automation, scheduled execution and such. It is usually much quicker to perform actions using the CLI rather than the user interface, as well as easier to reproduce.

    -

    Some scripts correspond to a single request to LinkedDataHub, others combine others into tasks with multiple interdependent requests, such as the CSV import.

    -

    You will need to supply a .pem file of your WebID certificate as well as its password as script arguments, among others.

    -
    -

    The CLI scripts live in the bin folder and need to be added to the $PATH environmental variable. For example:

    -
    export PATH="$(find bin -type d -exec realpath {} \\; | tr '\n' ':')$PATH"
    -

    They also use the Jena's CLI commands internally, so make sure to have them on $PATH before running the scripts.

    +

    Some commands correspond to a single request to LinkedDataHub, others combine several into tasks with multiple interdependent requests, such as the CSV import.

    +

    You will need a .p12 keystore of your WebID certificate as well as its password, among other arguments.

    +
    +

    Install

    +

    The CLI lives in the cli folder of the LinkedDataHub repository. Building it requires Java 21 and Maven. The build produces a self-contained target/ldh.jar which the cli/bin/ldh launcher runs:

    +
    cd cli
    +mvn package
    +export PATH="$PWD/bin:$PATH"
    +cd ..
    +
    +ldh --help
    +

    If you will be using the CLI regularly, add the export line to your shell profile. Shell completion for bash and zsh is generated by the CLI itself:

    +
    source <(ldh generate-completion)
    +
    +

    The CLI talks to the HTTP API directly and embeds Apache Jena. Unlike the shell scripts it replaces, it needs no curl, python or Jena command line tools on $PATH.

    +
    +
    +
    +

    Authentication

    +

    Commands authenticate with a WebID client certificate read from a PKCS12 (.p12) keystore — the format produced by bin/webid-keygen.sh and shipped as ssl/owner/keystore.p12 for the owner agent:

    +
    ldh get \\
    +  -f ./ssl/owner/keystore.p12 \\
    +  -p "$owner_cert_password" \\
    +  --accept text/turtle \\
    +  "https://localhost:4443/"
    +

    Server certificates are not validated, so self-signed development instances work out of the box.

    Parameters

    -

    Common parameters used by most scripts include:

    +

    Common parameters used by most commands include:

    -f
    -
    --cert-pem-file
    -
    .pem file with the WebID certificate of the agent
    +
    --cert-file
    +
    .p12 (PKCS12) keystore with the WebID certificate of the agent
    -p
    --cert-password
    Password of the WebID certificate
    @@ -38,46 +58,92 @@
    The host this request will be proxied through (optional)
    It can be used with port 5443 for which the client certificate authentication is always enabled, for example --proxy https://localhost:5443/
    -

    Other parameters are script-specific.

    +

    Other parameters are command-specific. Most commands take the URI of the document they act on as a default (positional) parameter.

    +

    Environment variables

    +

    The repeated parameters can be set once in the environment instead of being passed to every command:

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    VariableParameter
    LDH_CERT_FILE-f, --cert-file
    LDH_CERT_PASSWORD-p, --cert-password
    LDH_BASE-b, --base
    LDH_PROXY--proxy
    +
    export LDH_CERT_FILE=./ssl/owner/keystore.p12
    +export LDH_CERT_PASSWORD="$owner_cert_password"
    +export LDH_BASE=https://localhost:4443/
    +
    +ldh create-container --parent "$LDH_BASE" --title "Concepts" --slug concepts
    +
    +
    +

    Output

    +

    Commands that create or append to a document print its URL as the only line on standard output, so they compose in shell pipelines. All diagnostics go to standard error.

    +
    item=$(ldh create-item --container "$LDH_BASE" --title "Example" --slug example)
    +

    Exit codes are 0 on success, 1 on an HTTP error status or runtime failure (the message goes to standard error, a stack trace with --verbose), and 2 on a usage error.

    Usage

    -

    A usage message with parameters of a script is printed when the scripted is run without any arguments. There can be named parameters and default parameters, both of those can be optional. For example:

    -
    $ add-select.sh
    -Creates a SPARQL SELECT query.
    -
    -Usage:  add-select.sh options
    -
    -Options:
    -  -f, --cert-pem-file CERT_FILE        .pem file with the WebID certificate of the agent
    -  -p, --cert-password CERT_PASSWORD    Password of the WebID certificate
    -  -b, --base BASE_URI                  Base URI of the application
    -  --proxy PROXY_URL                    The host this request will be proxied through (optional)
    -
    -  --title TITLE                        Title of the chart
    -  --description DESCRIPTION            Description of the chart (optional)
    -  --uri URI                            URI of the query (optional)
    -
    -  --query-file ABS_PATH                Absolute path to the text file with the SPARQL query string
    -  --service SERVICE_URI                URI of the SPARQL service specific to this query (optional)
    -

    The optional parameters are marked with (Optional). In this case there is no default argument, but some scripts require document (named graph) URI as the default parameter, e.g. ontology document URL.

    -

    This is how an add-select.sh invocation would look like:

    -
    add-select.sh \\
    +        

    A usage message with the parameters of a command is printed when the command is run without required arguments, or with --help. There can be named parameters and default parameters, both of those can be optional. For example:

    +
    $ ldh add-select
    +Missing required options and parameters: '--title=TITLE', '--query-file=ABS_PATH', 'TARGET_URI'
    +Usage: ldh add-select [--verbose] [-b=BASE_URI] [--description=DESCRIPTION]
    +                      [-f=CERT_FILE] [-p=CERT_PASSWORD] [--proxy=PROXY_URL]
    +                      --query-file=ABS_PATH [--service=SERVICE_URI]
    +                      --title=TITLE [--uri=URI] TARGET_URI
    +Adds a SELECT query to a document.
    +      TARGET_URI            URI of the document
    +  -b, --base=BASE_URI       Base URI of the application (env: LDH_BASE)
    +      --description=DESCRIPTION
    +                            Description of the query (optional)
    +  -f, --cert-file=CERT_FILE .p12 (PKCS12) keystore with the WebID certificate
    +                              of the agent (env: LDH_CERT_FILE)
    +  -p, --cert-password=CERT_PASSWORD
    +                            Password of the WebID certificate (env:
    +                              LDH_CERT_PASSWORD)
    +      --proxy=PROXY_URL     The host this request will be proxied through
    +                              (optional) (env: LDH_PROXY)
    +      --query-file=ABS_PATH Path to the file with the query string
    +      --service=SERVICE_URI URI of the SPARQL service (optional)
    +      --title=TITLE         Title of the query
    +      --uri=URI             URI of the query (optional, blank node if not set)
    +      --verbose             Print stack traces of errors
    +

    The optional parameters are marked with (optional). In this case the default parameter is the URI of the document the query is appended to.

    +

    This is how an add-select invocation would look like:

    +
    ldh add-select \\
       -b "$base" \\
    -  -f ./ssl/owner/cert.pem \\
    +  -f ./ssl/owner/keystore.p12 \\
       -p "$owner_cert_password" \\
       --proxy "$proxy" \\
       --title "Select concepts" \\
    -  --query-file "$pwd/queries/select-concepts.rq"
    + --query-file "$pwd/queries/select-concepts.rq" \\ + "${base}queries/"
    -

    Scripts

    -

    Currently supported:

    +

    Commands

    +

    The subcommand groups admin, content and imports mirror the folder structure of the deprecated scripts. Currently supported:

    - + @@ -86,23 +152,23 @@ Options: - + - + - + - + - + @@ -111,11 +177,11 @@ Options: - + - + @@ -125,15 +191,15 @@ Options: - + - + - + @@ -143,19 +209,19 @@ Options: - + - + - + - + @@ -164,27 +230,27 @@ Options: - + - + - + - + - + - + @@ -193,11 +259,11 @@ Options: - + - + @@ -206,19 +272,19 @@ Options: - + - + - + - + @@ -227,57 +293,79 @@ Options: - + - + - + - + - + - + - - - - - - - - - - - - - - +
    PurposeScriptCommand
    GET requestget.shldh get
    POST requestpost.shldh post
    PUT requestput.shldh put
    PATCH requestpatch.shldh patch
    DELETE requestdelete.shldh delete
    Create container documentcreate-container.shldh create-container
    Create item documentcreate-item.shldh create-item
    Append object block (instance of ldh:Object) to documentcontent/add-object-block.shldh content add-object-block
    Append XHTML block (instance of ldh:XHTML) to documentcontent/add-xhtml-block.shldh content add-xhtml-block
    Remove block from documentcontent/remove-block.shldh content remove-block
    Append service (instance of ldh:GenericService) to documentadd-generic-service.shldh add-generic-service
    Append result set chart (instance of ldh:ResultSetChart) to documentadd-result-set-chart.shldh add-result-set-chart
    Append SELECT query (instance of sp:Select) to documentadd-select.shldh add-select
    Append SPARQL view (instance of ldh:View) to documentadd-view.shldh add-view
    Create fileadd-file.shldh add-file
    Create CONSTRUCT queryadd-construct.shldh add-construct
    Create CSV importimports/add-csv-import.shldh imports add-csv-import
    Import CSV dataimports/import-csv.shldh imports import-csv
    Create RDF importimports/add-rdf-import.shldh imports add-rdf-import
    Import RDF dataimports/import-rdf.shldh imports import-rdf
    Add owl:import to ontologyadmin/add-ontology-import.shldh admin add-ontology-import
    Clear and reload ontologyadmin/clear-ontology.shldh admin clear-ontology
    Add agent to groupadmin/acl/add-agent-to-group.shldh admin acl add-agent-to-group
    Create authorizationadmin/acl/create-authorization.shldh admin acl create-authorization
    Create groupadmin/acl/create-group.shldh admin acl create-group
    Make application publicly readable to any agentadmin/acl/make-public.shldh admin acl make-public
    Add classadmin/ontologies/add-class.shldh admin ontologies add-class
    Add CONSTRUCT queryadmin/ontologies/add-constructor.shldh admin ontologies add-constructor
    Create ontologyadmin/ontologies/create-ontology.shldh admin ontologies create-ontology
    Add property constraintadmin/ontologies/add-property-constraint.shldh admin ontologies add-property-constraint
    Add restrictionadmin/ontologies/add-restriction.shldh admin ontologies add-restriction
    Add SELECT queryadmin/ontologies/add-select.shldh admin ontologies add-select
    Import ontologyadmin/ontologies/import-ontology.sh
    Packages
    Install packageadmin/packages/install-package.sh
    Uninstall packageadmin/packages/uninstall-package.shldh admin ontologies import-ontology
    -

    Usage example:

    -
    add-file.sh \\
    -  -b "https://localhost:4443/" \\
    -  -f ./ssl/owner/cert.pem \\
    +        

    Usage example — a file is uploaded to the document that will hold it, so the document is created first:

    +
    base="https://localhost:4443/"
    +
    +doc=$(ldh create-item \\
    +  -b "$base" \\
    +  -f ./ssl/owner/keystore.p12 \\
    +  -p "$owner_cert_password" \\
    +  --title "Friends" \\
    +  --container "$base")
    +
    +ldh add-file \\
    +  -b "$base" \\
    +  -f ./ssl/owner/keystore.p12 \\
       -p "$owner_cert_password" \\
       --title "Friends" \\
       --file friends.csv \\
    -  --content-type text/csv
    + --content-type text/csv \\ + "$doc"
    +

    add-file prints the content-addressed URI of the upload ({base}uploads/{sha1}) rather than the document URI.

    See also the data import user guides.

    +

    Versioned documents

    +

    ldh get reaches a versioned document's Memento roles through mutually exclusive options: + --timemap (the version history), --version <sha> (the historical version at the given commit), and + --timegate with an optional --datetime (RFC 1123 or ISO 8601). --timegate prints the negotiated + version's URI as the only line on stdout, so it pipes into another ldh get:

    +
    version=$(ldh get \\
    +  -f ./ssl/owner/keystore.p12 \\
    +  -p "$owner_cert_password" \\
    +  --timegate \\
    +  --datetime "2026-08-30T12:00:00Z" \\
    +  "$doc")
    +
    +ldh get \\
    +  -f ./ssl/owner/keystore.p12 \\
    +  -p "$owner_cert_password" \\
    +  --accept text/turtle \\
    +  "$version"
    +

    Packages

    +

    Packages have no command of their own: an application imports a package with a single ldh:import triple, so ldh patch on the application's settings document is the whole interface. See the package management guide for the full install and uninstall commands.

    +
    +
    +

    Deprecated shell scripts

    +

    The HTTP API shell scripts in the bin folder that ldh replaces are deprecated. The certificate and WebID tooling (webid-keygen.sh, webid-keygen-pem.sh, webid-uri.sh, webid-modulus.sh, server-cert-gen.sh) talks to no API and is not deprecated.

    +

    Migrating from a script to its command means dropping the .sh suffix, prefixing the invocation with ldh (subfolders become subcommand groups, e.g. admin/acl/create-group.sh becomes ldh admin acl create-group), and passing the .p12 keystore to -f instead of the .pem certificate.

    -

    Find the CLI scripts on GitHub or check out the - demo apps that use them.

    +

    Find the CLI on GitHub or check out the + demo apps that use it.

    """^^rdf:XMLLiteral . diff --git a/docs/reference/data-model/blocks.ttl b/docs/reference/data-model/blocks.ttl index 969dcef..9e08b9e 100644 --- a/docs/reference/data-model/blocks.ttl +++ b/docs/reference/data-model/blocks.ttl @@ -2,24 +2,120 @@ @prefix rdf: . @prefix dh: . @prefix dct: . - -<> a dh:Container ; + +<> a dh:Item ; dct:title "Content blocks" ; dct:description "Content blocks and the basic data content actions" ; - rdf:_1 <#content> ; - rdf:_2 <#select-children> . + rdf:_1 <#content> . <#content> a ldh:XHTML ; rdf:value """

    Content blocks and the basic data content actions.

    -

    Content list is a layout mode introduced in LinkedDataHub 3.x. It allow composition of rich, structured web documents from multiple types of content blocks.

    +

    Content list is a layout mode that allows composition of rich, structured web documents from multiple types of content blocks.

    The blocks are attached to the document resource using RDF sequence properties rdf:_1, rdf:_2 etc. The index number in the property URI indicates the position of the content block on the document's content list.

    -

    The blocks have 2 main types: XHTML (ldh:XHTML) and object (ldh:Object).

    -

    The blocks can be re-arranged by dragging one of them and dropping them in a different position.

    +

    The blocks have 2 types: XHTML (ldh:XHTML) and object (ldh:Object). These are the only types allowed as content block values — anything that is not XHTML markup, such as a query, a chart or an external resource, has to be wrapped into an ldh:Object. A built-in constraint rejects other values.

    +

    The blocks can be re-arranged by dragging one of them by its drag handle and dropping it in a different position. Every block also offers a button that copies the block's URI to the clipboard.

    -
    """^^rdf:XMLLiteral . +
    +

    XHTML blocks

    +

    XHTML block is simply a fragment of XHTML, stored as a canonical XML literal (rdf:XMLLiteral) in the RDF document. It is edited in place using the rich-text editor, which also supports RDFa annotations.

    +
    +

    RDF serialization

    +
    @prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    +
    +<https://localhost:4443/concepts/example/#xhtml-block>
    +    a       ldh:XHTML ;
    +    rdf:value "<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n<p>A paragraph</p>\\n</div>"^^rdf:XMLLiteral .
    +
    +
    +
    +

    Object blocks

    +

    Blocks other than XHTML content are called objects and have to have a URI that dereferences. Objects are embedded (transcluded) into the HTML page. You can use any RDF resource or uploaded file as an object.

    +

    LinkedDataHub will first attempt to load RDF data from the object URI and render it as block. If that fails, it will simply embed it using the HTML <object> element. Object blocks can be used to embed queries, charts, and other LinkedDataHub system resources.

    +

    Built-in block types use a UI convention where their UI is split into left, main, and right content areas. The layout of the main content may depend on the active mode of the block. Left and right sections are block-type specific.

    +
    +

    RDF serialization

    +
    @prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    +@prefix ac: <https://w3id.org/atomgraph/client#> .
     
    -<#select-children> a ldh:Object ;
    -    rdf:value ldh:ChildrenView .
    +<https://localhost:4443/concepts/example/#object-block>
    +    a       ldh:Object ;
    +    rdf:value <http://dbpedia.org/resource/Copenhagen> ;
    +    ac:mode  ac:MapMode .
    +
    +
    +
    +

    Management

    +
    +

    User interface

    +

    Content blocks can only be managed when the Content layout mode is active. The edit and delete buttons only appear for agents with write access.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ActionDescription
    Create XHTML blockClick the XHTML button at the bottom of the page. Create the XHTML content in the rich-text editor.
    Create object blockClick the Object button at the bottom of the page. Enter the object's value URI. Click Save.
    Update XHTML blockClick into the block's content and edit it in place. The changes are saved automatically when the focus leaves the block — there is no separate save button.
    Update object blockClick the button in the top-right corner of the block (it will only appear when you move the mouse close to that corner). Change the object's value URI. Click Save.
    DeleteClick the button in the top-right corner of the block (it will only appear when you move the mouse close to that corner). Click the button to delete the block.
    ReorderDrag the block by its drag handle and drop it in a different position on the content list.
    Copy URIClick the copy button on the block to copy the block's URI to the clipboard.
    +
    +
    +

    Command line interface

    + + + + + + + + + + + + + + + + + + + + + +
    ActionCLI command
    Create XHTML blockldh content add-xhtml-block
    Create object blockldh content add-object-block
    Deleteldh content remove-block
    +
    +
    +
    """^^rdf:XMLLiteral . diff --git a/docs/reference/data-model/blocks/objects.ttl b/docs/reference/data-model/blocks/objects.ttl deleted file mode 100644 index 0ef9be8..0000000 --- a/docs/reference/data-model/blocks/objects.ttl +++ /dev/null @@ -1,81 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Object blocks" ; - dct:description "Blocks that embed/transclude any dereferenceable URI" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Blocks that embed/transclude any dereferenceable URI

    -

    Blocks other than HTML content is called an object and has to have a URI that dereferences. Objects are embedded (transcluded) into the HTML page. You can use any RDF resource or uploaded file as an object.

    -

    LinkedDataHub will first attempt to load RDF data from the object URI and render it as block. If that fails, it will simply embed it using the HTML <object> element. Object blocks can be used to embed queries, charts, and other LinkedDataHub system resources.

    -

    Built-in block types use a UI convention where their UI is split into left, main, and right content areas. The layout of the main content may depend on the active mode of the block. Left and right sections are block-type specific.

    -
    -

    RDF serialization

    -
    @prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    -@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    -@prefix ac: <https://w3id.org/atomgraph/client#> .
    -
    -<https://localhost:4443/concepts/example/#object-block>
    -    a       ldh:Object ;
    -    rdf:value <http://dbpedia.org/resource/Copenhagen> ;
    -    ac:mode  ac:MapMode .
    -
    -
    -

    Management

    -
    -

    User interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionDescription
    CreateClick the Object button at the bottom of the page (in content mode). Enter the object's value URI. Click Save.
    UpdateClick the button in the top-right corner of the block (it will only appear when you move the mouse close to that corner). Change the object's value URI. Click Save.
    DeleteClick the button in the top-right corner of the block (it will only appear when you move the mouse close to that corner). Click the button to delete the block.
    -
    -
    -

    Command line interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionCLI script
    Createcontent/add-object-block.sh
    Update
    Delete
    -
    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/data-model/blocks/xhtml.ttl b/docs/reference/data-model/blocks/xhtml.ttl deleted file mode 100644 index 13563f2..0000000 --- a/docs/reference/data-model/blocks/xhtml.ttl +++ /dev/null @@ -1,78 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "XHTML blocks" ; - dct:description "Blocks that embed XHTML markup as an RDF literal" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Blocks that embed XHTML markup as an RDF literal

    -

    XHTML block is simply a fragment of XHTML, stored as a canonical XML literal (rdf:XMLLiteral) in the RDF document. It can be edited using a WYSIWYG editor.

    -
    -

    RDF serialization

    -
    @prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    -@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    -
    -<https://localhost:4443/concepts/example/#xhtml-block>
    -    a       ldh:XHTML ;
    -    rdf:value "<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n<p>A paragraph</p>\\n</div>"^^rdf:XMLLiteral .
    -
    -
    -

    Management

    -
    -

    User interface

    -

    Content blocks can only be managed when the Content layout mode is active.

    - - - - - - - - - - - - - - - - - - - - - -
    ActionDescription
    CreateClick the XHTML button at the bottom of the page (in content mode). Create the XHTML content in the WYSIWYG editor. Click Save.
    UpdateClick the button in the top-right corner of the block (it will only appear when you move the mouse close to that corner). Change the XHTML content in the WYSIWYG editor. Click Save.
    DeleteClick the button in the top-right corner of the block (it will only appear when you move the mouse close to that corner). Click the button to delete the block.
    -
    -
    -

    Command line interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionCLI script
    Createcontent/add-xhtml-block.sh
    Update
    Delete
    -
    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/data-model/documents.ttl b/docs/reference/data-model/documents.ttl index dc069da..d6670b3 100644 --- a/docs/reference/data-model/documents.ttl +++ b/docs/reference/data-model/documents.ttl @@ -2,12 +2,11 @@ @prefix rdf: . @prefix dh: . @prefix dct: . - -<> a dh:Container ; + +<> a dh:Item ; dct:title "Documents" ; dct:description "Document hierarchy and the basic data management actions" ; - rdf:_1 <#content> ; - rdf:_2 <#select-children> . + rdf:_1 <#content> . <#content> a ldh:XHTML ; rdf:value """
    @@ -19,14 +18,131 @@ which are collections of items (files) as well as sub-containers (sub-folders). Both containers (instances of dh:Container) and items (instances of dh:Item) are documents (instances of foaf:Document). Items cannot contain other documents.

    The first level of resources in a container is referred to as its children (of which that container - is the parent, while all levels down the hierarchy are collectively referred to as + is the parent), while all levels down the hierarchy are collectively referred to as descendants.

    When a user logs in, the application loads its root container (unless a specific URI was requested). From there, users can navigate down the resource hierarchy, starting with children of the root container. At - any moment there is only one current document per page, on which management actions can + any moment there is only one current document per page, on which management actions can be performed: it can be viewed, edited etc.

    -
    """^^rdf:XMLLiteral . +
    +

    Containers

    +

    Containers are analogous to folders in a filesystem.

    +
    +

    RDF serialization

    +

    Document properties such as sioc:has_parent, dct:created, dct:modified, acl:owner are automatically managed by LinkedDataHub.

    +
    @prefix dh: <https://www.w3.org/ns/ldt/document-hierarchy#> .
    +@prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    +@prefix dct: <http://purl.org/dc/terms/> .
    +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    +@prefix sioc: <http://rdfs.org/sioc/ns#> .
    +@prefix acl: <http://www.w3.org/ns/auth/acl#> .
    +
    +<https://localhost:4443/concepts/>
    +    a       dh:Container ;
    +    rdf:_1  <https://localhost:4443/concepts/#select-children> ;
    +    dct:created "2025-06-02T19:12:26.533Z"^^xsd:dateTime ;
    +    dct:creator <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> ;
    +    dct:title "Concepts" ;
    +    sioc:has_parent <https://localhost:4443/concepts/> ;
    +    acl:owner <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> .
    +
    +<https://localhost:4443/concepts/#select-children>
    +    a       ldh:Object ;
    +    rdf:value ldh:ChildrenView .
    +
    +
    +
    +

    Items

    +

    Items are analogous to files in a filesystem.

    +
    +

    RDF serialization

    +

    Document properties such as sioc:has_container, dct:created, dct:modified, acl:owner are automatically managed by LinkedDataHub.

    +
    @prefix dh: <https://www.w3.org/ns/ldt/document-hierarchy#> .
    +@prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    +@prefix ac: <https://w3id.org/atomgraph/client#> .
    +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    +@prefix dct: <http://purl.org/dc/terms/> .
    +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    +@prefix sioc: <http://rdfs.org/sioc/ns#> .
    +@prefix acl: <http://www.w3.org/ns/auth/acl#> .
     
    -<#select-children> a ldh:Object ;
    -    rdf:value ldh:ChildrenView .
    +<https://localhost:4443/concepts/example/>
    +    a       dh:Item ;
    +    rdf:_1  <https://localhost:4443/concepts/example/#xhtml-block> ;
    +    rdf:_2  <https://localhost:4443/concepts/example/#object-block> ;
    +    dct:created "2025-06-02T19:49:48.126Z"^^xsd:dateTime ;
    +    dct:creator <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> ;
    +    dct:title "Example" ;
    +    sioc:has_container <https://localhost:4443/concepts/> ;
    +    acl:owner <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> .
    +
    +<https://localhost:4443/concepts/example/#xhtml-block>
    +    a       ldh:XHTML ;
    +    rdf:value "<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n<p>A paragraph</p>\\n</div>"^^rdf:XMLLiteral .
    +
    +<https://localhost:4443/concepts/example/#object-block>
    +    a       ldh:Object ;
    +    rdf:value <http://dbpedia.org/resource/Copenhagen> ;
    +    ac:mode  ac:MapMode .
    +
    +
    +
    +

    Management

    +
    +

    User interface

    + + + + + + + + + + + + + + + + + + + + + +
    ActionDescription
    CreateCreate a new child document by clicking the Create button. Fill out the form. Click Save.
    UpdateOpen the current document editing form by clicking the Edit button in the action bar. Make changes. Click Save.
    DeleteDelete the current document by clicking the Delete button in the action bar.
    +
    +
    +

    Command line interface

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    ActionCLI command
    Create containerldh create-container
    Create itemldh create-item
    Updateldh put
    Deleteldh delete
    +
    +
    +
    """^^rdf:XMLLiteral . diff --git a/docs/reference/data-model/documents/containers.ttl b/docs/reference/data-model/documents/containers.ttl deleted file mode 100644 index ee326f0..0000000 --- a/docs/reference/data-model/documents/containers.ttl +++ /dev/null @@ -1,91 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Containers" ; - dct:description "Containers are analogous to folders in a filesystem" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Containers are analogous to folders in a filesystem

    -
    -

    RDF serialization

    -

    Document properties such as sioc:has_parent, dct:created, dct:modified, acl:owner are automatically managed by LinkedDataHub.

    -
    @prefix dh: <https://www.w3.org/ns/ldt/document-hierarchy#> .
    -@prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    -@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    -@prefix dct: <http://purl.org/dc/terms/> .
    -@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    -@prefix sioc: <http://rdfs.org/sioc/ns#> .
    -@prefix acl: <http://www.w3.org/ns/auth/acl#> .
    -
    -<https://localhost:4443/concepts/>
    -    a       dh:Container ;
    -    rdf:_1  <https://localhost:4443/concepts/#select-children> ;
    -    dct:created "2025-06-02T19:12:26.533Z"^^xsd:dateTime ;
    -    dct:creator <https://localhost:4443/admin/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> ;
    -    dct:title "Concepts" ;
    -    sioc:has_parent <https://localhost:4443/concepts/> ;
    -    acl:owner <https://localhost:4443/admin/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> .
    -
    -<https://localhost:4443/concepts/#select-children>
    -    a       ldh:Object ;
    -    rdf:value ldh:ChildrenView .
    -
    -
    -

    Management

    -
    -

    User interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionDescription
    CreateCreate a new child document by clicking the Create button on the left of the navbar. Fill out the form. Click Save.
    UpdateOpen the current document editing form by clicking the Edit button in the middle section of the navbar. Make changes. Click Save.
    DeleteDelete the current document by clicking the Delete in the action bar (the right section of the navbar)
    -
    -
    -

    Command line interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionCLI script
    Createcreate-container.sh
    Updateput.sh
    Deletedelete.sh
    -
    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/data-model/documents/items.ttl b/docs/reference/data-model/documents/items.ttl deleted file mode 100644 index 4403901..0000000 --- a/docs/reference/data-model/documents/items.ttl +++ /dev/null @@ -1,98 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Items" ; - dct:description "Items are analogous to files in a filesystem" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Items are analogous to files in a filesystem

    -
    -

    RDF serialization

    -

    Document properties such as sioc:has_container, dct:created, dct:modified, acl:owner are automatically managed by LinkedDataHub.

    -
    @prefix dh: <https://www.w3.org/ns/ldt/document-hierarchy#> .
    -@prefix ldh: <https://w3id.org/atomgraph/linkeddatahub#> .
    -@prefix ac: <https://w3id.org/atomgraph/client#> .
    -@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
    -@prefix dct: <http://purl.org/dc/terms/> .
    -@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    -@prefix sioc: <http://rdfs.org/sioc/ns#> .
    -@prefix acl: <http://www.w3.org/ns/auth/acl#> .
    -
    -<https://localhost:4443/concepts/example/>
    -    a       dh:Item ;
    -    rdf:_1  <https://localhost:4443/concepts/example/#xhtml-block> ;
    -    rdf:_2  <https://localhost:4443/concepts/example/#object-block> ;
    -    dct:created "2025-06-02T19:49:48.126Z"^^xsd:dateTime ;
    -    dct:creator <https://localhost:4443/admin/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> ;
    -    dct:title "Example" ;
    -    sioc:has_container <https://localhost:4443/concepts/> ;
    -    acl:owner <https://localhost:4443/admin/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> .
    -    
    -<https://localhost:4443/concepts/example/#xhtml-block>
    -    a       ldh:XHTML ;
    -    rdf:value "<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">\\n<p>A paragraph</p>\\n</div>"^^rdf:XMLLiteral .
    -
    -<https://localhost:4443/concepts/example/#object-block>
    -    a       ldh:Object ;
    -    rdf:value <http://dbpedia.org/resource/Copenhagen> ;
    -    ac:mode  ac:MapMode .
    -
    -
    -

    Management

    -
    -

    User interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionDescription
    CreateCreate a new child document by clicking the Create button on the left of the navbar. Fill out the form. Click Save.
    UpdateOpen the current document editing form by clicking the Edit button in the middle section of the navbar. Make changes. Click Save.
    DeleteDelete the current document by clicking the Delete in the action bar (the right section of the navbar)
    -
    -
    -

    Command line interface

    - - - - - - - - - - - - - - - - - - - - - -
    ActionCLI script
    Createcreate-item.sh
    Updateput.sh
    Deletedelete.sh
    -
    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/data-model/resources.ttl b/docs/reference/data-model/resources.ttl index 16e37fe..f3f1c37 100644 --- a/docs/reference/data-model/resources.ttl +++ b/docs/reference/data-model/resources.ttl @@ -2,12 +2,11 @@ @prefix rdf: . @prefix dh: . @prefix dct: . - -<> a dh:Container ; + +<> a dh:Item ; dct:title "Resources" ; dct:description "RDF resources and their management" ; - rdf:_1 <#content> ; - rdf:_2 <#select-children> . + rdf:_1 <#content> . <#content> a ldh:XHTML ; rdf:value """
    @@ -17,7 +16,7 @@

    Built-in classes are defined in system ontologies while user-defined classes are defined in user ontologies.

    User-defined classes

    -

    TBD

    +

    User-defined classes live in the application's namespace ontology, which is managed in the admin application. A class becomes creatable in the UI once it has a constructor: it then appears in the Create dropdown alongside the built-in classes, and its instances are created and edited using the same forms. See Change the model for a walkthrough of defining classes, constructors and constraints.

    Built-in classes

    @@ -34,7 +33,7 @@ ASK sp:Ask - SPARQL ASK query + SPARQL ASK query Application @@ -44,7 +43,7 @@ CONSTRUCT sp:Construct - SPARQL CONSTRUCT query + SPARQL CONSTRUCT query CSV import @@ -59,7 +58,7 @@ Graph chart ldh:GraphChart - Chart based on CONSTRUCT/DESCRIBE query results + Chart based on CONSTRUCT/DESCRIBE query results RDF import @@ -69,12 +68,12 @@ Result set chart ldh:ResultSetChart - Chart based on SELECT query results + Chart based on SELECT query results Select sp:Select - SPARQL SELECT query + SPARQL SELECT query Service @@ -84,113 +83,210 @@ View ldh:View - View based on SELECT query results + View based on SELECT query results + + + +

    Resources that have customized UIs (such as queries, views, charts) are documented in more detail below.

    +
    +
    +
    +

    Queries

    +

    + Query +

    +

    Queries are SPARQL 1.1 query strings that can be executed interactively. They can be defined with a SPARQL + service that they execute against, otherwise they execute against the application's own SPARQL service.

    +

    It is only possible to save valid SPARQL 1.1 query strings. SPARQL updates are currently not supported.

    +
    +
    +

    Charts

    +

    + Chart +

    +

    Charts can render both types of SPARQL results:

    +
      +
    • tabular SELECT results (ldh:ResultSetChart)
    • +
    • graph results of DESCRIBE and CONSTRUCT (ldh:GraphChart)
    • +
    +

    In that sense they are similar to the chart layout mode in + views, but charts also store the chart type as well as the category and series information: variable names in the + case of tabular results, and property URIs in the case of graph results.

    +
    +

    RDF vocabulary

    +

    A chart references its query with spin:query and describes its rendering with the following properties:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PropertyApplies toDescription
    ldh:chartTypebothOne of ac:Table, ac:LineChart, ac:BarChart, ac:ScatterChart, ac:Timeline
    ldh:categoryVarNameldh:ResultSetChartName of the SELECT variable that provides the category values
    ldh:seriesVarNameldh:ResultSetChartName(s) of the SELECT variable(s) that provide the series values
    ldh:categoryPropertyldh:GraphChartURI of the property that provides the category values
    ldh:seriesPropertyldh:GraphChartURI(s) of the property(ies) that provide the series values
    +
    +

    The default chart type is the table. Other chart types might not apply to all result data; for example a + scatter chart will need numeric or datetime values for both category and series.

    +
    +
    +

    Views

    +

    + View +

    +

    Views are interactive, paginated, and optionally ordered results of a SPARQL SELECT result set. + What is rendered in the UI is not directly the tabular result however, but descriptions of the resources selected + by the result set. That is achieved by on-the-fly SPARQL query rewriting: the SELECT is wrapped into + a DESCRIBE query; DESCRIBE reuses the same variables from the SELECT projection. + This will not work for all SELECT queries.

    +

    To render paginated lists of resources, legacy applications would normally have a dedicated API endpoint that + supports pagination, ordering etc. In LinkedDataHub, views achieve the same functionality by simply building the + SPARQL query string on the client-side. Therefore views can be seen as client-side "containers".

    +

    Views can be rendered in multiple layout modes: properties, list, grid, table, map, chart, and graph. They also show + the total number of results and allow result ordering by property. In the table mode, columns can be sorted by clicking + their headers; the sort order follows each column's datatype.

    +
    +

    Projecting variables

    +

    Because the SELECT is rewritten into a DESCRIBE over its projected variables, a view can only render terms that the triplestore is able to describe:

    +
      +
    • Project resources (IRIs) that exist in the dataset — their labels, images and links are resolved from each resource's own description. Projecting literals (a ?label, a ?date, or any computed value) describes nothing, so the view renders empty.
    • +
    • Project IRIs that are stored locally. An IRI that only occurs as an object but is not itself described in the dataset — for example an external image URL — contributes no triples.
    • +
    • Project a single entity type. Projecting several resource variables of different types blends their descriptions into one result set, which rarely renders coherently; select the one type you want to list and reach the related resources through their properties instead.
    • +
    +
    +
    +

    Layout modes

    +

    View results can be rendered using the same layout modes as the document layout modes.

    +
    +
    +

    Inline creation

    +

    A view that carries ldh:container metadata renders a Create button that creates a new instance in that container, so a view can double as an entry point for the data it lists.

    +

    A view with ldh:showWhenEmpty set to false is hidden while its query returns no results.

    +
    +
    + +

    On the left side, views provide faceted search which acts as a filter that narrows down the view results.

    +

    By default the facets are generic and inferred from the triple patterns of the SPARQL SELECT query used by the view. They can be customized using XSLT.

    +
    +
    +

    Parallax navigation

    +

    Parallax navigation is a rather unique navigation approach that lends itself perfectly to graph data. It is enabled for container content and shown as Related results on the right side of the view. Parallax allows "jumping" from a result set to a related result set using the selected RDF property. It works in + combination with faceted search which can be used to filter the initial result set.

    +

    For example, facets can be used to filter a set products that belong to a certain category, and then parallax can be used to jump to a set of companies that provide those products, and then further on to + a set representatives of those companies.

    +

    + Related results +

    +
    +
    +
    +

    Management

    +
    +

    User interface

    +

    Resources can only be managed when the Properties layout mode is active.

    + + + + + + + + + + + + + + + + + + + + + +
    ActionDescription
    CreateClick the Create dropdown on the bottom of the page. Fill out the fields in the form that appears. Click Save.
    UpdateClick the button in the top-right corner of the resource header (in the middle column of the content). Make changes in the form that appears. Click Save.
    DeleteClick the button in the top-right corner of the resource header. Click the button to delete the block.
    +
    +
    +

    Command line interface

    +

    The following resource types can be created using the command line interface. Any resource can be updated or deleted with ldh patch, which applies a SPARQL update to the resource's document.

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TypeActionCLI command
    CONSTRUCTCreateldh add-construct
    CSV importCreateldh imports add-csv-import
    FileCreateldh add-file
    RDF importCreateldh imports add-rdf-import
    Result set chartCreateldh add-result-set-chart
    SelectCreateldh add-select
    ServiceCreateldh add-generic-service
    ViewCreateldh add-view
    -

    Resources that have customized UIs (such as queries, views, charts) are documented in more detail.

    -
    -

    Management

    -
    -

    User interface

    -

    Resources can only be managed when the Properties layout mode is active.

    - - - - - - - - - - - - - - - - - - - - - -
    ActionDescription
    CreateClick the Create dropdown on the bottom of the page. Fill out the fields in the form that appears. Click Save.
    UpdateClick the button in the top-right corner of the resource header (in the middle column of the content). Make changes in the form that appears. Click Save.
    DeleteClick the button in the top-right corner of the resource header. Click the button to delete the block.
    -
    -
    -

    Command line interface

    -

    The following actions can also be performed using the command line interface.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    TypeActionCLI script
    ASKCreate
    ApplicationCreate
    CONSTRUCTCreateadmin/ontologies/add-construct.sh
    CSV importCreateimports/add-csv-import.sh
    FileCreateadd-file.sh
    Graph chartCreate
    RDF importCreateimports/add-rdf-import.sh
    Result set chartCreateadd-result-set-chart.sh
    SelectCreateadd-select.sh
    ServiceCreateadd-generic-service.sh
    ViewCreateadd-view.sh
    -
    -
    """^^rdf:XMLLiteral . - -<#select-children> a ldh:Object ; - rdf:value ldh:ChildrenView . diff --git a/docs/reference/data-model/resources/views/ParallaxNav.gif b/docs/reference/data-model/resources/ParallaxNav.gif similarity index 100% rename from docs/reference/data-model/resources/views/ParallaxNav.gif rename to docs/reference/data-model/resources/ParallaxNav.gif diff --git a/docs/reference/data-model/resources/charts/chart-block.png b/docs/reference/data-model/resources/chart-block.png similarity index 100% rename from docs/reference/data-model/resources/charts/chart-block.png rename to docs/reference/data-model/resources/chart-block.png diff --git a/docs/reference/data-model/resources/charts.ttl b/docs/reference/data-model/resources/charts.ttl deleted file mode 100644 index d87d271..0000000 --- a/docs/reference/data-model/resources/charts.ttl +++ /dev/null @@ -1,27 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Charts" ; - dct:description "Interactive charts based on SPARQL queries" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Interactive charts based on SPARQL queries

    -

    - Chart -

    -

    Charts can render results both types of SPARQL results:

    -
      -
    • tabular SELECT results
    • -
    • graph results of DESCRIBE and CONSTRUCT
    • -
    -

    In that sense they are similar to the chart layout mode in - views, but charts also store the chart type as well as the category and series information: variable names in the - case of tabular results, and property URIs in the case of graph results.

    -

    The default chart type is the table. Other chart types might not apply to all result data; for example a - scatter chart will need a numeric or datetime values for both category and series.

    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/data-model/resources/queries.ttl b/docs/reference/data-model/resources/queries.ttl deleted file mode 100644 index 3453b0c..0000000 --- a/docs/reference/data-model/resources/queries.ttl +++ /dev/null @@ -1,20 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Queries" ; - dct:description "Interactive SPARQL queries" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Interactive SPARQL queries

    -

    - Query -

    -

    Queries are SPARQL 1.1 query strings that can be executed interactively. They can be defined with a SPARQL - service that they execute against, otherwise they execute against the application's own SPARQL service.

    -

    It is only possible to save valid SPARQL 1.1 query strings. SPARQL updates are currently not supported.

    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/data-model/resources/queries/query-block.png b/docs/reference/data-model/resources/query-block.png similarity index 100% rename from docs/reference/data-model/resources/queries/query-block.png rename to docs/reference/data-model/resources/query-block.png diff --git a/docs/reference/data-model/resources/views/view-block.png b/docs/reference/data-model/resources/view-block.png similarity index 100% rename from docs/reference/data-model/resources/views/view-block.png rename to docs/reference/data-model/resources/view-block.png diff --git a/docs/reference/data-model/resources/views.ttl b/docs/reference/data-model/resources/views.ttl deleted file mode 100644 index c0e554b..0000000 --- a/docs/reference/data-model/resources/views.ttl +++ /dev/null @@ -1,55 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Views" ; - dct:description "Paginated views based on SPARQL queries" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Paginated views based on SPARQL queries

    -

    - View -

    -

    Views are interactive, paginated, and optionally ordered results of a SPARQL SELECT result set. - What is rendered in the UI is not directly the tabular result however, but descriptions of the resources selected - by the result set. That is achieved by on-the-fly SPARQL query rewriting: the SELECT is wrapped into - a DESCRIBE query; DESCRIBE reuses the same variables from the SELECT projection. - This will not work for all SELECT queries.

    -

    To render paginated lists of resources, legacy applications would normally have a dedicated API endpoint that - supports pagination, ordering etc. In LinkedDataHub, views achieve the same functionality by simply building the - SPARQL query string on the client-side. Therefore views can be seen as client-side "containers".

    -

    Views can be rendered in multiple layout modes: properties, list, grid, table, map, chart, and graph. They also show - the total number of results and allow result ordering by property.

    -
    -

    Projecting variables

    -

    Because the SELECT is rewritten into a DESCRIBE over its projected variables, a view can only render terms that the triplestore is able to describe:

    -
      -
    • Project resources (IRIs) that exist in the dataset — their labels, images and links are resolved from each resource's own description. Projecting literals (a ?label, a ?date, or any computed value) describes nothing, so the view renders empty.
    • -
    • Project IRIs that are stored locally. An IRI that only occurs as an object but is not itself described in the dataset — for example an external image URL — contributes no triples.
    • -
    • Project a single entity type. Projecting several resource variables of different types blends their descriptions into one result set, which rarely renders coherently; select the one type you want to list and reach the related resources through their properties instead.
    • -
    -
    -
    -

    Layout modes

    -

    View results can be rendered using the same layout modes as the document layout modes.

    -
    -
    - -

    On the left side, views provide faceted search which acts as a filter that narrows down the view results.

    -

    By default the facets are generic and inferred from the triple patterns of the SPARQL SELECT query used by the view. They can be customized using XSLT.

    -
    -
    -

    Parallax navigation

    -

    Parallax navigation is a rather unique navigation approach that lends itself perfectly to graph data. It is enabled for container content and shown as Related results on the right side of the view. Parallax allows "jumping" from a result set to a related result set using the selected RDF property. It works in - combination with faceted search which can be used to filter the initial result set.

    -

    For example, facets can be used to filter a set products that belong to a certain category, and then parallax can be used to jump to a set of companies that provide those products, and then further on to - a set representatives of those companies.

    -

    - Related results -

    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/dataspace.ttl b/docs/reference/dataspace.ttl index 1014519..4097d99 100644 --- a/docs/reference/dataspace.ttl +++ b/docs/reference/dataspace.ttl @@ -122,17 +122,9 @@

    Document hierarchy

    -

    The basic structure of resources in an application is analogous to the file system, but built using RDF - resources and relationships between them instead. There is a hierarchy of containers, - which are collections of items as well as sub-containers. Both containers - and items are documents. Items cannot contain other documents.

    -

    The first level of resources in a container is referred to as its children (of which that container - is the parent), while all levels down the hierarchy are collectively referred to as - descendants.

    -

    When a user logs in, the application loads its root container (unless a specific URI was requested). From there, - users can navigate down the resource hierarchy, starting with children of the root container. At - any moment there is only one current document per page, on which actions can - be performed: it can be viewed, edited etc.

    +

    Each application's data is structured as a hierarchy of containers and items, analogous to folders and files in a file system. + See the document hierarchy reference for the details and the + management actions that can be performed on documents.

    If you are ready to create a dataspace, see our step-by-step tutorial on dataspace management.

    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/http-api.ttl b/docs/reference/http-api.ttl index 550a004..8affe73 100644 --- a/docs/reference/http-api.ttl +++ b/docs/reference/http-api.ttl @@ -13,18 +13,18 @@ rdf:value """

    Learn how to read and write RDF data from/to LinkedDataHub applications over HTTP

    LinkedDataHub implements a uniform, generic RESTful Linked Data API as defined by the - SPARQL 1.1 Graph Store Protocol. It adds a few conventions and constraints + SPARQL 1.1 Graph Store Protocol. It adds a few conventions (such as automatic document metadata) and constraints on top of it however.

    Authentication

    LinkedDataHub UI supports 2 authentication methods:

    See how those authentication methods can be configured or how to get an account on LinkedDataHub.

    -

    HTTP API access using CLI scripts or curl currently does not support the OIDC method.

    +

    HTTP API access using the CLI or curl currently does not support the OIDC method.

    @@ -135,11 +135,11 @@

    Document metadata

    Unlike earlier versions, LinkedDataHub 5.x manages the document hierarchy automatically.

    -

    By default, LinkedDataHub treats an RDF document as an item by giving it the dh:Item type and attaching it to the parent container using sioc:has_container. If the client wants to create a container instead, it has to explicitly add the dh:Container type on the document resource; the new container will be attached to its parent using sioc:has_container. In either case, the URI of the new document's will be relative to its parent's.

    +

    By default, LinkedDataHub treats an RDF document as an item by giving it the dh:Item type and attaching it to the parent container using sioc:has_container. If the client wants to create a container instead, it has to explicitly add the dh:Container type on the document resource; the new container will be attached to its parent using sioc:has_parent. In either case, the URI of the new document will be relative to its parent's.

    LinkedDataHub will also manage additional document metadata, such as its owner and creation/modification timestamps.

    For example, this HTTP request to create a new container (Turtle syntax):

    -
    PUT /namedgraph/new-container/ HTTP/1.1
    -Host: linkeddatahub.com
    +            
    PUT /new-container/ HTTP/1.1
    +Host: localhost:4443
     Content-Type: text/turtle
     
     @prefix dh:     <https://www.w3.org/ns/ldt/document-hierarchy#> .
    @@ -154,30 +154,30 @@ Content-Type: text/turtle
     @prefix sioc:   <http://rdfs.org/sioc/ns#> .
     @prefix acl:    <http://www.w3.org/ns/auth/acl#> .
     
    -<https://linkeddatahub.com/namedgraph/new-container/>
    +<https://localhost:4443/new-container/>
         a dh:Container ;
         dct:created "2025-03-31T21:46:21.984Z"^^xsd:dateTime ;
    -    dct:creator <https://linkeddatahub.com/namedgraph/admin/acl/agents/fda0009e-191b-4f07-838c-5daf2a74b35f/#this> ;
    +    dct:creator <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> ;
         dct:title "New container" ;
    -    sioc:has_parent <https://linkeddatahub.com/namedgraph/> ;
    -    acl:owner <https://linkeddatahub.com/namedgraph/admin/acl/agents/fda0009e-191b-4f07-838c-5daf2a74b35f/#this> .
    + sioc:has_parent <https://localhost:4443/> ; + acl:owner <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> .

    The HTTP request to produce a new item can be empty:

    -
    PUT /namedgraph/new-container/ HTTP/1.1
    -Host: linkeddatahub.com
    +            
    PUT /new-item/ HTTP/1.1
    +Host: localhost:4443
     Content-Type: text/turtle

    It will create an item document with the following triples:

    -
    @prefix dh:    <https://www.w3.org/ns/ldt/document-hierarchy#> .
    +            
    @prefix dh:     <https://www.w3.org/ns/ldt/document-hierarchy#> .
     @prefix dct:    <http://purl.org/dc/terms/> .
     @prefix xsd:    <http://www.w3.org/2001/XMLSchema#> .
     @prefix sioc:   <http://rdfs.org/sioc/ns#> .
     @prefix acl:    <http://www.w3.org/ns/auth/acl#> .
     
    -<https://linkeddatahub.com/namedgraph/new-item/>
    +<https://localhost:4443/new-item/>
         a dh:Item ;
         dct:created "2025-03-31T20:45:42.802Z"^^xsd:dateTime ;
    -    dct:creator <https://linkeddatahub.com/acl/agents/d47e1f9b-c8d0-4546-840f-5d9fbb479da2/#id9d3814f2-53bc-42e9-b1ab-46cbc9a94263> ;
    -    sioc:has_container <https://linkeddatahub.com/namedgraph/> ;
    -    acl:owner <https://linkeddatahub.com/admin/acl/agents/d47e1f9b-c8d0-4546-840f-5d9fbb479da2/#id9d3814f2-53bc-42e9-b1ab-46cbc9a94263> .
    + dct:creator <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> ; + sioc:has_container <https://localhost:4443/> ; + acl:owner <https://admin.localhost:4443/acl/agents/865c2431-8436-4ae8-b300-2a531a013cd0/#this> .

    Built-in constraints

    @@ -190,6 +190,20 @@ Content-Type: text/turtle

    The built-in constraints are similar to, but separate from the ontology constraints.

    +
    +

    Versioned documents

    +

    In dataspaces with versioning enabled, document URLs answer additional query parameters that expose the version + history through the standard Memento (RFC 7089) protocol:

    +
    +
    ?version=<commit-sha>
    +
    A historical version of the document, served with Memento-Datetime and immutable caching. Snapshots are read-only: write methods answer 405 Method Not Allowed
    +
    ?timemap
    +
    The document's version history — a TimeMap described with PROV-O in RDF formats, or in application/link-format as RFC 7089 requires
    +
    ?timegate
    +
    Datetime negotiation: a request with an Accept-Datetime header answers 302 Found with the closest version in Location
    +
    +

    See the versioning reference for details.

    +

    Executing SPARQL

    Every LinkedDataHub application provides a SPARQL endpoint on sparql path (relative to the application's base URI). It supports the @@ -207,6 +221,8 @@ Content-Type: text/turtle

    ns
    In-memory namespace ontology as well as its SPARQL endpoint
    +
    settings
    +
    The application's own description in the dataspace configuration. PATCH with a SPARQL update edits it — this is how packages are installed and uninstalled. Editing is restricted to end-user applications: a PATCH on an admin application's settings answers 422 Unprocessable Entity

    Admin app only

    @@ -226,10 +242,6 @@ Content-Type: text/turtle
    OpenID Connect with ORCID callback
    admin/clear
    Clears the specified ontology from memory cache and reloads it from the admin SPARQL endpoint
    -
    packages/install
    -
    Installs a package. Requires owner/admin authentication
    -
    packages/uninstall
    -
    Uninstalls a package. Requires owner/admin authentication
    @@ -242,6 +254,24 @@ Content-Type: text/turtle (<script type="application/ld+json">) and parses it as RDF using JSON-LD 1.1, so pages annotated with schema.org markup can be browsed as Linked Data. The schema.org JSON-LD context is bundled with LinkedDataHub and served locally, so no external network request is made to resolve it.

    The proxy only accepts external (non-relative to the current application's base URI) URLs; local URLs have to be dereferenced directly.

    +

    The proxy forwards the origin's ETag and Last-Modified validators and passes conditional request headers + (If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since) through, so preconditioned + writes against proxied documents are evaluated at the origin. Error statuses from the origin reach the client unchanged.

    +
    +
    +

    Federation

    +

    The Linked Data proxy makes dataspaces interoperable across origins: one dataspace's client can browse, query, and write to another + origin's dataspace, whether on the same LinkedDataHub instance or a different one.

    +
      +
    • The remote application's SPARQL endpoint is discovered from the Link headers the proxy forwards, so views and charts over + remote documents query the remote endpoint
    • +
    • Writes are graph-scoped SPARQL updates (PATCH) or appends (POST) sent through the proxy under the origin's + If-Match precondition
    • +
    • Requests carry the delegated identity of the requesting agent, so the remote dataspace's access control + arbitrates every operation
    • +
    +

    In the user interface this means you can browse a remote dataspace and + add data to its documents the same way as to local ones, provided the remote access control allows it.

    Content negotiation

    @@ -274,9 +304,10 @@ Content-Type: text/turtle

    Caching

    GET and HEAD RDF responses from the backend triplestores (not LinkedDataHub responses) are cached automatically by LinkedDataHub using Varnish as HTTP proxy cache. You can check the age of the response by inspecting the Age response header (the value is in seconds).

    -

    LinkedDataHub sends ETag response headers that are derived as hashes of the requested document's RDF content. Every serialization format (HTML, RDF/XML, Turtle etc.) gets a distinct ETag value.

    +

    LinkedDataHub sends ETag response headers that are derived as hashes of the requested document's RDF content. Every serialization format (HTML, RDF/XML, Turtle etc.) gets a distinct ETag value, and language-negotiated HTML representations factor the accepted languages into the tag.

    +

    HTML responses carry a Content-Language negotiated against the languages the UI ships, and advertise Vary: Accept-Language so shared caches keep the renderings apart.

    Caching of LinkedDataHub responses can be enabled on the nginx HTTP proxy server by uncommenting the add_header Cache-Control directives in the platform/nginx.conf.template file. - Caching of /uploads/ and /static/ namespaces is enabled by default (since version 4.0.4).

    + Caching of /uploads/ and /static/ namespaces is enabled by default.

    """^^rdf:XMLLiteral ; rdfs:seeAlso . diff --git a/docs/reference/imports.ttl b/docs/reference/imports.ttl index ce44380..2e9b62c 100644 --- a/docs/reference/imports.ttl +++ b/docs/reference/imports.ttl @@ -12,6 +12,21 @@ <#content> a ldh:XHTML ; rdf:value """

    Types of data imports supported by LinkedDataHub

    +
    +

    Mapping queries

    +

    Both import types use a user-defined SPARQL CONSTRUCT query to map the incoming data to the final RDF representation and to connect it to the documents in LinkedDataHub's dataset. These are the rules that hold for mapping queries:

    +
      +
    • BASE value is automatically set to the imported file's URI. Do not add an explicit BASE to the query.
    • +
    • $base binding is set to the value of the application's baseURI
    • +
    • use OPTIONAL for optional values (such as optional cell values in CSV)
    • +
    • use BIND() to introduce new values and/or cast literals to the appropriate result datatype or URI
    • +
    • when building document URIs, use natural IDs from the input data (or UUIDs if there are no IDs) and remember to URI-encode them using encode_for_uri
    • +
    • use a GRAPH block in the constructor template to construct triples for a specific document
    • +
    • construct dh:Container instances to create new container documents or dh:Item instances to create new item documents. dct:title values are mandatory for documents.
    • +
    • if you're constructing non-information resource (e.g. thing, concept) descriptions, assign them URIs with fragment identified (e.g. #this) and pair them with item documents using the foaf:primaryTopic property
    • +
    +

    We are planning to provide a UI-based mapping tool in the future.

    +
    """^^rdf:XMLLiteral . <#select-children> a ldh:Object ; diff --git a/docs/reference/imports/csv.ttl b/docs/reference/imports/csv.ttl index 8db4eec..16eab8e 100644 --- a/docs/reference/imports/csv.ttl +++ b/docs/reference/imports/csv.ttl @@ -72,18 +72,7 @@ _:e8f2e8e9-3d02-4bf5-b4f1-4794ba5b52c9

    Vocabulary conversion

    This step provides a semantic "lift" for the generic RDF output of the previous step by mapping it to classes and properties from specific vocabularies. It also connects instances in the imported data to the documents in LinkedDataHub's dataset.

    -

    These are the rules that hold for mapping queries:

    -
      -
    • BASE value is automatically set to the imported file's URI. Do not add an explicit BASE to the query.
    • -
    • $base binding is set to the value of the application's baseURI
    • -
    • useOPTIONAL for optional cell values
    • -
    • use BIND() to introduce new values and/or cast literals to the appropriate result datatype or URI
    • -
    • when building document URIs, use natural IDs from the input data (or UUIDs if there are no IDs) and remember to URI-encode them using encode_for_uri
    • -
    • use a GRAPH block in the constructor template to construct triples for a specific document
    • -
    • construct dh:Container instances to create new container documents or dh:Item instances to create new item documents. dct:title values are mandatory for documents.
    • -
    • if you're constructing non-information resource (e.g. thing, concept) descriptions, assign them URIs with fragment identified (e.g. #this) and pair them with item documents using the foaf:primaryTopic property
    • -
    -

    We are planning to provide a UI-based mapping tool in the future.

    +

    The mapping is a user-defined SPARQL CONSTRUCT that follows the shared mapping query rules.

    Example

    In this example we produce a SKOS concept paired with its item (document) for each country:

    diff --git a/docs/reference/imports/rdf.ttl b/docs/reference/imports/rdf.ttl index 68cd42e..1cd44cc 100644 --- a/docs/reference/imports/rdf.ttl +++ b/docs/reference/imports/rdf.ttl @@ -62,18 +62,7 @@

    Transformation

    This step is used to transform the RDF data that is being imported, if necessary (to a different vocabulary, for example). It also connects instances in the imported data to the documents in LinkedDataHub's dataset.

    -

    The mapping is a user-defined SPARQL CONSTRUCT. These are the rules that hold for mapping queries:

    -
      -
    • BASE value is automatically set to the imported file's URI. Do not add an explicit BASE to the query.
    • -
    • $base binding is set to the value of the application's baseURI
    • -
    • useOPTIONAL for optional values
    • -
    • use BIND() to introduce new values and/or cast literals to the appropriate result datatype or URI
    • -
    • when building document URIs, use natural IDs from the input data (or UUIDs if there are no IDs) and remember to URI-encode them using encode_for_uri
    • -
    • use a GRAPH block in the constructor template to construct triples for a specific document
    • -
    • construct dh:Container instances to create new container documents or dh:Item instances to create new item documents. dct:title values are mandatory for documents.
    • -
    • if you're constructing non-information resource (e.g. thing, concept) descriptions, assign them URIs with fragment identified (e.g. #this) and pair them with item documents using the foaf:primaryTopic property
    • -
    -

    We plan to provide a UI-based mapping tool in the future.

    +

    The mapping is a user-defined SPARQL CONSTRUCT that follows the shared mapping query rules.

    Example

    In this example we pair each SKOS concept from the imported dataset with a new document:

    diff --git a/docs/reference/stylesheets.ttl b/docs/reference/stylesheets.ttl index 9f30f32..6b4ec0c 100644 --- a/docs/reference/stylesheets.ttl +++ b/docs/reference/stylesheets.ttl @@ -85,19 +85,19 @@ ixsl: http://saxonica.com/ns/interactiveXSLT - Namespace for the Interactive XSL extensions + Namespace for the Interactive XSL extensions bs2: http://graphity.org/xsl/bootstrap/2.3.2 - XSLT-only namespace that is used for Bootstrap 2.3.2-based layout templates + XSLT-only namespace that is used for layout templates. Historically Bootstrap 2.3.2-based; the namespace is retained for compatibility although the UI has moved to the design system xhtml: - http://www.w3.org/2011/http# + http://www.w3.org/1999/xhtml - XSLT-only namespace that is used for generic (X)HTML templates + XHTML namespace, also used for generic (X)HTML templates ldt: @@ -114,7 +114,7 @@ lapp: https://w3id.org/atomgraph/linkeddatahub/apps# - LinkedDataHub application ontology + LinkedDataHub application ontology LinkedDataHub application concepts @@ -385,7 +385,7 @@ using the document() XSLT function. To avoid XSLT errors on any possible error responses, it is advisable to do a conditional check using the doc-available() function before doing the actual document() call.

    For example, instead of hardcoding the title of this document as Stylesheets, we can use the following code to load it and output it on the fly:

    -
    <xsl:value-of select="key('resources', 'https://docs.linkeddatahub.com/reference/stylesheet/', document('https://docs.linkeddatahub.com/reference/stylesheet/'))"/>
    +
    <xsl:value-of select="key('resources', 'https://docs.linkeddatahub.com/reference/stylesheets/', document('https://docs.linkeddatahub.com/reference/stylesheets/'))"/>

    In case this document changes its title, all such references would automatically render the updated title. On the other hand, it incurs the overhead of making an HTTP request.

    LinkedDataHub's default stylesheets are using this feature extensively. In fact, one HTML page is rendered from a dozen of RDF/XML documents.

    Built-in ontologies as well as some other system and well-known ontologies, have a local copy in diff --git a/docs/reference/user-interface.ttl b/docs/reference/user-interface.ttl index d99a1c5..1db28c7 100644 --- a/docs/reference/user-interface.ttl +++ b/docs/reference/user-interface.ttl @@ -28,7 +28,7 @@ Navigation bar

    The application title or logo in the top-left always links to the root container of the current application.

    -

    The address bar in the middle accepts the URI of a resource to navigate to. Entering an external http:// or https:// URL loads it through the Linked Data proxy so that it can be browsed within LinkedDataHub.

    +

    The address bar in the middle accepts the URI of a resource to navigate to. Entering an external http:// or https:// URL loads it through the Linked Data proxy so that it can be browsed within LinkedDataHub.

    On the right, dropdown menus provide access to the list of applications and to your user account.

    Due to current web browser limitations, it is not possible to logout using client certificate authentication. @@ -46,8 +46,9 @@

    The path leading from the current document up the parent/child hierarchy to the root container is shown in the breadcrumb bar, where the current document is always the last breadcrumb. The user can always open any of the ascendant containers by clicking breadcrumbs left from the current one. The icon shows the - type of the current document (container or item). A label is displayed when the URL currently being browsed is external.

    + type of the current document (container or item). A label is displayed when the URL currently being browsed is external.

    Further to the right, the action bar displays buttons for performing actions on the current document.

    +

    In dataspaces with versioning enabled, the action bar also shows the document's last-modified timestamp. Clicking it opens the version history dialog, which lists the document's versions with the agents that authored them.

    Last but not least, the settings button provides a link to the administration application.

    @@ -84,7 +85,7 @@
    Backlinks
    In properties mode, lists resources that link to a resource in the current document — that is, resources that have a property whose value is the current resource. This complements the outgoing properties shown in the main view.
    Related results
    -
    For view result sets, parallax navigation lets you jump from the current results to a related set by following a selected property (for example, from a set of products to the companies that supply them). It works together with the view's faceted search.
    +
    For view result sets, parallax navigation lets you jump from the current results to a related set by following a selected property (for example, from a set of products to the companies that supply them). It works together with the view's faceted search.
    @@ -109,7 +110,7 @@

    Document tabs

    LinkedDataHub navigates within a single page. When you follow a link to an external document (for example a resource fetched through the - Linked Data proxy), it opens in its own tab so that several documents can be kept open side by side. + Linked Data proxy), it opens in its own tab so that several documents can be kept open side by side. A sticky tab bar appears at the top of the page once the first external tab is opened.

    Each tab keeps its own content pane, and modal dialogs (such as creation and edit forms) are scoped to the active tab pane. Switching between tabs is cached, so returning to a previously opened tab does not re-fetch it. Closing a tab removes its pane and falls back to the base document's tab.

    @@ -124,4 +125,9 @@
    Creates new resource of the selected type
    +
    +

    Languages

    +

    The language of the user interface is negotiated from the browser's language preferences (the Accept-Language header) against the languages the UI translation bundle ships. There is no in-app language switcher — the browser setting is the single source of the language preference. If none of the preferred languages are available, the UI falls back to English.

    +

    Data is language-aware as well: property values are ordered by the reader's language preference rather than filtered by it, so values in other languages remain visible, each displaying its own language tag.

    +
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/reference/versioning.ttl b/docs/reference/versioning.ttl new file mode 100644 index 0000000..99e8edf --- /dev/null +++ b/docs/reference/versioning.ttl @@ -0,0 +1,115 @@ +@prefix ldh: . +@prefix rdf: . +@prefix dh: . +@prefix dct: . + +<> a dh:Item ; + dct:title "Versioning" ; + dct:description "Git-backed document versioning with Memento access to the history" ; + rdf:_1 <#content> . + +<#content> a ldh:XHTML ; + rdf:value """
    +

    Git-backed document versioning with Memento access to the history

    +

    A dataspace can be configured to mirror every document write into a GitHub repository. Each document is stored as a sorted + N-Triples file and every write becomes a commit authored with the writing agent's WebID, so the repository holds a complete, + attributable history of the dataspace's documents.

    +

    The history is accessible over HTTP through the standard Memento (RFC 7089) + protocol — historical versions, a TimeMap, and datetime negotiation through a TimeGate — as well as through the + version history dialog in the user interface and the + command line interface.

    +
    +

    Setup

    +

    Versioning is enabled per dataspace:

    +
      +
    1. Create a GitHub repository to hold the version history, and a fine-grained personal access token with read/write access to that repository's contents
    2. +
    3. In config/system.trig, point the application at a repository description with lapp:versioningRepository: +
      <urn:linkeddatahub:apps/end-user>
      +{
      +    <urn:linkeddatahub:apps/end-user> a lapp:EndUserApplication ;
      +        ldt:service <urn:linkeddatahub:services/end-user> ;
      +        lapp:versioningRepository <urn:linkeddatahub:versioning/end-user> .
      +}
      +
      +<urn:linkeddatahub:versioning/end-user>
      +{
      +    <urn:linkeddatahub:versioning/end-user> a doap:GitRepository ;
      +        doap:location <https://github.com/OWNER/REPO> ;
      +        github:branch "main" ;
      +        github:pathPrefix "graphs" .
      +}
      +
    4. +
    5. Add the access token to secrets/credentials.trig (and make sure the credentials secret is enabled in docker-compose.yml): +
      <urn:linkeddatahub:versioning/end-user>
      +{
      +    <urn:linkeddatahub:versioning/end-user> a:authToken "github_pat_..." .
      +}
      +
    6. +
    7. Restart LinkedDataHub
    8. +
    +

    From then on, every successful document write in the dataspace is committed to the repository. Writes from different documents + share one commit chain per branch, and a conflicting commit is retried rather than dropped, so concurrent writers do not lose versions.

    +
    +
    +

    Historical versions

    +

    GET with the ?version=<commit-sha> query parameter serves the document as it was at that commit. The + response carries a Memento-Datetime header with the commit datetime, the commit SHA as the ETag, and immutable + Cache-Control — a historical version never changes.

    +

    Snapshots are read-only: only acl:Read is advertised in the Link headers and write methods answer + 405 Method Not Allowed. In the user interface a snapshot page shows a notice banner with a link back to the current version.

    +
    +
    +

    TimeMap

    +

    GET with the ?timemap query parameter serves the document's version history. In RDF formats the TimeMap is described + with PROV-O: a prov:Collection of prov:Entity mementos, each + prov:specializationOf the document, prov:generatedAtTime its commit datetime, and prov:wasRevisionOf its + predecessor. Requesting application/link-format serves the serialization RFC 7089 requires.

    +

    A TimeMap of a document with no versions answers 404 Not Found, and ordinary documents answer 406 Not Acceptable + for application/link-format.

    +

    The Memento hypermedia uses the IANA-registered relation types: the document advertises its timemap and timegate in + Link headers, a historical version links back to the document with rel=original, and the TimeMap identifies itself with + rel=self.

    +
    +
    +

    TimeGate

    +

    GET with the ?timegate query parameter performs datetime negotiation: the version closest to the + Accept-Datetime request header is answered with 302 Found and its URI in Location, with ties resolved + towards the more recent version. Without Accept-Datetime, the most recent version is selected. The response carries + Vary: accept-datetime.

    +
    +
    +

    Restore and diff

    +

    From the version history dialog, a document can be restored to an earlier version — the + restore rolls forward as a new commit, so the versions rolled past stay in the TimeMap — and any two versions can be compared as a diff + rendered on the document page.

    +
    +
    +

    Management

    +
    +

    Command line interface

    +

    ldh get reaches a versioned document's Memento roles through mutually exclusive options:

    + + + + + + + + + + + + + + + + + + + + + +
    OptionDescription
    --timemapThe version history (PROV-O in RDF formats, or application/link-format via --accept)
    --version <sha>The historical version at the given commit
    --timegateDatetime negotiation; the optional --datetime takes an RFC 1123 or ISO 8601 datetime. Prints the negotiated version's URI as the only line on stdout, so it pipes into another ldh get
    +
    +
    +
    """^^rdf:XMLLiteral . diff --git a/docs/ttl-to-html.xsl b/docs/ttl-to-html.xsl index 0691883..174750a 100644 --- a/docs/ttl-to-html.xsl +++ b/docs/ttl-to-html.xsl @@ -222,7 +222,8 @@ - + +

    From file

    diff --git a/docs/user-guide/browse-data.ttl b/docs/user-guide/browse-data.ttl index e4d2704..3316b76 100644 --- a/docs/user-guide/browse-data.ttl +++ b/docs/user-guide/browse-data.ttl @@ -2,18 +2,70 @@ @prefix rdf: . @prefix dh: . @prefix dct: . - + <> a dh:Item ; dct:title "Browse data" ; - dct:description "Use LinkedDataHub's built-in Linked Data browser to explore remote datasources" ; + dct:description "Browse, navigate and search local and remote data" ; rdf:_1 <#content> . - + <#content> a ldh:XHTML ; rdf:value """
    -

    Use LinkedDataHub's built-in Linked Data browser to explore remote datasources

    -

    LinkedDataHub's has a built-in Linked Data browser which is accessed through the navigation bar. Enter a http:// or https:// URL and - press Enter or click the search button.

    -

    The browser supports all standard RDF formats as well as JSON-LD embedded in HTML page's <script> elements, which is often used to publish schema.org metadata.

    -

    When the data loads successfully, you can navigate it the same way as your local documents, switch between different layout modes etc. You can also add the data into your dataspace.

    -

    If an RDF document cannot be read from the supplied URL, an error message will be shown.

    -
    """^^rdf:XMLLiteral . \ No newline at end of file +

    Browse, navigate and search local and remote data

    +

    + +

    +
    +

    Linked Data browser

    +

    LinkedDataHub has a built-in Linked Data browser which is accessed through the navigation bar. Enter a http:// or https:// URL and + press Enter or click the search button.

    +

    The browser supports all standard RDF formats as well as JSON-LD embedded in HTML page's <script> elements, which is often used to publish schema.org metadata.

    +

    When the data loads successfully, you can navigate it the same way as your local documents, switch between different layout modes etc. You can also add the data into your dataspace.

    +

    If an RDF document cannot be read from the supplied URL, an error message will be shown.

    +
    +
    +

    Document tree

    +

    Document tree is a widget that enables quick overview of the application's document hierarchy. Each document is shown as a node in the tree; container documents + can be expanded to reveal the children documents.

    +

    The widget can be accessed by sliding the mouse to the left edge of the screen (on responsive layouts, it is always visible).

    +

    + Document tree +

    +
    +
    +

    Class-based navigation

    +

    In addition to navigating by document hierarchy, LinkedDataHub lets you navigate by class. The Classes list in the left sidebar shows the classes (rdf:type values) that occur in your data, each labelled with the number of its instances and ordered with the most-used classes first.

    +

    Using class-based navigation

    +
      +
    1. Open the left sidebar (slide the mouse to the left edge on desktop, or it is always visible on responsive layouts)
    2. +
    3. Find the Classes list
    4. +
    5. Click on any class to open a dialog listing all instances of that class in your dataspace
    6. +
    +

    When to use class navigation

    +

    Class-based navigation is most useful when:

    +
      +
    • Finding all instances of a type: Quickly locate all resources of a particular class, regardless of where they're stored in the document hierarchy
    • +
    • Exploring domain concepts: Browse your data organized by business concepts (e.g., all Customers, all Orders) rather than by container structure
    • +
    • Cross-container queries: View instances that span multiple containers in a single unified view
    • +
    +

    The classes list is derived from the data itself, so it reflects whatever types are present — no ontology needs to be defined. It respects your access control settings — you'll only see classes and instances you're authorized to access.

    +
    +
    + +

    Backlinks are shown in the right-side navigation for every resource in property layout mode. + They display a list of resources which have properties with the current resource as the object.

    +
    +
    + +

    You can lookup resources by typing a phrase (it does not have to be complete, start with a few letters) into the search box at the top of the left sidebar.

    +

    A dropdown list will appear if there are any matches. Use up/down keys or mouse click to select one of the results, and you will be redirected to its document.

    +

    The matching is done by looking for substrings using SPARQL regex() in common literal properties such as dct:title, rdfs:label, foaf:name etc. You can find the exact query in Queries / Select labelled.

    +

    The same widget is used for autocomplete inputs in the create/edit forms.

    +

    + Search +

    +
    +
    + +

    You can use SPARQL to query data from the application's SPARQL service.

    +
    +
    """^^rdf:XMLLiteral . diff --git a/docs/user-guide/navigate-data/DocTree.gif b/docs/user-guide/browse-data/DocTree.gif similarity index 100% rename from docs/user-guide/navigate-data/DocTree.gif rename to docs/user-guide/browse-data/DocTree.gif diff --git a/docs/user-guide/search-data/search.png b/docs/user-guide/browse-data/search.png similarity index 100% rename from docs/user-guide/search-data/search.png rename to docs/user-guide/browse-data/search.png diff --git a/docs/user-guide/build-apps.ttl b/docs/user-guide/build-apps.ttl index a2a1012..cc73b62 100644 --- a/docs/user-guide/build-apps.ttl +++ b/docs/user-guide/build-apps.ttl @@ -13,12 +13,13 @@

    Using LinkedDataHub as a low-code platform for Knowledge Graph applications

    Every component in LinkedDataHub is data-driven and was designed with extensibility in mind. You can override behavior (e.g. Java method or XSLT template) without having to modify LinkedDataHub's codebase, and more importantly, without having to write the same logic from scratch.

    +

    If you are building your first app, start with the tutorial — this guide covers the layers beyond it.

    The following sections are split by component/layer and explain how to extend them when building bespoke apps.

    Docker

    You can go a long way just by mounting files (e.g. config files, ontologies, stylesheets) into LinkedDataHub's default Docker setup. But you may also want to build a dedicated Docker image for your app using LinkedDataHub as the base. Usually this is done to COPY files inside the image or RUN additional commands.

    The recommended approach is a multi-stage Docker build: the first stage uses Maven to compile your WAR overlay, and the second stage copies it into the LinkedDataHub base image alongside any static files:

    -
    FROM maven:3.8.4-openjdk-17 AS maven
    +        
    FROM maven:3.9-eclipse-temurin-21 AS maven
     
     WORKDIR /usr/src/myapp
     
    @@ -29,7 +30,7 @@ RUN mvn clean install
     
     # =================
     
    -FROM atomgraph/linkeddatahub:5.3.2
    +FROM atomgraph/linkeddatahub:5.10.0
     
     WORKDIR $CATALINA_HOME
     
    @@ -50,13 +51,13 @@ COPY files/style.css  /usr/local/tomcat/webapps/ROOT/static/com/example/myapp/cs
         <dependency>
             <groupId>com.atomgraph</groupId>
             <artifactId>linkeddatahub</artifactId>
    -        <version>5.5.3</version>
    +        <version>5.10.0</version>
             <classifier>classes</classifier>
         </dependency>
         <dependency>
             <groupId>com.atomgraph</groupId>
             <artifactId>linkeddatahub</artifactId>
    -        <version>5.5.3</version>
    +        <version>5.10.0</version>
             <type>war</type>
         </dependency>
     </dependencies>
    @@ -108,7 +109,7 @@ COPY files/style.css  /usr/local/tomcat/webapps/ROOT/static/com/example/myapp/cs
         

    RDF

    You can modify or extend the default RDF datasets used by LinkedDataHub. However, a better practice is to use the - CLI scripts to create documents and to import CSV and RDF data.

    + CLI to create documents and to import CSV and RDF data.

    See the LinkedDataHub app repository for more examples.

    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/user-guide/change-layout.ttl b/docs/user-guide/change-layout.ttl index 7fe2496..396b674 100644 --- a/docs/user-guide/change-layout.ttl +++ b/docs/user-guide/change-layout.ttl @@ -21,27 +21,17 @@

    Configure the dataspace

    -
    - -
    -
    -
    -

    First, either upload the XSLT file or mount it using Docker and docker-compose.override.yml:

    -
    version: "2.3"
    -services:
    +        
    +

    First, either upload the XSLT file or mount it using Docker and docker-compose.override.yml:

    +
    services:
       linkeddatahub:
         volumes:
           - ../LinkedDataHub-Apps/demo/skos/files/skos.xsl:/usr/local/tomcat/webapps/ROOT/static/com/atomgraph/linkeddatahub/demo/skos/xsl/index.xsl:ro
    -

    Then change the value of ac:stylesheet on the dataspace with base URI https://localhost:4443/ to the relative URI of the stylesheet:

    -
    <urn:linkeddatahub:apps/end-user> a lapp:EndUserApplication ;
    +            

    Then change the value of ac:stylesheet on the dataspace with base URI https://localhost:4443/ to the URL of the stylesheet. The value has to be an absolute URL on the application's own origin:

    +
    <urn:linkeddatahub:apps/end-user> a lapp:EndUserApplication ;
         ...
    -    ac:stylesheet <static/com/atomgraph/linkeddatahub/demo/skos/xsl/index.xsl> ;
    +    ac:stylesheet <https://localhost:4443/static/com/atomgraph/linkeddatahub/demo/skos/xsl/index.xsl> ;
         ...
    -
    -
    -
    diff --git a/docs/user-guide/change-model.ttl b/docs/user-guide/change-model.ttl index 3c44600..28ffd4f 100644 --- a/docs/user-guide/change-model.ttl +++ b/docs/user-guide/change-model.ttl @@ -85,16 +85,18 @@ WHERE {}
    pwd=$(realpath -s $PWD)
    +base="https://localhost:4443/"
    +admin_base="https://admin.localhost:4443/"
     
    -create-construct.sh \\
    -  -b "${base}admin/" \\
    -  -f ./ssl/owner/cert.pem \\
    +ldh admin ontologies add-constructor \\
    +  -b "$admin_base" \\
    +  -f ./ssl/owner/keystore.p12 \\
       -p "$owner_cert_password" \\
       --uri "${base}ns#ConstructConcept" \\
       --label "Construct concept" \\
    -  --slug construct-concept \\
       --query-file "${pwd}/queries/construct-concept.rq" \\
    -  "${base}admin/model/ontologies/namespace/"
    + "${admin_base}ontologies/namespace/" +

    The model lives in the administration application, whose origin is the dataspace origin with an admin. prefix — so -b and the target document URL both use $admin_base, while the ontology terms being defined keep the end-user origin.

    @@ -134,15 +136,14 @@ create-construct.sh \\
    -
    create-property-constraint.sh \\
    -  -b "$base" \\
    -  -f ./ssl/owner/cert.pem \\
    +                        
    ldh admin ontologies add-property-constraint \\
    +  -b "$admin_base" \\
    +  -f ./ssl/owner/keystore.p12 \\
       -p "$owner_cert_password" \\
    -  --uri "https://localhost:4443/ns#MissingPrefLabel" \\
    +  --uri "${base}ns#MissingPrefLabel" \\
       --label "Missing skos:prefLabel" \\
    -  --slug missing-pref-label \\
       --property "http://www.w3.org/2004/02/skos/core#prefLabel" \\
    -  "${base}admin/model/ontologies/namespace/"
    + "${admin_base}ontologies/namespace/"
    @@ -180,16 +181,15 @@ create-construct.sh \\
    -
    create-class.sh \\
    -  -b "$base" \\
    -  -f ./ssl/owner/cert.pem \\
    +                        
    ldh admin ontologies add-class \\
    +  -b "$admin_base" \\
    +  -f ./ssl/owner/keystore.p12 \\
       -p "$owner_cert_password" \\
       --uri "http://www.w3.org/2004/02/skos/core#Concept" \\
       --label "Concept" \\
    -  --slug concept \\
    -  --constructor "{$base}ns#ConstructConcept" \\
    -  --constraint "{$base}ns#MissingPrefLabel" \\
    -  "${base}admin/model/ontologies/namespace/"
    + --constructor "${base}ns#ConstructConcept" \\ + --constraint "${base}ns#MissingPrefLabel" \\ + "${admin_base}ontologies/namespace/"
    diff --git a/docs/user-guide/create-data/create-content.ttl b/docs/user-guide/create-data/create-content.ttl index 7201683..b0fa419 100644 --- a/docs/user-guide/create-data/create-content.ttl +++ b/docs/user-guide/create-data/create-content.ttl @@ -46,19 +46,14 @@
    1. Make sure you're in the content layout mode
    2. At the bottom of the page, click the XHTML button
    3. -
    4. Add content in the WYSIWYG editor
    5. +
    6. Add content in the rich-text editor
    7. Click the Save button to save the block

    After this, a new XHTML block should be appended to the page, replacing the form.

    Edit block

    -

    To edit an XHTML block, follow these steps:

    -
      -
    1. Move the mouse to the very top-right of the block element where an editing button should appear
    2. -
    3. Make changes in the WYSIWYG editor that appears
    4. -
    5. Click Save to save the block
    6. -
    +

    To edit an XHTML block, click anywhere inside it and make changes in place. The changes are saved automatically when the focus leaves the block — there is no separate save button.

    To remove an XHTML block, follow these steps:

    1. Move the mouse to the very top-right of the block element where an editing button should appear
    2. @@ -69,13 +64,13 @@

      To add a new XHTML block with the value <p>A paragraph</p>, replace owner_cert_password with the value of the corresponding secret and execute the following command:

      -
      content/add-xhtml-block.sh \\
      -    -b "https://localhost:4443/" \\
      -    -f ./ssl/owner/cert.pem \\
      +                        
      ldh content add-xhtml-block \\
      +    -f ./ssl/owner/keystore.p12 \\
           -p "$owner_cert_password" \\
      -    --fragment "xhtml-block" \\
      -    --value "<div xmlns=\\\\\\"http://www.w3.org/1999/xhtml\\\\\\"><p>A paragraph</p></div>" \\
      +    --uri "#xhtml-block" \\
      +    --value '<div xmlns="http://www.w3.org/1999/xhtml"><p>A paragraph</p></div>' \\
           "https://localhost:4443/concepts/example/"
      +

      --uri is resolved against the document URI, so #xhtml-block identifies the block within it. Leave it out to let the block be a blank node.

    @@ -129,11 +124,10 @@

    To add a new object block with the value http://dbpedia.org/resource/Copenhagen, replace $owner_cert_password with the value of the corresponding secret and execute the following command:

    -
    content/add-object-block.sh \\
    -    -b "https://localhost:4443/" \\
    -    -f ./ssl/owner/cert.pem \\
    +                        
    ldh content add-object-block \\
    +    -f ./ssl/owner/keystore.p12 \\
         -p "$owner_cert_password" \\
    -    --fragment "object-block" \\
    +    --uri "#object-block" \\
         --value "http://dbpedia.org/resource/Copenhagen" \\
         "https://localhost:4443/concepts/example/"
    @@ -141,10 +135,5 @@
    -

    Read more about content blocks.

    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/user-guide/create-data/create-documents.ttl b/docs/user-guide/create-data/create-documents.ttl index 897aad1..58d900e 100644 --- a/docs/user-guide/create-data/create-documents.ttl +++ b/docs/user-guide/create-data/create-documents.ttl @@ -44,9 +44,9 @@

    Replace owner_cert_password with the value of the corresponding secret and execute the following command:

    -
    create-container.sh \\
    +                    
    ldh create-container \\
       -b "https://localhost:4443/" \\
    -  -f ./ssl/owner/cert.pem \\
    +  -f ./ssl/owner/keystore.p12 \\
       -p "$owner_cert_password" \\
       --title "Concepts" \\
       --slug "concepts" \\
    diff --git a/docs/user-guide/edit-content.ttl b/docs/user-guide/edit-content.ttl
    index 2ebb68f..5cecbce 100644
    --- a/docs/user-guide/edit-content.ttl
    +++ b/docs/user-guide/edit-content.ttl
    @@ -14,7 +14,7 @@
         

    XHTML content blocks in LinkedDataHub are living documents: authenticated users with write access can click directly into the content to edit it, apply rich-text formatting, and annotate any span of text with RDFa — linking words and phrases to terms in the Knowledge Graph without leaving the page.

    Entering edit mode

    -

    Click anywhere inside an XHTML block to enter edit mode. The block gains a focus outline and the editor toolbar appears above the action bar. Clicking outside the block (including the surrounding column margins) returns the block to read mode.

    +

    Click anywhere inside an XHTML block to enter edit mode. The block gains a focus outline and the editor toolbar appears above the action bar. Clicking outside the block (including the surrounding column margins) returns the block to read mode.

    Edit mode is only available to authenticated users who have write access to the document. The block remains read-only for all other visitors.

    diff --git a/docs/user-guide/import-data/import-csv-data.ttl b/docs/user-guide/import-data/import-csv-data.ttl index 95e1c9a..9d95490 100644 --- a/docs/user-guide/import-data/import-csv-data.ttl +++ b/docs/user-guide/import-data/import-csv-data.ttl @@ -89,14 +89,25 @@
    -

    Checkout the Command line interface (CLI) scripts into a folder on your machine. Provide a list of arguments to the import-csv script and execute it. For example:

    -
    import-csv.sh \\
    -    -b "https://localhost:4443/" \\
    -    -f ./ssl/owner/cert.pem \\
    +                    

    Build the command line interface (CLI) and put it on your $PATH. The import is described by a document, which the command takes as its last argument, so create that document first. For example:

    +
    base="https://localhost:4443/"
    +
    +doc=$(ldh create-item \\
    +    -b "$base" \\
    +    -f ./ssl/owner/keystore.p12 \\
    +    -p "$owner_cert_password" \\
    +    --title "Places" \\
    +    --container "$base")
    +
    +ldh imports import-csv \\
    +    -b "$base" \\
    +    -f ./ssl/owner/keystore.p12 \\
         -p "$owner_cert_password" \\
         --title "Places" \\
         --query-file places.rq \\
    -    --csv-file places.csv
    + --csv-file places.csv \\ + "$doc"
    +

    --delimiter defaults to , and can be set to another character.

    diff --git a/docs/user-guide/import-data/import-rdf-data.ttl b/docs/user-guide/import-data/import-rdf-data.ttl index f23331a..6b0bab9 100644 --- a/docs/user-guide/import-data/import-rdf-data.ttl +++ b/docs/user-guide/import-data/import-rdf-data.ttl @@ -79,15 +79,25 @@
    -

    Checkout the Command line interface (CLI) scripts into a folder on your machine. Provide a list of arguments to the import-rdf script and execute it. For example:

    -
    import-rdf.sh \\
    -    -b "https://localhost:4443/" \\
    -    -f ./ssl/owner/cert.pem \\
    +                        

    Build the command line interface (CLI) and put it on your $PATH. The import is described by a document, which the command takes as its last argument, so create that document first. For example:

    +
    base="https://localhost:4443/"
    +
    +doc=$(ldh create-item \\
    +    -b "$base" \\
    +    -f ./ssl/owner/keystore.p12 \\
    +    -p "$owner_cert_password" \\
    +    --title "Concepts" \\
    +    --container "$base")
    +
    +ldh imports import-rdf \\
    +    -b "$base" \\
    +    -f ./ssl/owner/keystore.p12 \\
         -p "$owner_cert_password" \\
         --title "Concepts" \\
         --rdf-file concepts.ttl \\
         --content-type "text/turtle" \\
    -    --graph "${base}skos/"
    + --graph "${base}skos/" \\ + "$doc"
    @@ -159,15 +169,25 @@
    -

    Checkout the Command line interface (CLI) scripts into a folder on your machine. Provide a list of arguments to the import-rdf script and execute it. For example:

    -
    import-rdf.sh \\
    -    -b "https://localhost:4443/" \\
    -    -f ./ssl/owner/cert.pem \\
    +                        

    Build the command line interface (CLI) and put it on your $PATH. The import is described by a document, which the command takes as its last argument, so create that document first. For example:

    +
    base="https://localhost:4443/"
    +
    +doc=$(ldh create-item \\
    +    -b "$base" \\
    +    -f ./ssl/owner/keystore.p12 \\
    +    -p "$owner_cert_password" \\
    +    --title "Concepts" \\
    +    --container "$base")
    +
    +ldh imports import-rdf \\
    +    -b "$base" \\
    +    -f ./ssl/owner/keystore.p12 \\
         -p "$owner_cert_password" \\
         --title "Concepts" \\
         --rdf-file concepts.ttl \\
         --content-type "text/turtle" \\
    -    --query-file concepts.rq
    + --query-file concepts.rq \\ + "$doc"
    diff --git a/docs/user-guide/manage-dataspaces.ttl b/docs/user-guide/manage-dataspaces.ttl index 2d8db45..f9557ef 100644 --- a/docs/user-guide/manage-dataspaces.ttl +++ b/docs/user-guide/manage-dataspaces.ttl @@ -23,7 +23,7 @@ application and an administrative application, both backed by their own SPARQL service. Each application can also specify its own XSLT stylesheet.

    See the configuration reference for more details about these files.

    -

    Application base URIs need to be relative to the system base URI configured in the .env file. A change of system base URI currently requires a change of application base URIs, otherwise they will not be reachable.

    +

    Application base URIs need to be relative to the system base URI configured in the .env file. A change of system base URI currently requires a change of application base URIs, otherwise they will not be reachable.

    Create a dataspace

    @@ -41,7 +41,7 @@

    Change stylesheet

    -

    Change the value of ac:stylesheet to the URI of your XSLT stylesheet. Add the property if it is absent.

    +

    Change the value of ac:stylesheet to the URI of your XSLT stylesheet. Add the property if it is absent. The value has to be an absolute URL on the application's own origin.

    The stylesheet can either be uploaded as a file or mounted in docker-compose.yml, in the volumes section of the linkeddatahub service. Mounting is useful while developing.

    You will need to restart LinkedDataHub's Docker service for the new stylesheet to take effect.

    It is rarely necessary to change the stylesheet of an admin application.

    diff --git a/docs/user-guide/manage-dataspaces/CreateContextDataspace.webm b/docs/user-guide/manage-dataspaces/CreateContextDataspace.webm deleted file mode 100644 index e799fd4..0000000 Binary files a/docs/user-guide/manage-dataspaces/CreateContextDataspace.webm and /dev/null differ diff --git a/docs/user-guide/manage-packages.ttl b/docs/user-guide/manage-packages.ttl index 8e49902..4ac8a83 100644 --- a/docs/user-guide/manage-packages.ttl +++ b/docs/user-guide/manage-packages.ttl @@ -12,20 +12,18 @@ rdf:value """

    Step by step guide to installing and uninstalling packages in your LinkedDataHub dataspace.

    -

    Version: Packages were introduced in LinkedDataHub 5.2.

    +

    Version: Packages were introduced in LinkedDataHub 5.2. Since 5.10 they are installed declaratively: an application imports a package with a single ldh:import triple in its settings. The dedicated install/uninstall endpoints and the package Actions menu they were driven from are gone.

    -

    Package management requires Control access to the administration application. Only users with administrative privileges can install or uninstall packages.

    +

    Package management requires write access to the application's settings document, which in practice means the owner of the dataspace.

    Read more about how packages work.

    -
    -

    Important: After installing or uninstalling a package, you must restart the Docker service for XSLT stylesheet changes to take effect:

    -
    docker-compose restart linkeddatahub
    -

    Do not use --force-recreate as that would overwrite the stylesheet file changes.

    +
    +

    Installing or uninstalling a package takes effect on the next request — the package ontology joins the application's imports closure and its stylesheet is composed into the application stylesheet. No restart is needed.

    Install a package

    -

    Installing a package adds new functionality to your dataspace by importing ontologies, data, and resources.

    +

    Installing a package adds new functionality to your dataspace by importing its ontology and stylesheet.

    Uninstall a package

    -

    Uninstalling a package removes its ontologies and resources from your dataspace. User-created data that uses the package vocabulary will remain but may not function correctly.

    +

    Uninstalling a package removes its ontology and stylesheet from your dataspace. Data you created using the package vocabulary remains but may not display or function correctly without the package.

    Verify package installation

    -

    After installing a package, verify that it was installed correctly:

    +

    After installing a package, verify that it was applied:

      -
    1. Navigate to the administration application
    2. -
    3. Click on Ontologies in the left navigation menu
    4. -
    5. Verify that the package ontology appears in the list of imported ontologies
    6. -
    7. In the end-user application, check that package-specific classes and properties are available when creating new resources
    8. -
    9. If the package includes stylesheets, verify that custom UI elements are rendering correctly
    10. +
    11. Reopen the Application settings modal and check that the package's Installed checkbox is ticked
    12. +
    13. Check that the ldh:import declaration was stored by fetching the settings document: +
      ldh get \\
      +  -f ssl/owner/keystore.p12 \\
      +  -p "$owner_cert_password" \\
      +  --accept text/turtle \\
      +  "https://localhost:4443/settings"
      +

      The output contains an ldh:import triple with the package URI as its object.

      +
    14. +
    15. In the end-user application, check that package-specific classes are offered by the Create dropdown
    16. +
    17. If the package includes a stylesheet, verify that its custom UI elements are rendering

    Customize package behavior

    Packages may include custom XSLT stylesheets that affect how data is displayed. You can further customize the behavior by:

    See the Change layout guide for detailed instructions on stylesheet customization.

    @@ -122,19 +131,18 @@

    Troubleshooting

    Installation fails

    -

    If package installation fails:

    +

    If the Save or the ldh patch is rejected:

      -
    • Verify you have Control access to the administration application
    • -
    • Check that all package dependencies are met
    • -
    • Review the error message for specific issues
    • -
    • Ensure the package files are valid and well-formed
    • +
    • Verify you are logged in with an agent that has write access to the application's settings
    • +
    • Verify the package URI is correct and dereferenceable, and that its metadata declares ldt:ontology and ac:stylesheet
    • +
    • Review the error message — a rejected settings update reports the constraint violation that caused it

    Package functionality not visible

    If installed package features are not visible:

      -
    • Refresh your browser to reload the application
    • +
    • Reload the page — the stylesheet is composed on the next request, but the browser may still hold the previous one
    • Clear the browser cache if stylesheets are not updating
    • -
    • Verify the package ontology is imported in the Ontologies section
    • +
    • Verify the package ontology is in the imports closure by querying the ns endpoint as shown above
    • Check that you have appropriate permissions to view package resources
    diff --git a/docs/user-guide/navigate-data.ttl b/docs/user-guide/navigate-data.ttl deleted file mode 100644 index aeecff4..0000000 --- a/docs/user-guide/navigate-data.ttl +++ /dev/null @@ -1,49 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Navigate data" ; - dct:description "Navigate the document hierarchy, related results, and backlinks" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Navigate the document hierarchy, related results, and backlinks

    -

    - -

    -
    -

    Document tree

    -

    Document tree is a widget that enables quick overview of the application's document hierarchy. Each document is shown as a node in the tree; container documents - can be expanded to reveal the children documents.

    -

    The widget can be accessed by sliding the mouse to the left edge of the screen (on responsive layouts, it is always visible).

    -

    - Document tree -

    -
    -
    -

    Class-based navigation

    -

    In addition to navigating by document hierarchy, LinkedDataHub lets you navigate by class. The Classes list in the left sidebar shows the classes (rdf:type values) that occur in your data, each labelled with the number of its instances and ordered with the most-used classes first.

    -

    Using class-based navigation

    -
      -
    1. Open the left sidebar (slide the mouse to the left edge on desktop, or it is always visible on responsive layouts)
    2. -
    3. Find the Classes list
    4. -
    5. Click on any class to open a dialog listing all instances of that class in your dataspace
    6. -
    -

    When to use class navigation

    -

    Class-based navigation is most useful when:

    -
      -
    • Finding all instances of a type: Quickly locate all resources of a particular class, regardless of where they're stored in the document hierarchy
    • -
    • Exploring domain concepts: Browse your data organized by business concepts (e.g., all Customers, all Orders) rather than by container structure
    • -
    • Cross-container queries: View instances that span multiple containers in a single unified view
    • -
    -

    The classes list is derived from the data itself, so it reflects whatever types are present — no ontology needs to be defined. It respects your access control settings — you'll only see classes and instances you're authorized to access.

    -
    -
    - -

    Backlinks are shown in the right-side navigation for every resource in property layout mode. - They display a list of resources which have properties with the current resource as the object.

    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/user-guide/query-data.ttl b/docs/user-guide/query-data.ttl index 973668a..7a564b4 100644 --- a/docs/user-guide/query-data.ttl +++ b/docs/user-guide/query-data.ttl @@ -11,8 +11,9 @@ <#content> a ldh:XHTML ; rdf:value """

    This guide describes how the application dataset can be queried using SPARQL.

    -

    The end-user application dataset can be queried using SPARQL 1.1 - endpoint, which is available via link in the navigation bar.

    +

    The end-user application dataset can be queried using the SPARQL 1.1 + endpoint at the application's sparql URL (e.g. https://localhost:4443/sparql). Opening it in the browser renders an + interactive SPARQL editor.

    All forms of SPARQL queries are allowed; SPARQL updates are not allowed. A result limit might apply.

    For DESCRIBE and CONSTRUCT results, it is possible to switch the layout mode using a button above to the results.

    diff --git a/docs/user-guide/search-data.ttl b/docs/user-guide/search-data.ttl deleted file mode 100644 index e52adf3..0000000 --- a/docs/user-guide/search-data.ttl +++ /dev/null @@ -1,28 +0,0 @@ -@prefix ldh: . -@prefix rdf: . -@prefix dh: . -@prefix dct: . - -<> a dh:Item ; - dct:title "Search data" ; - dct:description "Search for resources using text keywords" ; - rdf:_1 <#content> . - -<#content> a ldh:XHTML ; - rdf:value """
    -

    Search for resources using text keywords

    -
    - -

    You can lookup resources by typing a phrase (it does not have to be complete, start with a few letters) into the search box at the top of the left sidebar.

    -

    A dropdown list will appear if there are any matches. Use up/down keys or mouse click to select one of the results, and you will be redirected to its document.

    -

    The matching is done by looking for substrings using SPARQL regex() in common literal properties such as dct:title, rdfs:label, foaf:name etc. You can find the exact query in Queries / Select labelled.

    -

    The same widget is used for autocomplete inputs in the create/edit forms.

    -

    - SPARQL endpoint -

    -
    -
    - -

    You can use SPARQL to query data from the application's SPARQL service.

    -
    -
    """^^rdf:XMLLiteral . \ No newline at end of file diff --git a/docs/user-guide/upload-file.ttl b/docs/user-guide/upload-file.ttl index f3347fd..6682779 100644 --- a/docs/user-guide/upload-file.ttl +++ b/docs/user-guide/upload-file.ttl @@ -40,14 +40,25 @@
    -

    Replace owner_cert_password with its value from the .env file and execute the following command:

    -
    add-file.sh \\
    --b "https://localhost:4443/" \\
    --f ./ssl/owner/cert.pem \\
    --p "$owner_cert_password" \\
    ---title "$title" \\
    ---file "$filename" \\
    ---content-type "$content_type"
    +

    A file is uploaded to the document that will hold it, so create that document first. Replace owner_cert_password with the value of the corresponding secret and execute the following commands:

    +
    base="https://localhost:4443/"
    +
    +doc=$(ldh create-item \\
    +  -b "$base" \\
    +  -f ./ssl/owner/keystore.p12 \\
    +  -p "$owner_cert_password" \\
    +  --title "$title" \\
    +  --container "$base")
    +
    +ldh add-file \\
    +  -b "$base" \\
    +  -f ./ssl/owner/keystore.p12 \\
    +  -p "$owner_cert_password" \\
    +  --title "$title" \\
    +  --file "$filename" \\
    +  --content-type "$content_type" \\
    +  "$doc"
    +

    The command prints the content-addressed URI of the upload. --content-type is optional — the media type is detected from the file when it is not given.

    diff --git a/docs/user-guide/version-history.ttl b/docs/user-guide/version-history.ttl new file mode 100644 index 0000000..ed54757 --- /dev/null +++ b/docs/user-guide/version-history.ttl @@ -0,0 +1,45 @@ +@prefix ldh: . +@prefix rdf: . +@prefix dh: . +@prefix dct: . + +<> a dh:Item ; + dct:title "View version history" ; + dct:description "View, compare and restore document versions" ; + rdf:_1 <#content> . + +<#content> a ldh:XHTML ; + rdf:value """
    +

    View, compare and restore document versions

    +
    +

    Version history is available in dataspaces with versioning enabled.

    +
    +
    +

    Open the history

    +

    Click the document's last-modified timestamp in the action bar. The + History dialog opens with the document's versions listed newest first, each showing its datetime and the agent who authored it. + The version you are currently viewing is marked.

    +

    Click a version to view the document as it was at that point. A notice banner reminds you that you are viewing a historical version and + links back to the current one. Historical versions are read-only.

    +
    +
    +

    Compare versions

    +

    In the History dialog, select a From and a To version to compare. The diff renders on the document page:

    +
      +
    • added, removed and changed parts are marked with distinct borders, explained by a color legend
    • +
    • changed property values are marked individually
    • +
    • a changed XHTML block stacks its old content above the new
    • +
    +

    The diff is display state read from the URL — going back and forward re-renders it, and reloading the page falls back to the plain + historical version.

    +
    +
    +

    Restore a version

    +

    In the History dialog, click Restore on the version you want to bring back and confirm. The version's + content is written to the live document, so the restore rolls forward as a new version — nothing is rewritten and the versions rolled past + stay in the history.

    +

    Restoring requires write access to the document; the button is not offered on the version you are currently viewing.

    +
    +

    Read more about versioning and the Memento protocol or access the history from the + command line interface.

    +
    """^^rdf:XMLLiteral . diff --git a/packages/skos/layout.xsl b/packages/skos/layout.xsl index bea9ee2..94c1fc6 100644 --- a/packages/skos/layout.xsl +++ b/packages/skos/layout.xsl @@ -50,12 +50,6 @@ exclude-result-prefixes="#all"> - - - - - - \ No newline at end of file