Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
145 commits
Select commit Hold shift + click to select a range
b3d5dd0
Port the content block to the design system's class vocabulary
namedgraph Aug 26, 2026
6b17fc9
Vendor the design system's CSS and fonts, link them after Bootstrap
namedgraph Aug 26, 2026
f732ebf
Port the app shell to the design system Header and Sidebar
namedgraph Aug 26, 2026
5269631
Route all block link columns through the ldh-drawer
namedgraph Aug 26, 2026
1a4f227
Port the action bar, mode lists, type badges, property lists and pager
namedgraph Aug 26, 2026
ffe76ea
Skin modals and RDF/POST forms with the design system via the bridge
namedgraph Aug 26, 2026
5bd46da
Drop the Bootstrap 2 framework stylesheets; purge its markup vocabulary
namedgraph Aug 26, 2026
818b181
Fix dl column placement, button-on-accent color, block stacking rhythm
namedgraph Aug 26, 2026
f3bae2f
Delete the Bootstrap 2 artifacts: framework CSS, bootstrap.js, jQuery…
namedgraph Aug 26, 2026
f7ef8f9
Rename LDH's app stylesheet out of its Bootstrap identity
namedgraph Aug 26, 2026
6127582
Quiet XHTML prose blocks; stop double-carding the CSR block wrapper
namedgraph Aug 26, 2026
2cf656f
Stick the action bar directly under the sticky header
namedgraph Aug 26, 2026
9833ada
Design-system breadcrumbs; restore the document action button order
namedgraph Aug 26, 2026
cd32a3b
Fix the doubled card borders: nested cards, orphaned ldh.css tokens, …
namedgraph Aug 26, 2026
47e3586
Replace the action-bar chrome with the design system's markup
namedgraph Aug 26, 2026
8ad8292
Retire the sprite-icon layer; design create-block buttons
namedgraph Aug 26, 2026
6be7dd9
One caret per dropdown: glyph-native carets replace the border triangle
namedgraph Aug 26, 2026
00861ca
Static carets become glyph spans; the ::before shim serves state togg…
namedgraph Aug 26, 2026
330d2c9
Float modals above the sticky chrome; close menus on pick
namedgraph Aug 26, 2026
e26ed81
Header dropdowns: readable ink, right alignment, avatar state
namedgraph Aug 26, 2026
512cc1c
Purge the dead pull-left/pull-right tokens
namedgraph Aug 26, 2026
54a9e05
Modal header: grid layout immune to the legacy child order
namedgraph Aug 26, 2026
67eee7a
Chart controls take the design's chart-controls grid
namedgraph Aug 26, 2026
fadb668
Audit P1: restore sprite-era button faces, title the constructor modal
namedgraph Aug 26, 2026
f1b3e9d
Reglyph the ldh:logo-faced buttons the sprite purge left blank
namedgraph Aug 26, 2026
50d40a1
Views grow the design's facet toolbar; the pull-* floats and stray En…
namedgraph Aug 26, 2026
05a08a5
RDF exports leave the Actions menu for their own icon dropdown
namedgraph Aug 26, 2026
f5741a4
Un-park the document tree flyout the ldh-sidebar class drove off-canvas
namedgraph Aug 26, 2026
a1dc3fa
Form footers shed their Bootstrap look - and the form-actions token w…
namedgraph Aug 26, 2026
9c39057
Every modal form closes from its footer, and modals span the content …
namedgraph Aug 26, 2026
41e958e
Footer takes the AppShell anatomy: brand column, bare links, legal strip
namedgraph Aug 26, 2026
c5ba77f
cors-static test follows bootstrap.css to its successor
namedgraph Aug 26, 2026
5a21afa
Document-level creation floats in a bottom-right dock, unified as + p…
namedgraph Aug 26, 2026
6fc687c
Give bare .tb buttons the quiet toolbar treatment
namedgraph Aug 26, 2026
f9795bf
Create dock parks above the footer; short pages pin the footer to the…
namedgraph Aug 27, 2026
2a7ebc4
Idiom/DRY sweep across the ported XSL, IXSL, CSS and JS
namedgraph Aug 27, 2026
7c90613
Content column stays full-width inside the pane flex stack
namedgraph Aug 27, 2026
c18bc55
Create bar right-aligns with the content blocks
namedgraph Aug 27, 2026
8dcff7e
Drop the Web-Client bootstrap.css link; ldh-bridge.css carries the pr…
namedgraph Aug 27, 2026
d38d17e
Breadcrumb zone and address bar left-align with the content column
namedgraph Aug 27, 2026
7ed2113
Tab switches show panes as flex, keeping the create bar parked above …
namedgraph Aug 27, 2026
a3ab072
Toolbar host inner div gets the content-body class its mount selector…
namedgraph Aug 27, 2026
ac63804
Editor toolbar follows the active tab; editor chrome hides on tabs wi…
namedgraph Aug 27, 2026
371f19b
Editor toolbar restyled on design tokens; its inner content-body left…
namedgraph Aug 27, 2026
31dd873
Sidebar class list renders as design rows; search input stops paintin…
namedgraph Aug 27, 2026
3945175
List view rows follow the design's ldh-list-block grid: icon chip, ti…
namedgraph Aug 27, 2026
a1c5acd
Charts draw with resolved design tokens via ldh:css-token(); the HTML…
namedgraph Aug 27, 2026
18b65cc
Primary and negative ldhc-btn buttons carry the ap-solid appearance c…
namedgraph Aug 27, 2026
bc990b8
Document edit modal renders its form in a block shell, sharing the in…
namedgraph Aug 27, 2026
1c946ce
Document-level graph mode fills the space between action bar and foot…
namedgraph Aug 27, 2026
2bf6577
Action bar height becomes a token so the editor bar's sticky offset s…
namedgraph Aug 27, 2026
163a927
Block head nested in the block body drops its side padding, aligning …
namedgraph Aug 27, 2026
6332f03
Read-mode property rows light up on hover as one continuous band, lik…
namedgraph Aug 27, 2026
3dfc587
View mode clicks read the mode class off the clicked item itself, sin…
namedgraph Aug 27, 2026
1200af0
Blocks stop clipping their dropdown menus: results scroll inside the …
namedgraph Aug 27, 2026
3155e37
Grid mode renders design-system cards: pastel media area, title and d…
namedgraph Aug 27, 2026
7d5bbf0
Map canvas stays in flow so the view toolbar survives map mode; absol…
namedgraph Aug 27, 2026
4294489
RDFa extractor entry becomes a named template only, so it stops inter…
namedgraph Aug 27, 2026
49e0fee
RDFa editor template names and modes move into the module namespaces:…
namedgraph Aug 27, 2026
aa7ed4d
Create dock yields the bottom strip to the RDFa editor's breadcrumb b…
namedgraph Aug 27, 2026
14b0650
Document-level map mode fills the space between action bar and footer…
namedgraph Aug 27, 2026
6d1d69a
Every view mode branch tunnels show-edit-button=false, so item templa…
namedgraph Aug 27, 2026
5129e1c
Facet popover states, anonymous RDF export, mock map retired, chart f…
namedgraph Aug 27, 2026
e28bf54
RDFa editor Source shows the region's children (the ol payload), the …
namedgraph Aug 27, 2026
e5ba843
XMLLiteral canonicalization is the RDFa editor's cm:canonical pass in…
namedgraph Aug 27, 2026
cf5baaa
Block DnD payload-typed: an app MIME marker replaces the mode-cache g…
namedgraph Aug 27, 2026
02b08c2
DataTransfer.types marshals to an XDM array under SaxonJS, so the pay…
namedgraph Aug 27, 2026
4916ed6
View pager becomes the three-zone ldh-pager (page-size selector, prev…
namedgraph Aug 28, 2026
5f792ce
Block drop lands per the HTML processing model: dragenter is canceled…
namedgraph Aug 28, 2026
5d07d02
The content-body DnD guards spell the child step as ./div - after 'an…
namedgraph Aug 28, 2026
90a8659
Facet filter and parallax step restart paging from the first page via…
namedgraph Aug 28, 2026
3ac6619
The View links drawer becomes reachable: a tb-links opener lands in t…
namedgraph Aug 28, 2026
47a49a8
The block drawer anchors to its card: .ldh-block goes position:relati…
namedgraph Aug 28, 2026
6a44f92
The hand cursor over XHTML blocks becomes an honest affordance: the p…
namedgraph Aug 28, 2026
16e2080
Legacy Bootstrap buttons move to the ldhc-btn intent/appearance/size …
namedgraph Aug 28, 2026
8a3c1d8
View table columns become sortable: xhtml:TableHeaderCell reverse-map…
namedgraph Aug 28, 2026
13f1b8b
The block links drawer retires in favour of two design-system placeme…
namedgraph Aug 28, 2026
36b006a
The copy-URI button reaches every block type through a context-free l…
namedgraph Aug 28, 2026
7bacb60
The Table chart fills the block width like its SVG siblings: ac:draw-…
namedgraph Aug 28, 2026
ec1a434
View event handlers resolve their client-side cache through a single …
namedgraph Aug 28, 2026
ff29c5c
Content-column alignment survives classic scrollbars: the address bar…
namedgraph Aug 29, 2026
d05fe16
The facet pill's predicate label sheds the pred class: app.css styles…
namedgraph Aug 29, 2026
c0bac68
Read-mode property lists become name-value groups: bs2:PropertyListId…
namedgraph Aug 29, 2026
8b644a0
View table sortable columns read at rest and the property-list hover …
namedgraph Aug 29, 2026
27ebce8
View table rows highlight on hover: tbody tr:hover paints its cells a…
namedgraph Aug 29, 2026
982e672
View sort's numeric keys yield empty sequence instead of NaN for non-…
namedgraph Aug 29, 2026
25730b7
The view's client-side sort keys off each column's XSD datatype, thro…
namedgraph Aug 29, 2026
a03c0f0
The map marker info window becomes the card itself: .ol-overlay-conta…
namedgraph Aug 29, 2026
162316a
The info window's block head aligns with the property list below it: …
namedgraph Aug 29, 2026
0187c8f
The XSD datatype enumeration behind the view sort moves into ldh:data…
namedgraph Aug 29, 2026
c4a27b8
Timestamp handling gets one ldh:date-time(), retiring the date-or-dat…
namedgraph Aug 29, 2026
d9f10a7
The DataTable converters recognise the derived numeric and dateTime t…
namedgraph Aug 29, 2026
8f8ce7a
Image objects render at a height the surface picks rather than the on…
namedgraph Aug 29, 2026
3774429
The vendored typefaces gain their latin-ext subsets, so Latin Extende…
namedgraph Aug 29, 2026
e38f693
The edit pencil on a resource description appears only under acl:Writ…
namedgraph Aug 29, 2026
2bd87e6
One navigation leaves one history entry. After a document load, ldh:r…
namedgraph Aug 29, 2026
e2b902c
CSR failures render through the design system's block-states componen…
namedgraph Aug 29, 2026
431fb08
The busy cursor becomes a promise concern. Raising it was 53 copies o…
namedgraph Aug 29, 2026
c6836f3
Three legacy ixsl:schedule-action http-request callbacks become promi…
namedgraph Aug 29, 2026
1440438
Parallax chips stop being read off the loaded page and are discovered…
namedgraph Aug 29, 2026
ef14806
A quiet block no longer lifts under the cursor. The hover elevation w…
namedgraph Aug 29, 2026
0eda7e0
The rdf:type pill follows the type rather than the type list. Web-Cli…
namedgraph Aug 29, 2026
fe0b50e
A chart over an empty result set says so in the design system's empty…
namedgraph Aug 30, 2026
1d79c84
An empty block says so in its header as well as its body. The design …
namedgraph Aug 30, 2026
d986d4b
The two RDF/POST subject-control handlers move out of JavaScript into…
namedgraph Aug 30, 2026
e409c41
The DOMContentLoaded block retires: half of it was doing nothing, the…
namedgraph Aug 30, 2026
40b64c8
A press anywhere dismisses the drop-downs it is not inside, which clo…
namedgraph Aug 30, 2026
60ae436
The drop-down dismissal moves to where a modal is actually mounted, a…
namedgraph Aug 30, 2026
0320628
ac:uuid() generates through the platform, and the seven call sites th…
namedgraph Aug 30, 2026
b617807
The query block's Chart/View strip becomes the design system's tab co…
namedgraph Aug 30, 2026
437a16e
Charts and views are both creatable from a query block now, and both …
namedgraph Aug 30, 2026
25ced7e
The row form is inserted with xsl:result-document rather than the DOM…
namedgraph Aug 30, 2026
1912d21
Two faults in the block-creation merge, both found by driving the cas…
namedgraph Aug 30, 2026
ab472f2
Editing a query into a different form now retypes it, and a rejected …
namedgraph Aug 30, 2026
f970956
The violation renderer goes back to failing on an absent form, becaus…
namedgraph Aug 30, 2026
ef5125f
The map info window pans itself into view, so the view controls no lo…
namedgraph Aug 30, 2026
c1e7a28
The marker info window is fitted into the map viewport itself, replac…
namedgraph Aug 30, 2026
4995785
The parallax glyph sits on the same line as the first pivot chip. Bot…
namedgraph Aug 30, 2026
54243b3
Marker clicks pan the map again, and one info window replaces another…
namedgraph Aug 30, 2026
17a2990
The hang in the previous commit was a wrong type on the pixel read. o…
namedgraph Aug 30, 2026
0a36174
Keep the server's render on the initial load
namedgraph Aug 31, 2026
7a60dce
The row form opens on its first required control whatever kind of con…
namedgraph Aug 31, 2026
d6f7c3a
The metadata merge keys on the RDF term, not the lexical form, so a t…
namedgraph Aug 31, 2026
527850d
Property lists order their values by the reader's language instead of…
namedgraph Aug 31, 2026
9a2a76e
Drop the Bootstrap 2 class vocabulary that no stylesheet backs, and g…
namedgraph Aug 31, 2026
efe5be0
Responses advertise that they vary by Accept-Language, so a rendering…
namedgraph Aug 31, 2026
6899378
Revert "Responses advertise that they vary by Accept-Language, so a r…
namedgraph Aug 31, 2026
3f75858
Predicate labels come from the application ontology on the server ren…
namedgraph Aug 31, 2026
ab141f4
Property values declare their own language instead of inheriting the …
namedgraph Sep 1, 2026
e47ac2f
Complete the Spanish translations, so the bundle covers every UI stri…
namedgraph Sep 1, 2026
a81788e
Supported UI languages are derived from the translation bundle instea…
namedgraph Sep 1, 2026
e6f95e8
The page reports the language it is written in, and Content-Language …
namedgraph Sep 1, 2026
bb7c8b5
Predicate labels declare their negotiated language, so they survive t…
namedgraph Sep 1, 2026
1904891
Move the language negotiation out of Application into its own utility.
namedgraph Sep 1, 2026
a2b136f
The writer reads the language off the response instead of negotiating…
namedgraph Sep 1, 2026
1969fe4
The document's lang attribute derives from the Content-Language it is…
namedgraph Sep 1, 2026
fcde58c
Document responses pass the accepted languages, so their entity tags …
namedgraph Sep 1, 2026
910ad0b
Only label the language of representations whose rendering depends on…
namedgraph Sep 1, 2026
fda1dd4
The language sort key comes from Web-Client, so there is one of it ra…
namedgraph Sep 2, 2026
2e4f36f
Drop ldt:lang, which nothing declares, reads or writes.
namedgraph Sep 2, 2026
2900a33
Cover the response filter's hypermedia, not only the language it labe…
namedgraph Sep 2, 2026
7cb82e7
Assert the language rules over HTTP, where the header and the documen…
namedgraph Sep 2, 2026
cfeffca
Don't use getter in constructor
namedgraph Sep 2, 2026
49482fe
Publish the shortest language tag the bundle justifies, not the key i…
namedgraph Sep 2, 2026
76851a3
Read the browser's languages through ac:langs(), so client.xsl declar…
namedgraph Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions http-tests/language/content-language-by-reader.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env bash
set -euo pipefail

# Content-Language names the language the page was composed in, which is the first of the reader's accepted languages that
# the UI translation bundle actually has - not the reader's top preference. The bundle ships English and Spanish, so a
# Lithuanian or German reader gets an honest en rather than their own request echoed back at them. What is published is the
# shortest tag the bundle justifies: its keys are en-US and es-ES, but neither region distinguishes anything.

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

response()
{
curl -i -k -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: text/html" \
"$@" \
"$END_USER_BASE_URL" \
| tr -d '\r'
}

assert_language()
{
local accept_language="$1"
local expected="$2"
local response actual

if [ -n "$accept_language" ]; then
response=$(response -H "Accept-Language: $accept_language")
else
response=$(response)
fi

# an error page is rendered through the same stylesheet and carries a Content-Language of its own, so every probe
# confirms this is the document rather than a failure that happens to be labelled
echo "$response" | grep -qE "^HTTP/[0-9.]+ 200"

actual=$(echo "$response" | grep -i "^Content-Language:" | sed 's/^Content-Language: *//i' || true)

[ "$actual" = "$expected" ]
}

# the bundle keys are region-qualified while browsers send primary subtags, so matching is RFC 4647 Basic
# Filtering - but the published tag drops a region that distinguishes nothing, per BCP 47

assert_language "en-US,en;q=0.9,da;q=0.8,lt;q=0.7" "en"
assert_language "en" "en"
assert_language "es" "es"

# preference order decides between two languages the bundle has, rather than bundle order

assert_language "es,en;q=0.9" "es"
assert_language "en,es;q=0.9" "en"

# the bundle has no Lithuanian, so the page is composed in the next language it does have

assert_language "lt,en-US;q=0.9,en;q=0.8" "en"

# none of the reader's languages are available: fall back rather than claim a language the page is not in

assert_language "de,fr;q=0.9" "en"

# no preference expressed at all

assert_language "" "en"
55 changes: 55 additions & 0 deletions http-tests/language/content-language-media-type.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail

# Only a representation whose rendering depends on language gets labelled with one. An RDF representation is byte-identical
# for every reader - its literals carry their own tags and none is dropped - so it is intended for all language audiences,
# which RFC 9110 spells as no Content-Language at all.

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

headers()
{
curl -i -k -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: $1" \
-H "Accept-Language: en" \
"$END_USER_BASE_URL" \
| tr -d '\r'
}

assert_labelled()
{
local response actual

response=$(headers "$1")

# an error page is rendered through the same stylesheet and carries a Content-Language of its own, so every probe
# confirms this is the representation rather than a failure that happens to be labelled
echo "$response" | grep -qE "^HTTP/[0-9.]+ 200"

actual=$(echo "$response" | grep -i "^Content-Language:" | sed 's/^Content-Language: *//i' || true)

[ "$actual" = "en" ]
}

assert_unlabelled()
{
local response

response=$(headers "$1")

echo "$response" | grep -qE "^HTTP/[0-9.]+ 200"

! echo "$response" | grep -qi "^Content-Language:"
}

# both HTML flavours are rendered through the translation bundle and so are composed in one language

assert_labelled "text/html"
assert_labelled "application/xhtml+xml"

assert_unlabelled "application/rdf+xml"
assert_unlabelled "text/turtle"
assert_unlabelled "application/n-triples"
assert_unlabelled "application/ld+json"
53 changes: 53 additions & 0 deletions http-tests/language/etag-varies-by-language.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
set -euo pipefail

# Two readers get two different HTML representations of the same document, so the two must not share an entity tag: a
# Spanish reader arriving with the English reader's tag would be told 304 and shown a page in a language they never asked
# for. The RDF representation is the same bytes for both, so its tag has to stay stable across them.

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

etag()
{
curl -i -k -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: $1" \
-H "Accept-Language: $2" \
"$END_USER_BASE_URL" \
| tr -d '\r' \
| grep -i "^ETag:" \
| sed 's/^ETag: *//i' || true
}

html_en=$(etag "text/html" "en")
html_es=$(etag "text/html" "es")

[ -n "$html_en" ]

# two languages must not share one entity tag, or a conditional request would serve the wrong language

[ "$html_en" != "$html_es" ]

# the tag is still stable for one reader, otherwise conditional requests never revalidate

[ "$html_en" = "$(etag "text/html" "en")" ]

status()
{
curl -k -w "%{http_code}" -o /dev/null -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: text/html" \
-H "Accept-Language: $1" \
-H "If-None-Match: $2" \
"$END_USER_BASE_URL"
}

# the reader's own tag revalidates, another reader's does not

[ "$(status "en" "$html_en")" = "$STATUS_NOT_MODIFIED" ]
[ "$(status "es" "$html_en")" = "$STATUS_OK" ]

# RDF carries every language at once, so language is not part of its identity

[ "$(etag "application/n-triples" "en")" = "$(etag "application/n-triples" "es")" ]
45 changes: 45 additions & 0 deletions http-tests/language/html-lang-matches-content-language.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
set -euo pipefail

# The header and the document have to agree about what language the page is in. They do because both read the same
# computation: the stylesheet takes the root lang straight from Content-Language rather than from the request. A reader whose
# language the bundle lacks is the case that used to disagree - the header said nothing while the document claimed lt.

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

assert_agrees()
{
local expected="$2"
local headers body header document

headers=$(mktemp)

# XHTML is the same rendering as text/html but well-formed, so the document can be queried with XPath rather than
# matched as text - attribute order is a serialization detail and nothing here depends on it
body=$(curl -k -s \
-D "$headers" \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: application/xhtml+xml" \
-H "Accept-Language: $1" \
"$END_USER_BASE_URL")

# an error page is rendered through the same stylesheet and agrees with itself just as happily, so confirm this is the
# document before comparing the two
head -1 "$headers" | grep -qE "^HTTP/[0-9.]+ 200"

header=$(tr -d '\r' < "$headers" | grep -i "^Content-Language:" | sed 's/^Content-Language: *//i' || true)
rm -f "$headers"

document=$(echo "$body" | xmllint --xpath 'string(/*[local-name() = "html"]/@lang)' - 2> /dev/null || true)

[ "$header" = "$expected" ] && [ "$document" = "$expected" ]
}

assert_agrees "en" "en"
assert_agrees "es" "es"

# the bundle has no Lithuanian and no German: the root must not claim a language the page is not written in

assert_agrees "lt,en;q=0.9" "en"
assert_agrees "de" "en"
72 changes: 72 additions & 0 deletions http-tests/language/literal-lang-attributes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -euo pipefail

# RDF carries language per literal, HTML per element, so every rendered value declares its own rather than inheriting the
# document's. A property renders every language it has, side by side, and the root value is wrong for at least one of them:
# without this a screen reader says "Square" with Lithuanian phonetics and "Aikštė" with an English voice. An untagged
# literal makes no language claim at all, which HTML spells lang="" - the exact counterpart of RDF's absent tag - while a
# number is not prose and inherits, so it is read out in the reader's own language.

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

slug=$(uuidgen | tr '[:upper:]' '[:lower:]')
doc_url="${END_USER_BASE_URL}${slug}/"

# the properties belong to the document's primary topic: a document describing nothing but itself renders its title and
# an empty body, so the values would never reach a dd

echo "<${doc_url}> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://www.w3.org/ns/ldt/document-hierarchy#Item> .
<${doc_url}> <http://purl.org/dc/terms/title> \"Language marking\" .
<${doc_url}> <http://xmlns.com/foaf/0.1/primaryTopic> <${doc_url}#this> .
<${doc_url}#this> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://example.org/test#Thing> .
<${doc_url}#this> <https://example.org/test#tagged> \"Aikštė\"@lt .
<${doc_url}#this> <https://example.org/test#tagged> \"Square\"@en .
<${doc_url}#this> <https://example.org/test#plain> \"Untagged value\" .
<${doc_url}#this> <https://example.org/test#typed> \"42\"^^<http://www.w3.org/2001/XMLSchema#integer> ." | \
ldh put \
-f "$OWNER_CERT_KEYSTORE" \
-p "$OWNER_CERT_PWD" \
-t "application/n-triples" \
"$doc_url"

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

# XHTML is the same rendering as text/html but well-formed, so the values can be queried with XPath rather than matched as
# text. Attribute order is a serialization detail - the build inlines entities through an XML round trip that alphabetizes
# literal-result-element attributes, while xsl:attribute instructions append in execution order - and nothing here depends
# on it
body=$(curl -k -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: application/xhtml+xml" \
-H "Accept-Language: en" \
"$doc_url")

count()
{
echo "$body" | xmllint --xpath "count($1)" - 2> /dev/null || echo "0"
}

# the page is in English and both values are on it: each says which language it is in

[ "$(count "//*[local-name() = 'dd'][@property = 'https://example.org/test#tagged'][@lang = 'lt']")" = "1" ]
[ "$(count "//*[local-name() = 'dd'][@property = 'https://example.org/test#tagged'][@lang = 'en']")" = "1" ]

# an untagged literal claims no language rather than inheriting the document's

[ "$(count "//*[local-name() = 'dd'][@property = 'https://example.org/test#plain'][@lang = '']")" = "1" ]

# a number is not prose - it inherits, so it is read out in whatever language the reader is in

[ "$(count "//*[local-name() = 'dd'][@property = 'https://example.org/test#typed']")" = "1" ]
[ "$(count "//*[local-name() = 'dd'][@property = 'https://example.org/test#typed'][@lang]")" = "0" ]

# both languages of the property survive to the page - suppressing one is the defect this marking exists to make safe

[ "$(count "//*[local-name() = 'span'][@class = 'chip-inline']")" = "2" ]

ldh delete \
-f "$OWNER_CERT_KEYSTORE" \
-p "$OWNER_CERT_PWD" \
"$doc_url" > /dev/null
39 changes: 39 additions & 0 deletions http-tests/language/vary-accept-language.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail

# An HTML representation differs by the reader's language, so the response has to say so or a shared cache will hand one
# reader's page to another. This is the header the caches downstream key on, and the reason two readers can be served from
# the same URL at all.

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

curl -i -k -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: text/html" \
-H "Accept-Language: en" \
"$END_USER_BASE_URL" \
| tr -d '\r' \
| grep -i "^Vary:" \
| grep -qi "Accept-Language"

# the same URL must survive two readers in sequence through the caches in front of it

purge_cache "$END_USER_VARNISH_SERVICE"
purge_cache "$FRONTEND_VARNISH_SERVICE"

fetch_language()
{
curl -i -k -s \
-E "$OWNER_CERT_FILE":"$OWNER_CERT_PWD" \
-H "Accept: text/html" \
-H "Accept-Language: $1" \
"$END_USER_BASE_URL" \
| tr -d '\r' \
| grep -i "^Content-Language:" \
| sed 's/^Content-Language: *//i' || true
}

[ "$(fetch_language "en")" = "en" ]
[ "$(fetch_language "es")" = "es" ]
[ "$(fetch_language "en")" = "en" ]
4 changes: 2 additions & 2 deletions http-tests/misc/cors-static.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set -euo pipefail

response=$(curl -i -k -s \
-H "Origin: https://example.com" \
"${END_USER_BASE_URL}static/com/atomgraph/linkeddatahub/css/bootstrap.css")
"${END_USER_BASE_URL}static/com/atomgraph/linkeddatahub/css/ldh.css")

# Verify Access-Control-Allow-Origin header is present
if ! echo "$response" | grep -q "Access-Control-Allow-Origin: \*"; then
Expand All @@ -26,7 +26,7 @@ preflight=$(curl -i -k -s \
-X OPTIONS \
-H "Origin: https://example.com" \
-H "Access-Control-Request-Method: GET" \
"${END_USER_BASE_URL}static/com/atomgraph/linkeddatahub/css/bootstrap.css")
"${END_USER_BASE_URL}static/com/atomgraph/linkeddatahub/css/ldh.css")

# Verify preflight response has CORS headers
if ! echo "$preflight" | grep -q "Access-Control-Allow-Origin: \*"; then
Expand Down
2 changes: 2 additions & 0 deletions http-tests/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ run_tests "sparql-protocol" $(find ./sparql-protocol/ -type f -name '*.sh')
(( error_count += $? ))
run_tests "versioning" $(find ./versioning/ -type f -name '*.sh')
(( error_count += $? ))
run_tests "language" $(find ./language/ -type f -name '*.sh')
(( error_count += $? ))

end_time=$(date +%s)
runtime=$((end_time-start_time))
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,13 @@
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>client</artifactId>
<version>5.0.3</version>
<version>5.0.4-SNAPSHOT</version>
<classifier>classes</classifier>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>client</artifactId>
<version>5.0.3</version>
<version>5.0.4-SNAPSHOT</version>
<type>war</type>
</dependency>
<dependency>
Expand Down
Loading
Loading