diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml deleted file mode 100644 index d525375c..00000000 --- a/.github/workflows/publish-docs.yml +++ /dev/null @@ -1,63 +0,0 @@ - name: Publish Docs - - on: - push: - branches: - - master - paths: - - 'docs/**' - workflow_dispatch: - - jobs: - publish-docs: - name: Transform and publish documentation - runs-on: ubuntu-latest - env: - ASF_ARCHIVE: https://archive.apache.org/dist/ - JENA_VERSION: 6.0.0 - - steps: - - name: Checkout LinkedDataHub-Apps - uses: actions/checkout@v4 - - - name: Set up Java 21 - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: '21' - - - name: Install Linux packages - run: sudo apt-get update && sudo apt-get install -y xmlstarlet - - - name: Download Apache Jena - run: | - curl -sS --fail \ - "${{ env.ASF_ARCHIVE }}jena/binaries/apache-jena-${{ env.JENA_VERSION }}.tar.gz" \ - -o "${{ runner.temp }}/jena.tar.gz" - - - name: Unpack Jena - run: tar -zxf jena.tar.gz - working-directory: ${{ runner.temp }} - - - name: Add Jena bin to PATH - run: echo "${{ runner.temp }}/apache-jena-${{ env.JENA_VERSION }}/bin" >> $GITHUB_PATH - - - name: Pull Saxon Docker image - run: docker pull atomgraph/saxon - - - name: Run documentation transformation - run: ./ttl-to-html.sh - working-directory: docs - - - name: Deploy to gh-pages - uses: peaceiris/actions-gh-pages@v4 - with: - personal_token: ${{ secrets.GH_PAGES_TOKEN }} - external_repository: AtomGraph/LinkedDataHub - publish_branch: gh-pages - publish_dir: docs/html - destination_dir: linkeddatahub/docs - keep_files: true - user_name: 'github-actions[bot]' - user_email: 'github-actions[bot]@users.noreply.github.com' - commit_message: 'Update docs from LinkedDataHub-Apps@${{ github.sha }}' diff --git a/demo/northwind-traders/admin/model/ns.ttl b/demo/northwind-traders/admin/model/ns.ttl index 1cb72a95..6879dfe7 100644 --- a/demo/northwind-traders/admin/model/ns.ttl +++ b/demo/northwind-traders/admin/model/ns.ttl @@ -7,6 +7,8 @@ @prefix spin: . @prefix dct: . @prefix schema: . +@prefix foaf: . +@prefix geo: . : a owl:Ontology . @@ -85,7 +87,7 @@ schema:Place a owl:Class ; # constructors and constraints schema:Product spin:constructor :ProductConstructor ; - spin:constraint :MissingName . + spin:constraint :MissingName, :MissingIdentifier, :MissingDescription, :MissingCategory, :MissingProvider . :ProductConstructor a ldh:Constructor ; rdfs:label "Product constructor" ; @@ -96,8 +98,7 @@ schema:Product spin:constructor :ProductConstructor ; PREFIX xsd: CONSTRUCT { - $this dct:title [ a xsd:string ] ; - schema:name [ a xsd:string ] ; + $this schema:name [ a xsd:string ] ; schema:identifier [ a xsd:string ] ; schema:description [ a xsd:string ] ; schema:category [ a schema:ProductGroup ] ; @@ -107,7 +108,8 @@ schema:Product spin:constructor :ProductConstructor ; WHERE {}""" ; rdfs:isDefinedBy : . -schema:City spin:constructor :CityConstructor . +schema:City spin:constructor :CityConstructor ; + spin:constraint :MissingName, :MissingIdentifier, :MissingContainedInPlace, :MissingLatitude, :MissingLongitude . :CityConstructor a ldh:Constructor ; rdfs:label "Territory constructor" ; @@ -119,8 +121,7 @@ schema:City spin:constructor :CityConstructor . PREFIX xsd: CONSTRUCT { - $this dct:title [ a xsd:string ] ; - schema:name [ a xsd:string ] ; + $this schema:name [ a xsd:string ] ; schema:identifier [ a xsd:string ] ; geo:lat [ a xsd:float ] ; geo:long [ a xsd:float ] ; @@ -129,7 +130,8 @@ schema:City spin:constructor :CityConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:Person spin:constructor :PersonConstructor . +schema:Person spin:constructor :PersonConstructor ; + spin:constraint :MissingName, :MissingJobTitle, :MissingTelephone, :MissingAddress . :PersonConstructor a ldh:Constructor ; rdfs:label "Person constructor" ; @@ -144,7 +146,6 @@ schema:Person spin:constructor :PersonConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; schema:identifier [ a xsd:string ] ; schema:name [ a xsd:string ] ; schema:givenName [ a xsd:string ] ; @@ -165,7 +166,8 @@ schema:Person spin:constructor :PersonConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:Corporation spin:constructor :CorporationConstructor . +schema:Corporation spin:constructor :CorporationConstructor ; + spin:constraint :MissingName, :MissingIdentifier, :MissingLegalName . :CorporationConstructor a ldh:Constructor ; rdfs:label "Company constructor" ; @@ -177,7 +179,7 @@ schema:Corporation spin:constructor :CorporationConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:legalName [ a xsd:string ] ; schema:identifier [ a xsd:string ] ; schema:telephone [ a xsd:string ] ; @@ -187,7 +189,8 @@ schema:Corporation spin:constructor :CorporationConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:ContactPoint spin:constructor :ContactPointConstructor . +schema:ContactPoint spin:constructor :ContactPointConstructor ; + spin:constraint :MissingTelephone . :ContactPointConstructor a ldh:Constructor ; rdfs:label "Contact point constructor" ; @@ -206,7 +209,8 @@ schema:ContactPoint spin:constructor :ContactPointConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:PostalAddress spin:constructor :PostalAddressConstructor . +schema:PostalAddress spin:constructor :PostalAddressConstructor ; + spin:constraint :MissingStreetAddress, :MissingAddressLocality, :MissingAddressCountry . :PostalAddressConstructor a ldh:Constructor ; rdfs:label "Postal address constructor" ; @@ -227,7 +231,8 @@ schema:PostalAddress spin:constructor :PostalAddressConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:ProductGroup spin:constructor :ProductGroupConstructor . +schema:ProductGroup spin:constructor :ProductGroupConstructor ; + spin:constraint :MissingName, :MissingIdentifier, :MissingDescription, :MissingDepiction . :ProductGroupConstructor a ldh:Constructor ; rdfs:label "Category constructor" ; @@ -241,7 +246,6 @@ schema:ProductGroup spin:constructor :ProductGroupConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; schema:name [ a xsd:string ] ; schema:description [ a xsd:string ] ; schema:identifier [ a xsd:string ] ; @@ -250,7 +254,8 @@ schema:ProductGroup spin:constructor :ProductGroupConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:Place spin:constructor :PlaceConstructor . +schema:Place spin:constructor :PlaceConstructor ; + spin:constraint :MissingName, :MissingIdentifier . :PlaceConstructor a ldh:Constructor ; rdfs:label "Region constructor" ; @@ -262,14 +267,14 @@ schema:Place spin:constructor :PlaceConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; schema:name [ a xsd:string ] ; schema:identifier [ a xsd:string ] . } WHERE {}""" ; rdfs:isDefinedBy : . -schema:Order spin:constructor :OrderConstructor . +schema:Order spin:constructor :OrderConstructor ; + spin:constraint :MissingName, :MissingIdentifier, :MissingOrderDate, :MissingCustomer, :MissingBroker, :MissingAcceptedOffer, :MissingOrderDelivery, :MissingOrderStatus . :OrderConstructor a ldh:Constructor ; rdfs:label "Order constructor" ; @@ -281,7 +286,7 @@ schema:Order spin:constructor :OrderConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:identifier [ a xsd:string ] ; schema:orderDate [ a xsd:date ] ; schema:orderStatus schema:OrderProcessing ; @@ -294,7 +299,8 @@ schema:Order spin:constructor :OrderConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:OrderItem spin:constructor :OrderItemConstructor . +schema:OrderItem spin:constructor :OrderItemConstructor ; + spin:constraint :MissingOrderedItem, :MissingOrderQuantity . :OrderItemConstructor a ldh:Constructor ; rdfs:label "Order item constructor" ; @@ -316,7 +322,8 @@ schema:OrderItem spin:constructor :OrderItemConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:ParcelDelivery spin:constructor :ParcelDeliveryConstructor . +schema:ParcelDelivery spin:constructor :ParcelDeliveryConstructor ; + spin:constraint :MissingPartOfOrder, :MissingProvider, :MissingDeliveryAddress, :MissingExpectedArrivalUntil . :ParcelDeliveryConstructor a ldh:Constructor ; rdfs:label "Delivery constructor" ; @@ -337,7 +344,8 @@ schema:ParcelDelivery spin:constructor :ParcelDeliveryConstructor . WHERE {}""" ; rdfs:isDefinedBy : . -schema:DeliveryEvent spin:constructor :DeliveryEventConstructor . +schema:DeliveryEvent spin:constructor :DeliveryEventConstructor ; + spin:constraint :MissingName, :MissingStartDate . :DeliveryEventConstructor a ldh:Constructor ; rdfs:label "Shipment constructor" ; @@ -349,7 +357,7 @@ schema:DeliveryEvent spin:constructor :DeliveryEventConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:startDate [ a xsd:date ] . } WHERE {}""" ; @@ -367,7 +375,7 @@ schema:Offer spin:constructor :OfferConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:itemOffered [ a schema:Product ] ; schema:price [ a xsd:float ] ; schema:priceCurrency [ a xsd:string ] ; @@ -388,14 +396,15 @@ schema:PriceSpecification spin:constructor :PriceSpecificationConstructor . CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:price [ a xsd:float ] ; schema:priceCurrency [ a xsd:string ] . } WHERE {}""" ; rdfs:isDefinedBy : . -schema:UnitPriceSpecification spin:constructor :UnitPriceSpecificationConstructor . +schema:UnitPriceSpecification spin:constructor :UnitPriceSpecificationConstructor ; + spin:constraint :MissingName, :MissingPrice, :MissingPriceCurrency, :MissingPriceType . :UnitPriceSpecificationConstructor a ldh:Constructor ; rdfs:label "Unit price constructor" ; @@ -407,14 +416,16 @@ schema:UnitPriceSpecification spin:constructor :UnitPriceSpecificationConstructo CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:price [ a xsd:float ] ; - schema:priceCurrency [ a xsd:string ] . + schema:priceCurrency [ a xsd:string ] ; + schema:priceType schema:ListPrice . } WHERE {}""" ; rdfs:isDefinedBy : . -schema:DeliveryChargeSpecification spin:constructor :DeliveryChargeSpecificationConstructor . +schema:DeliveryChargeSpecification spin:constructor :DeliveryChargeSpecificationConstructor ; + spin:constraint :MissingName, :MissingPrice, :MissingPriceCurrency, :MissingAppliesToDeliveryMethod . :DeliveryChargeSpecificationConstructor a ldh:Constructor ; rdfs:label "Delivery charge constructor" ; @@ -426,18 +437,184 @@ schema:DeliveryChargeSpecification spin:constructor :DeliveryChargeSpecification CONSTRUCT { $this - dct:title [ a xsd:string ] ; + schema:name [ a xsd:string ] ; schema:price [ a xsd:float ] ; - schema:priceCurrency [ a xsd:string ] . + schema:priceCurrency [ a xsd:string ] ; + schema:appliesToDeliveryMethod schema:ParcelService . } WHERE {}""" ; rdfs:isDefinedBy : . +# A property is required of a class when every import query that types an instance of that +# class binds it on every row, in the same row graph: the imports write through the same +# validating API as the forms, so a constraint the CSV cannot meet would reject the row. +# Every required property is in its class's constructor, so the create form can always be +# completed: schema:orderStatus, schema:priceType and schema:appliesToDeliveryMethod take +# schema.org enumeration members the dataspace does not hold, so their constructors fill in +# the value the data always uses rather than offering a lookup that would find nothing. +# schema:offers is on every imported product but is not required: the form can only link an +# existing Offer, and every Offer belongs to another product, so a new product is priced after. + :MissingName a ldh:MissingPropertyValue ; rdfs:label "Missing schema:name" ; sp:arg1 schema:name ; rdfs:isDefinedBy : . +:MissingAcceptedOffer a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:acceptedOffer" ; + sp:arg1 schema:acceptedOffer ; + rdfs:isDefinedBy : . + +:MissingAddress a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:address" ; + sp:arg1 schema:address ; + rdfs:isDefinedBy : . + +:MissingAddressCountry a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:addressCountry" ; + sp:arg1 schema:addressCountry ; + rdfs:isDefinedBy : . + +:MissingAddressLocality a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:addressLocality" ; + sp:arg1 schema:addressLocality ; + rdfs:isDefinedBy : . + +:MissingBroker a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:broker" ; + sp:arg1 schema:broker ; + rdfs:isDefinedBy : . + +:MissingCategory a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:category" ; + sp:arg1 schema:category ; + rdfs:isDefinedBy : . + +:MissingContainedInPlace a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:containedInPlace" ; + sp:arg1 schema:containedInPlace ; + rdfs:isDefinedBy : . + +:MissingCustomer a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:customer" ; + sp:arg1 schema:customer ; + rdfs:isDefinedBy : . + +:MissingDeliveryAddress a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:deliveryAddress" ; + sp:arg1 schema:deliveryAddress ; + rdfs:isDefinedBy : . + +:MissingDepiction a ldh:MissingPropertyValue ; + rdfs:label "Missing foaf:depiction" ; + sp:arg1 foaf:depiction ; + rdfs:isDefinedBy : . + +:MissingDescription a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:description" ; + sp:arg1 schema:description ; + rdfs:isDefinedBy : . + +:MissingExpectedArrivalUntil a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:expectedArrivalUntil" ; + sp:arg1 schema:expectedArrivalUntil ; + rdfs:isDefinedBy : . + +:MissingIdentifier a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:identifier" ; + sp:arg1 schema:identifier ; + rdfs:isDefinedBy : . + +:MissingJobTitle a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:jobTitle" ; + sp:arg1 schema:jobTitle ; + rdfs:isDefinedBy : . + +:MissingLatitude a ldh:MissingPropertyValue ; + rdfs:label "Missing geo:lat" ; + sp:arg1 geo:lat ; + rdfs:isDefinedBy : . + +:MissingLegalName a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:legalName" ; + sp:arg1 schema:legalName ; + rdfs:isDefinedBy : . + +:MissingLongitude a ldh:MissingPropertyValue ; + rdfs:label "Missing geo:long" ; + sp:arg1 geo:long ; + rdfs:isDefinedBy : . + +:MissingOrderDate a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:orderDate" ; + sp:arg1 schema:orderDate ; + rdfs:isDefinedBy : . + +:MissingOrderDelivery a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:orderDelivery" ; + sp:arg1 schema:orderDelivery ; + rdfs:isDefinedBy : . + +:MissingOrderQuantity a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:orderQuantity" ; + sp:arg1 schema:orderQuantity ; + rdfs:isDefinedBy : . + +:MissingOrderedItem a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:orderedItem" ; + sp:arg1 schema:orderedItem ; + rdfs:isDefinedBy : . + +:MissingPartOfOrder a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:partOfOrder" ; + sp:arg1 schema:partOfOrder ; + rdfs:isDefinedBy : . + +:MissingOrderStatus a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:orderStatus" ; + sp:arg1 schema:orderStatus ; + rdfs:isDefinedBy : . + +:MissingPriceType a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:priceType" ; + sp:arg1 schema:priceType ; + rdfs:isDefinedBy : . + +:MissingAppliesToDeliveryMethod a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:appliesToDeliveryMethod" ; + sp:arg1 schema:appliesToDeliveryMethod ; + rdfs:isDefinedBy : . + +:MissingPrice a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:price" ; + sp:arg1 schema:price ; + rdfs:isDefinedBy : . + +:MissingPriceCurrency a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:priceCurrency" ; + sp:arg1 schema:priceCurrency ; + rdfs:isDefinedBy : . + +:MissingProvider a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:provider" ; + sp:arg1 schema:provider ; + rdfs:isDefinedBy : . + +:MissingStartDate a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:startDate" ; + sp:arg1 schema:startDate ; + rdfs:isDefinedBy : . + +:MissingStreetAddress a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:streetAddress" ; + sp:arg1 schema:streetAddress ; + rdfs:isDefinedBy : . + +:MissingTelephone a ldh:MissingPropertyValue ; + rdfs:label "Missing schema:telephone" ; + sp:arg1 schema:telephone ; + rdfs:isDefinedBy : . + # properties schema:broker a owl:ObjectProperty ; diff --git a/demo/northwind-traders/categories/categories.rq b/demo/northwind-traders/categories/categories.rq index 801ccb9b..8b24b560 100644 --- a/demo/northwind-traders/categories/categories.rq +++ b/demo/northwind-traders/categories/categories.rq @@ -10,8 +10,6 @@ CONSTRUCT foaf:primaryTopic ?category . ?category a schema:ProductGroup ; - dct:title ?categoryName ; - dct:description ?description ; schema:name ?categoryName ; schema:identifier ?categoryID ; schema:description ?description ; diff --git a/demo/northwind-traders/customers/customers.rq b/demo/northwind-traders/customers/customers.rq index 5f177992..217fe982 100644 --- a/demo/northwind-traders/customers/customers.rq +++ b/demo/northwind-traders/customers/customers.rq @@ -14,7 +14,7 @@ CONSTRUCT ?customer a schema:Corporation ; schema:identifier ?customerID ; - dct:title ?companyName ; + schema:name ?companyName ; schema:legalName ?companyName ; schema:contactPoint ?contactPoint ; schema:employee ?employee . @@ -27,7 +27,6 @@ CONSTRUCT ?employee a schema:Person ; foaf:page ?graph ; schema:name ?contactName ; - dct:title ?contactName ; schema:jobTitle ?contactTitle ; schema:telephone ?phone ; schema:faxNumber ?fax ; diff --git a/demo/northwind-traders/employees/employees.rq b/demo/northwind-traders/employees/employees.rq index 5c0f75fd..e27e744d 100644 --- a/demo/northwind-traders/employees/employees.rq +++ b/demo/northwind-traders/employees/employees.rq @@ -14,8 +14,7 @@ CONSTRUCT ?employee a schema:Person ; schema:identifier ?employeeID ; - dct:title ?lastName ; - dct:description ?title ; + schema:name ?employeeName ; schema:givenName ?firstName ; schema:familyName ?lastName ; schema:jobTitle ?title ; @@ -71,6 +70,7 @@ WHERE BIND(uri(concat(str($base), "employees/")) AS ?container) BIND(uri(concat(str(?container), encode_for_uri(?employeeID), "/")) AS ?graph) BIND(uri(concat(str(?graph), "#this")) AS ?employee) + BIND(concat(?firstName, " ", ?lastName) AS ?employeeName) BIND(uri(concat(str(?graph), "#address")) AS ?postalAddress) BIND(xsd:float(?lat_string) AS ?lat) BIND(xsd:float(?long_string) AS ?long) diff --git a/demo/northwind-traders/orders/order_details.rq b/demo/northwind-traders/orders/order_details.rq index 89f8e7e0..33f47ff8 100644 --- a/demo/northwind-traders/orders/order_details.rq +++ b/demo/northwind-traders/orders/order_details.rq @@ -32,7 +32,7 @@ CONSTRUCT # 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:name "List price" ; schema:priceType schema:ListPrice ; schema:price ?price ; schema:priceCurrency "USD" . diff --git a/demo/northwind-traders/orders/orders.rq b/demo/northwind-traders/orders/orders.rq index 9db16ff7..7ffd97ae 100644 --- a/demo/northwind-traders/orders/orders.rq +++ b/demo/northwind-traders/orders/orders.rq @@ -14,7 +14,7 @@ CONSTRUCT ?order a schema:Order ; schema:identifier ?orderID ; - dct:title ?orderID ; + schema:name ?orderID ; schema:customer ?customer ; schema:broker ?employee ; schema:orderDate ?orderDate ; @@ -36,12 +36,12 @@ CONSTRUCT # accepted is spelled out as exactly that. ?shippingOffer a schema:Offer ; foaf:page ?graph ; - dct:title "Shipping" ; + schema:name "Shipping" ; schema:priceSpecification ?freightCharge . ?freightCharge a schema:DeliveryChargeSpecification ; foaf:page ?graph ; - dct:title "Freight" ; + schema:name "Freight" ; schema:price ?freight ; schema:priceCurrency "USD" ; schema:appliesToDeliveryMethod schema:ParcelService . @@ -51,7 +51,7 @@ CONSTRUCT # off schema:deliveryStatus; Northwind knows exactly one leg, the dispatch. ?shipment a schema:DeliveryEvent ; foaf:page ?graph ; - dct:title ?shipmentTitle ; + schema:name ?shipmentName ; schema:startDate ?shippedDate . ?deliveryAddress a schema:PostalAddress ; @@ -87,7 +87,7 @@ WHERE <#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) + BIND(concat("Shipped ", ?shippedDateString) AS ?shipmentName) } OPTIONAL { ?order_row <#shipRegion> ?shipRegion diff --git a/demo/northwind-traders/products/products.rq b/demo/northwind-traders/products/products.rq index fa0000a3..d1d6a1d9 100644 --- a/demo/northwind-traders/products/products.rq +++ b/demo/northwind-traders/products/products.rq @@ -13,7 +13,6 @@ CONSTRUCT ?product a schema:Product ; schema:identifier ?productID ; - dct:title ?productName ; schema:name ?productName ; schema:provider ?supplier ; schema:category ?category ; @@ -29,7 +28,7 @@ CONSTRUCT # 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:name "List price" ; schema:itemOffered ?product ; schema:price ?unitPrice ; schema:priceCurrency "USD" . diff --git a/demo/northwind-traders/regions/regions.rq b/demo/northwind-traders/regions/regions.rq index 6e60ce18..4306ac90 100644 --- a/demo/northwind-traders/regions/regions.rq +++ b/demo/northwind-traders/regions/regions.rq @@ -10,7 +10,6 @@ CONSTRUCT foaf:primaryTopic ?region . ?region a schema:Place ; - dct:title ?regionDescription ; schema:name ?regionDescription ; schema:identifier ?regionID . } diff --git a/demo/northwind-traders/shippers/shippers.rq b/demo/northwind-traders/shippers/shippers.rq index 584aafde..9097aa35 100644 --- a/demo/northwind-traders/shippers/shippers.rq +++ b/demo/northwind-traders/shippers/shippers.rq @@ -10,7 +10,7 @@ CONSTRUCT foaf:primaryTopic ?shipper . ?shipper a schema:Corporation ; - dct:title ?companyName ; + schema:name ?companyName ; schema:legalName ?companyName ; schema:identifier ?shipperID ; schema:telephone ?phone . diff --git a/demo/northwind-traders/suppliers/suppliers.rq b/demo/northwind-traders/suppliers/suppliers.rq index da80b63d..421a9978 100644 --- a/demo/northwind-traders/suppliers/suppliers.rq +++ b/demo/northwind-traders/suppliers/suppliers.rq @@ -14,7 +14,7 @@ CONSTRUCT ?supplier a schema:Corporation ; schema:identifier ?supplierID ; - dct:title ?companyName ; + schema:name ?companyName ; schema:legalName ?companyName ; schema:contactPoint ?contactPoint ; schema:employee ?employee . @@ -28,7 +28,6 @@ CONSTRUCT ?employee a schema:Person ; foaf:page ?graph ; schema:name ?contactName ; - dct:title ?contactName ; schema:jobTitle ?contactTitle ; schema:telephone ?phone ; schema:faxNumber ?fax ; diff --git a/demo/northwind-traders/territories/territories.rq b/demo/northwind-traders/territories/territories.rq index bca97887..979d9b60 100644 --- a/demo/northwind-traders/territories/territories.rq +++ b/demo/northwind-traders/territories/territories.rq @@ -12,7 +12,6 @@ CONSTRUCT foaf:primaryTopic ?territory . ?territory a schema:City ; - dct:title ?territoryDescription ; schema:name ?territoryDescription ; schema:identifier ?territoryID ; schema:containedInPlace ?region ; diff --git a/demo/unesco-thesaurus/install.sh b/demo/unesco-thesaurus/install.sh index 7720e49b..b167d565 100755 --- a/demo/unesco-thesaurus/install.sh +++ b/demo/unesco-thesaurus/install.sh @@ -21,12 +21,12 @@ ldh admin make-public ldh_app_step "Creating authorization for authenticated agents to read items" ldh admin create authorization \ - -b "$admin_base" \ --proxy "$admin_proxy" \ --label "Read access to graph items" \ --agent-class "http://www.w3.org/ns/auth/acl#AuthenticatedAgent" \ --to-all-in "https://w3id.org/atomgraph/linkeddatahub/document-hierarchy#Item" \ - --read + --read \ + "$admin_base" ldh_app_step "Pushing documents and files" ldh push --dir "$app_dir" "$LDH_BASE" diff --git a/docs/about.ttl b/docs/about.ttl index 88e527a3..82177e5b 100644 --- a/docs/about.ttl +++ b/docs/about.ttl @@ -41,7 +41,7 @@
  • creating structured content from Linked Data and SPARQL resources
  • creating RDF documents (graphs)
  • browsing Linked Data
  • -
  • forking RDF resources to the local dataspace
  • +
  • copying RDF resources into the local dataspace
  • searching and querying SPARQL endpoints
  • creating collections from SPARQL queries
  • editing content in place, with RDFa annotations linking text to resources
  • diff --git a/docs/get-started/agents.ttl b/docs/get-started/agents.ttl index be8fa780..fb76bb36 100644 --- a/docs/get-started/agents.ttl +++ b/docs/get-started/agents.ttl @@ -72,8 +72,8 @@ uv run --with web-algebra uvicorn web_algebra.server:app

    Try it

    -

    Ask the agent for something small that it has to declare rather than code, for example:

    -

    Create a container for books in my dataspace, add three books with authors, and add a chart of books per author.

    +

    Ask the agent for something small that it has to declare rather than code. With the Northwind Traders demo installed, for example:

    +

    Create a container for new offers in my dataspace, add a 20% discount offer for three Beverages products, and add a chart of the offer prices.

    Then open https://localhost:4443/ in the browser. The container, the items and the chart are there, and you can keep editing them by hand — the agent and you work on the same documents.

    diff --git a/docs/get-started/setup.ttl b/docs/get-started/setup.ttl index 0ec3b8dd..0289504d 100644 --- a/docs/get-started/setup.ttl +++ b/docs/get-started/setup.ttl @@ -66,6 +66,7 @@ OWNER_COUNTRY_NAME=DK ├── config/ # dataspace and service configuration (dataspaces.trig, system.trig) ├── datasets/ # owner and secretary agent metadata ├── fuseki/ # the triplestore's persisted RDF data, one TDB2 directory per dataset +├── settings/ # dataspace settings changed at runtime, applied over config/ on restart ├── ssl/ │ ├── owner/ # your WebID certificate (keystore.p12) │ └── secretary/ # the service agent's certificate diff --git a/docs/reference/administration/packages.ttl b/docs/reference/administration/packages.ttl index 177fd8d0..78d07217 100644 --- a/docs/reference/administration/packages.ttl +++ b/docs/reference/administration/packages.ttl @@ -265,10 +265,13 @@ ldh packages add --package https://packages.linkeddatahub.com/editor/taxonomy/#t ldh packages remove --package https://packages.linkeddatahub.com/editor/taxonomy/#this

    packages list prints one tab-separated line per package — state, URI, title — so the listing greps and cuts. The registry defaults to https://packages.linkeddatahub.com/ and --registry overrides it; it is read through the - dataspace's Linked Data proxy rather than fetched directly, so list needs --base as much as the other two do.

    + dataspace's Linked Data proxy rather than fetched directly, so list needs the dataspace URI as much as the other two do.

    All three go through PATCH on the settings document, which is the live path: the change takes effect on the - next request with no restart, but lives in the running dataspace's context dataset. Declaring the same ldh:import triple - in config/dataspaces.trig is the permanent one, applied on restart. packages remove is idempotent.

    + next request with no restart. The platform records it in settings/dataspaces.ru as a change to the configuration, the + statements it removed and the ones it added, and applies that change over config/dataspaces.trig and + config/system.trig on every restart. A package removed at runtime therefore stays removed, and a later edit to the + configuration files still takes effect. Declaring the same ldh:import triple in config/dataspaces.trig + is the alternative that lives with the rest of the configuration. packages remove is idempotent.

    To install and manage packages step by step, follow the Manage packages guide.

    """^^rdf:XMLLiteral . diff --git a/docs/reference/command-line-interface.ttl b/docs/reference/command-line-interface.ttl index 46adad34..0efd2b0b 100644 --- a/docs/reference/command-line-interface.ttl +++ b/docs/reference/command-line-interface.ttl @@ -94,17 +94,13 @@ ldh --help -p, --cert-password Password of the keystore, or passphrase of an encrypted PEM key. Not needed when the PEM key is unencrypted - - -b, --base - Base URI of the dataspace - --proxy The host the request is proxied through (optional). Use it with port 5443, on which client certificate authentication is always enabled — for example --proxy https://localhost:5443/. -

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

    +

    Other parameters are command-specific. Most commands take the URI of the document they act on as a default (positional) parameter. The commands that act on the dataspace as a whole rather than on one of its documents — packages, admin clear ontology, admin create ontology, admin create group, admin create authorization, admin import ontology and admin make-public — take its base URI in that position instead.

    Environment variables

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

    @@ -126,7 +122,7 @@ ldh --help - + @@ -152,14 +148,13 @@ ldh create container --parent "$LDH_BASE" --title "Categories" --slug categories

    A usage message with the parameters of a command is printed when the command is run without required arguments, or with --help. Commands take named parameters and default (positional) parameters; either kind 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 [-h] [--verbose] [-b=BASE_URI] [-c=CERT_FILE]
    +Usage: ldh add select [-h] [--verbose] [-c=CERT_FILE]
                           [--description=DESCRIPTION] [-p=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)
       -c, --cert=CERT_FILE    PKCS12 keystore or PEM with the WebID certificate and
                                 private key of the agent (env: LDH_CERT_FILE)
           --description=DESCRIPTION
    diff --git a/docs/reference/configuration.ttl b/docs/reference/configuration.ttl
    index 58003850..07563621 100644
    --- a/docs/reference/configuration.ttl
    +++ b/docs/reference/configuration.ttl
    @@ -135,6 +135,7 @@ HOST=ec2-54-235-229-141.compute-1.amazonaws.com
    orcid_client_id
    ORCID OpenID Connect client ID
    Login with ORCID authentication is enabled when this value is provided
    +
    The client is expected to be registered on production ORCID (https://orcid.org). For a client registered on ORCID's sandbox, set the ORCID_ISSUER environment variable to https://sandbox.orcid.org: the authorization, token and key endpoints derive from it, and only ID tokens from that issuer are accepted
    orcid_client_secret
    ORCID OpenID Connect client secret
    credentials
    @@ -231,12 +232,14 @@ HOST=ec2-54-235-229-141.compute-1.amazonaws.com

    HTTP client pool

    -

    The size of the connection pool those clients share. Both are passed as servlet parameters rather than as CATALINA_OPTS system properties.

    +

    The size of the connection pool those clients share, and how often they retry. The entrypoint passes them to the platform as CATALINA_OPTS system properties, like the timeouts above.

    MAX_CONN_PER_ROUTE
    Maximum pooled connections per host. Defaults to 200.
    MAX_TOTAL_CONN
    Maximum pooled connections in total. Defaults to 400.
    +
    MAX_REQUEST_RETRIES
    +
    Maximum number of times a failed request is retried. Defaults to the HTTP client's own retry policy.
    diff --git a/docs/reference/data-model/blocks.ttl b/docs/reference/data-model/blocks.ttl index f2fae135..231fae1c 100644 --- a/docs/reference/data-model/blocks.ttl +++ b/docs/reference/data-model/blocks.ttl @@ -38,6 +38,7 @@

    Object blocks

    Blocks other than XHTML content are called objects and must have a dereferenceable URI. 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 a 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.

    +

    An object can also be a page on the web, which LinkedDataHub reads as data when it loads it. A web page that embeds JSON-LD in a <script type="application/ld+json"> element contributes that data: a product, an article, an event or a recipe the page describes is rendered as a resource. A YouTube video, given by its https://www.youtube.com/watch?v=… or https://youtu.be/… URL, has its metadata translated to RDF on the fly as a schema:VideoObject with its title, channel and thumbnail, and the block shows the video's player. Support for other video and media services is planned. To embed a video or a page from the web, create an object block with the page's URL as its value rather than pasting an <iframe> into an XHTML block: the object block is a resource LinkedDataHub describes and renders, and the iframe is markup it knows nothing about.

    Built-in block types render as cards: a header carries the block's title and actions such as the links popover and the copy-URI button, and the block's content follows below. Block-type-specific controls join the header area — a view block, for example, adds its facet toolbar and parallax row there. The layout of the content may depend on the active mode of the block.

    RDF serialization

    diff --git a/docs/reference/data-model/resources.ttl b/docs/reference/data-model/resources.ttl index bf6bbe50..e4f6b584 100644 --- a/docs/reference/data-model/resources.ttl +++ b/docs/reference/data-model/resources.ttl @@ -222,11 +222,19 @@ ORDER BY DESC(?revenue)''' . The UI renders the descriptions of the resources selected by the result set, rather than the tabular result itself. 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. + The first projected variable, plain or aliased as in (?s AS ?resource), names the resources the view lists. When it binds + values rather than resources, as in (YEAR(NOW()) AS ?year), there is nothing to describe and the block says the query lists no resources. This will not work for all SELECT queries.

    To render paginated lists of resources, traditional 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. A view is therefore a client-side counterpart of a container: it produces a listing without a server-side endpoint.

    +

    A view is defined in one of two ways. A standalone view is a resource like a query or a chart: + it is stored in a document and placed on a page by an object block whose value is its URI. + A property-attached view is declared in the dataspace's ontology with ldh:view or ldh:inverseView. It is stored + in no document and is rendered on every resource of the property's domain or range, as the + ontologies reference describes. Both kinds offer the same pagination, + faceted search and parallax navigation.

    A view references its query and optionally fixes a layout mode:

    <#select-categories-view> a ldh:View ;
         spin:query <#select-categories-query> ;
    diff --git a/docs/reference/stylesheets.ttl b/docs/reference/stylesheets.ttl
    index a095e800..d1275ab7 100644
    --- a/docs/reference/stylesheets.ttl
    +++ b/docs/reference/stylesheets.ttl
    @@ -349,6 +349,7 @@
                         
    + @@ -356,76 +357,91 @@ + - + - + + + + - + + + + - + + - + + + + + + + + @@ -437,6 +453,7 @@ + @@ -444,66 +461,79 @@ + + + + + + + + + + + + + @@ -515,33 +545,33 @@ + - - + + + - + - + + - + - - - - - - + + + @@ -553,6 +583,7 @@ + @@ -560,16 +591,19 @@ + + + @@ -581,6 +615,7 @@ + @@ -588,18 +623,15 @@ + + - - - - -
    LDH_BASE-b, --baseBASE_URI, the default parameter of the commands that act on the dataspace
    LDH_PROXY
    Function ReturnsRuns on Description
    ac:absolute-path($href as xs:anyURI) xs:anyURIBoth Strips query string and fragment from a URI, returning the path-only form
    ac:build-uri(

      $path as xs:anyURI?,

      $params as map(xs:string, xs:string*)

    )
    ac:build-uri(

      $absolute-path as xs:anyURI?,

      $query-params as map(xs:string, xs:string*)

    )
    xs:anyURI?Appends a percent-encoded query string built from the parameter map to $pathBothAppends a percent-encoded query string built from $query-params to $absolute-path
    ac:document-uri($uri as item()) xs:anyURIBoth Strips the fragment identifier from a URI, making it suitable for use with document()
    ac:fragment-id($uri as xs:anyURI) xs:string?Both Extracts the fragment identifier (the part after #)
    ac:uri() xs:anyURI?URI of the external resource being viewed through the Linked Data proxy; absent for local documents. Works in both environments, unlike the server-side $ac:uri parameter it wrapsBothURI of the external resource being viewed through the Linked Data proxy; absent for local documents. The server reads it from the $ac:uri parameter, the browser from the uri query parameter
    lds:origin() xs:anyURIBoth Returns the origin (scheme, host and port) of the current dataspace, e.g. https://localhost:4443/. Used to build absolute URIs for static resources and same-site requests
    lds:base() xs:anyURIBoth Returns the base URI of the current dataspace
    ldh:request-uri() xs:anyURIReturns the full request URI including query string (server-side only)BothReturns the full request URI including query string
    ldh:base-uri($node as node())ldh:base-uri($arg as node()) xs:anyURIBoth Returns the base URI of an XML node; wraps the built-in base-uri()
    ldh:href($uri as xs:anyURI?) xs:anyURIBoth Resolves a URI to a local href, proxying external URIs through the LinkedDataHub proxy. Overloads accept $query-params as map(xs:string, xs:string*) and $fragment as xs:string?
    ldh:parse-href($href as xs:anyURI) map(xs:string, item()?)Both Inverse of ldh:href() — parses a local href back into its target URI, query parameters and fragment. Shared by the client-side navigation handlers
    ldh:query-params() map(xs:string, xs:string*)Both Parses the current request URI's query string into a map of parameter names to values
    ldh:build-query($mode as xs:anyURI*) map(xs:string, xs:string*)Both Builds the mode query parameter map that selects the given layout mode(s)
    ldh:parse-query-params($query-string as xs:string) map(xs:string, xs:string*)Both Parses a URL query string into a map of key to value(s)
    ldh:url-decode($encoded-string as xs:string) xs:stringBoth Percent-decodes a URL-encoded string
    Function ReturnsRuns on Description
    ac:label($resource as element()) xs:string?Both Extracts a human-readable label from an RDF resource description, trying multiple vocabulary properties
    ac:description($resource as element()) xs:string?Both Extracts a human-readable description from an RDF resource description
    ac:image($resource as element()) attribute()*Both Extracts image references (e.g. foaf:depiction, foaf:img) from a resource description
    ac:property-label($property as element()) xs:string?Both Label of a predicate element, capitalized — resolved by the ac:property-label mode templates. An overload accepts $property-metadata as document-node(), the property descriptions loaded for the current document
    ac:object-label($object as node()) xs:string?Both Label of a property's object: a resource-valued object is looked up in the current graph, the loaded metadata or its dereferenced document, falling back to the URI's fragment or last path segment; a literal yields its lexical value. An overload accepts $object-metadata as document-node()
    ac:mode($doc as document-node()) xs:anyURIBoth The active layout mode of the given document. Replaces the former $ac:mode parameter
    ac:langs() xs:string*Both The reader's accepted languages, deduplicated to primary subtags and always ending with en as a fallback — negotiated server-side, read from navigator.languages in the browser
    ac:lang-rank($value as element()) xs:integerBoth Ranks a property value's language against ac:langs(), so values sort by the reader's preference
    ldh:date-time($value as xs:string?) xs:dateTime?Both Parses a lexical value into a xs:dateTime where possible
    ldh:datatype-family($datatype as xs:anyURI?) xs:stringBoth Groups an XSD datatype into a family (numeric, dateTime, string etc.) — drives table column sorting
    ldh:sort-key($key as xs:string?, $datatype as xs:anyURI?) xs:anyAtomicType?Browser Converts a lexical value into a typed sort key for its datatype family
    ldh:sort-key-lexical($resource as element(), $predicates as xs:anyURI*) xs:string?Browser The lexical sort key of a resource: coalesces across $predicates in order, preferring values whose language matches the reader's first accepted language. Feeds ldh:sort-key()
    ldh:sort-datatype($resources as element()*, $predicates as xs:anyURI*) xs:anyURI?Browser The datatype shared by a sort column's literals; absent when the column mixes datatypes or carries plain literals or resources. Keys the typed re-sort that keeps a view block's client-side order agreeing with its SPARQL ORDER BY
    Function ReturnsRuns on Description
    ldh:query-result($endpoint as xs:anyURI, $query as xs:string)document-node()Executes a SPARQL query against the given endpoint and returns the result document (cached)document-node()?BothRuns $query against $endpoint over the SPARQL Protocol and returns the response: RDF/XML for CONSTRUCT and DESCRIBE, SPARQL Results XML for SELECT and ASK. Call it from any stylesheet: each engine has its own implementation. Server-side, it POSTs the query as application/sparql-query through ldh:send-request(), so a long query does not hit a URL length limit; the result is empty when the response has no body. Client-side, it sends a GET and loads the response with document(). An endpoint on another origin is fetched through the Linked Data proxy (?uri=). document() is synchronous, so the page waits for the response. SaxonJS also keeps the document for the rest of the page session, so a repeated query does not reach the network. A failed request raises a dynamic error, so wrap the call in xsl:try when the endpoint may be unavailable. Example: ldh:query-result(sd:endpoint(), 'DESCRIBE <' || @rdf:about || '>')
    ldh:send-request(

      $href as xs:anyURI,

      $method as xs:string,

      $media-type as xs:string?,

      $body as item()?,

      $headers as map(xs:string, xs:string)

    )
    ldh:send-request(

      $href as xs:anyURI,

      $method as xs:string,

      $media-type as xs:string?,

      $body as item()?,

      $headers as map(xs:string, xs:string*)

    )
    document-node()?Makes an HTTP request and returns the response as a document (server-side extension function)ServerSends an HTTP request with any $method and parses the response body as XML; returns an empty sequence when the response has no body. A body is sent only when both $media-type and $body are given, and $body is sent as its string value, so serialize a node before passing it. $headers maps a header name to its value, of which only the first is sent; pass map{} for none. A response status outside 2xx, a timeout or an unparseable body raises a dynamic error that xsl:try can catch. Server-side only: it is the SendHTTPRequest Java extension function and has no browser declaration, so calling it from a stylesheet that also runs in the browser fails to compile. Guard such a call with use-when="system-property('xsl:product-name') = 'SAXON'"; for a SPARQL query, call ldh:query-result() instead, which works on both sides. In the browser, ixsl:http-request() is the asynchronous counterpart. Example: ldh:send-request($href, 'GET', (), (), map{ 'Accept': 'application/rdf+xml' })
    ldh:link-targets($link-header as xs:string?, $marker as xs:string)ldh:link-targets($link-headers as xs:string*, $marker as xs:string) xs:anyURI*Extracts the target URIs of the Link response header entries whose parameters contain $marker (e.g. a rel value) — the parser behind acl:mode(), sd:endpoint() and ldh:timemap() (client-side)
    ldh:parse-html($string as xs:string, $mime-type as xs:string)document-node()Parses a markup string into a documentBothExtracts the target URIs of the Link response header entries, across all Link header values, whose parameters contain $marker (e.g. a rel value) — on the server, acl:mode() and ldh:timemap() read the response's Link header through it; in the browser, it reads each response's Link header into the values that acl:mode(), sd:endpoint() and ldh:timemap() return
    ldh:reserialize($doc as document-node()) document-node()Both Round-trips a document through serialization, normalizing it
    Function ReturnsRuns on Description
    sd:endpoint() xs:anyURIBoth SPARQL endpoint of the current data pane — the local endpoint, or the remote dataspace's when browsing another dataspace through the proxy. Replaces the former $sd:endpoint parameter
    lds:dataspace() xs:anyURI?Browser URI of the dataspace resource describing the current dataspace. Replaces the former $lds:Dataspace parameter (its description is looked up in $lds:Context)
    acl:mode() xs:anyURI*Both Access modes granted on the current document, read from the response's Link headers. Replaces the former $acl:mode parameter
    Function ReturnsRuns on Description
    ldh:memento-datetime() xs:string?Both The Memento-Datetime of the currently viewed historical version, absent on the live document
    ldh:timemap() xs:anyURI?Both The document's TimeMap URI from the Link response header; absent when the document is not versioned
    ldh:snapshot-params($query-params as map(xs:string, xs:string*))map(xs:string, xs:string*)Filters a query parameter map down to the snapshot parameters (version, timemap), so they survive URL rebuilds

    Utility functions

    @@ -609,6 +641,7 @@ Function Returns + Runs on Description @@ -616,22 +649,14 @@ ac:uuid() xs:string + Both Generates a random UUID ldh:hash-code($string as xs:string) xs:integer - Deterministic 32-bit djb2 hash of a string's codepoints — the counterpart to ac:uuid() for identifiers that must survive a reload because they are re-derived rather than stored (client-side) - - - ac:value-intersect($arg1 as xs:anyAtomicType*, $arg2 as xs:anyAtomicType*) - xs:anyAtomicType* - Distinct values present in both sequences - - - ac:value-except($arg1 as xs:anyAtomicType*, $arg2 as xs:anyAtomicType*) - xs:anyAtomicType* - Distinct values of $arg1 that are not present in $arg2 + Browser + Deterministic 32-bit djb2 hash of a string's codepoints — the counterpart to ac:uuid() for identifiers that must survive a reload because they are re-derived rather than stored diff --git a/docs/reference/user-interface-1.webp b/docs/reference/user-interface-1.webp index 993639be..9982fa26 100644 Binary files a/docs/reference/user-interface-1.webp and b/docs/reference/user-interface-1.webp differ diff --git a/docs/reference/user-interface.ttl b/docs/reference/user-interface.ttl index a8966d7d..58a0d0d2 100644 --- a/docs/reference/user-interface.ttl +++ b/docs/reference/user-interface.ttl @@ -21,7 +21,7 @@ actions such as creating or editing documents are only available to users with the corresponding write access.

    - The dataspace layout: navigation bar, action bar, sidebar and content + The dataspace layout: navigation bar, action bar, sidebar and content
    The navigation bar with the address bar and menus diff --git a/docs/reference/versioning.ttl b/docs/reference/versioning.ttl index 3b46174d..e28d6f37 100644 --- a/docs/reference/versioning.ttl +++ b/docs/reference/versioning.ttl @@ -106,14 +106,18 @@ Link: <https://localhost:4443/orders/10248/>; rel="original" prov:wasRevisionOf <https://localhost:4443/orders/10248/?version=4d1e7aa> .

    and the same TimeMap as application/link-format:

    <https://localhost:4443/orders/10248/>;rel="original",
    -<https://localhost:4443/orders/10248/?timemap>;rel="self";type="application/link-format",
    -<https://localhost:4443/orders/10248/?version=4d1e7aa>;rel="memento";datetime="Sun, 30 Aug 2026 10:12:04 GMT",
    -<https://localhost:4443/orders/10248/?version=8c9f2b1>;rel="memento";datetime="Tue, 01 Sep 2026 14:03:59 GMT"
    -

    A TimeMap of a document with no versions answers 404 Not Found. A non-versioned document answers 406 Not Acceptable +<https://localhost:4443/orders/10248/?timemap>;rel="self";type="application/link-format";from="Sun, 30 Aug 2026 10:12:04 GMT";until="Tue, 01 Sep 2026 14:03:59 GMT", +<https://localhost:4443/orders/10248/?timegate>;rel="timegate", +<https://localhost:4443/orders/10248/?version=4d1e7aa>;rel="first memento";datetime="Sun, 30 Aug 2026 10:12:04 GMT", +<https://localhost:4443/orders/10248/?version=8c9f2b1>;rel="last memento";datetime="Tue, 01 Sep 2026 14:03:59 GMT" +

    A document of a versioned dataspace that no write has reached yet, such as imported data or a document that predates versioning being + enabled, has no versions: its TimeMap lists the Original Resource, the TimeMap itself without a from/until + range and the TimeGate, and no Mementos. Its next write makes the first version. A non-versioned document answers 406 Not Acceptable when application/link-format is requested.

    The Memento hypermedia uses 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.

    + Link headers, a historical version links back to the document with rel="original", and the TimeMap identifies itself with + rel="self". Relation types are always quoted, as RFC 8288 requires of a URI relation type such as + rel="http://www.w3.org/ns/auth/acl#mode".

    TimeGate

    diff --git a/docs/tutorial/model.ttl b/docs/tutorial/model.ttl index a56dfefa..b8f34656 100644 --- a/docs/tutorial/model.ttl +++ b/docs/tutorial/model.ttl @@ -58,8 +58,8 @@ schema:Order a owl:Class ; "${admin_base}ontologies/namespace/"

    The ontology is cached in memory, so tell the dataspace to reload it:

    ldh admin clear ontology \\
    -    -b "$admin_base" \\
    -    --ontology "${LDH_BASE}ns#"
    + --ontology "${LDH_BASE}ns#" \\ + "$admin_base"
    diff --git a/docs/user-guide/add-data-1.mp4 b/docs/user-guide/add-data-1.mp4 index 031aa878..43b8d736 100644 Binary files a/docs/user-guide/add-data-1.mp4 and b/docs/user-guide/add-data-1.mp4 differ diff --git a/docs/user-guide/add-data.ttl b/docs/user-guide/add-data.ttl index dbafe7a3..1afb65b7 100644 --- a/docs/user-guide/add-data.ttl +++ b/docs/user-guide/add-data.ttl @@ -11,7 +11,7 @@ <#content> a ldh:XHTML ; rdf:value """

    Copy RDF data from Linked Data resources and file uploads into your dataspace

    -

    Forking copies the contents of any RDF document into a named graph in your dataspace. Two sources of RDF are supported: RDF resources on the web (Linked Data) and RDF file uploads.

    +

    You can copy the contents of any RDF document into a named graph in your dataspace. Two sources of RDF are supported: RDF resources on the web (Linked Data) and RDF file uploads.

    @@ -26,7 +26,7 @@
    - +

    To copy the contents of the current RDF document (local or remote) into a local document, follow these steps:

    1. Choose Save as in the action bar's Actions dropdown. @@ -38,17 +38,17 @@
      The target document. The source data is appended to the target document's existing content, replacing nothing. You can start typing to find the document by its label, or paste its URI here.
    2. -
    3. Click Save to perform the fork operation.
    4. +
    5. Click Save to copy the data.

    For example, the Northwind Beverages category maps to the UNESCO Thesaurus concept Beverages. Open it in the - Linked Data browser and fork it into your own dataspace.

    + Linked Data browser and copy it into your own dataspace.

    The target document does not have to be local: you can also add data to a document in another dataspace by pasting its URI. The append is performed with your identity, so the target dataspace's access control decides whether it is allowed — a refusal surfaces as a form error.

    -

    Browse data to find resources worth forking, or import RDF data for larger datasets.

    +

    Browse data to find resources worth copying, or import RDF data for larger datasets.

    """^^rdf:XMLLiteral . diff --git a/docs/user-guide/change-model.ttl b/docs/user-guide/change-model.ttl index ad55c021..aeb189fc 100644 --- a/docs/user-guide/change-model.ttl +++ b/docs/user-guide/change-model.ttl @@ -72,16 +72,18 @@ WHERE {} admin_base="https://admin.localhost:4443/" ldh admin add constructor \\ - -b "$admin_base" \\ --uri "${base}ns#ProductConstructor" \\ --label "Product constructor" \\ --query-file queries/construct-product.rq \\ "${admin_base}ontologies/namespace/" -

    The model lives in the administration dataspace, whose origin is the dataspace origin with an admin. prefix — so -b (overriding the LDH_BASE default) and the target document URL both use $admin_base, because admin commands target the admin app's base URI, while the ontology terms being defined keep the end-user origin.

    +

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

    Follow the same steps for Category (schema:ProductGroup) if you want its instances to be creatable too.

    +

    Edit a constructor from a form

    +

    A constructor can also be changed from the form of any instance of its class. Open the resource's form with the button and click Edit constructors at the bottom of the form. A dialog lists the properties the class's constructor gives a new instance: each row is a property, switched between Resource and Literal, with the class of the resource or the datatype of the literal. Change a row, remove one with , or add one with Property, then click Save: the create forms of the class follow.

    +

    For a resource of several classes the button becomes a menu with an entry per class. It appears once LinkedDataHub has checked that you can write the class's constructor, which in practice means membership of the owners group. A class that has no constructor yet is offered too, so its first property can be added this way.

    Read more about constructors.

    @@ -116,7 +118,6 @@ ldh admin add constructor \\