Skip to content

Commit e34db78

Browse files
namedgraphclaude
andcommitted
Refactor external doc tabs: per-URI panes, data-* state, tab-pane-id param
- Replace LinkedDataHub.tabs/tab-counter JS registry with data-uri/data-endpoint attributes directly on tab <li> elements; sd:endpoint() now reads from dataset - Give each external document its own tab pane (one per URI) instead of a shared external pane; tab pane visibility toggled via display:none/block - Rename ldh:CreateTab → ldh:AddTabNavBarListItem (no longer handles rendering); ldh:RenderTab now accepts $tab-pane-id (xs:string) instead of $tab-pane element, since the element is detached after ixsl:replace-element - ProxyRequestFilter: inject MediaTypes, throw NotAcceptableException on no variant match instead of silently returning; remove manual HTML Accept-header inspection - Remove $ac:uri XSLT param and related proxy-target plumbing from XSLTWriterBase and layout.xsl; remove LDH.access_to vocabulary property - Switch service document fetches from ac:build-uri() to ldh:href() throughout - Move srx:sparql bs2:ContentBody template from layout.xsl to document.xsl Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 18dd437 commit e34db78

14 files changed

Lines changed: 262 additions & 501 deletions

File tree

src/main/java/com/atomgraph/linkeddatahub/server/filter/request/ProxyRequestFilter.java

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717
package com.atomgraph.linkeddatahub.server.filter.request;
1818

19-
import com.atomgraph.core.MediaTypes;
19+
import com.atomgraph.client.MediaTypes;
2020
import com.atomgraph.client.util.HTMLMediaTypePredicate;
2121
import com.atomgraph.client.vocabulary.AC;
2222
import com.atomgraph.core.exception.BadGatewayException;
@@ -102,11 +102,11 @@ public class ProxyRequestFilter implements ContainerRequestFilter
102102
{
103103

104104
private static final Logger log = LoggerFactory.getLogger(ProxyRequestFilter.class);
105-
private static final MediaTypes MEDIA_TYPES = new MediaTypes();
106105
private static final Pattern LINK_SPLITTER = Pattern.compile(",(?=\\s*<)");
107106

108107
@Inject com.atomgraph.linkeddatahub.Application system;
109108
@Inject jakarta.inject.Provider<Optional<Ontology>> ontology;
109+
@Inject MediaTypes mediaTypes;
110110
@Context Request request;
111111

112112
@Override
@@ -117,26 +117,24 @@ public void filter(ContainerRequestContext requestContext) throws IOException
117117

118118
URI targetURI = targetOpt.get();
119119

120-
// do not proxy requests from clients that explicitly accept (X)HTML — they expect the app shell,
121-
// which the downstream handler serves. Browsers list text/html as a non-wildcard type; pure API
122-
// clients (curl etc.) send only */* and must reach the proxy.
123-
// Defending against resource exhaustion: proxying + full server-side XSLT rendering for arbitrary
124-
// external URIs on every browser request would amplify CPU and connection-pool load unboundedly.
125-
boolean clientAcceptsHtml = requestContext.getAcceptableMediaTypes().stream()
126-
.anyMatch(mt -> !mt.isWildcardType() && !mt.isWildcardSubtype() &&
127-
(mt.isCompatible(MediaType.TEXT_HTML_TYPE) ||
128-
mt.isCompatible(MediaType.APPLICATION_XHTML_XML_TYPE)));
129-
if (clientAcceptsHtml) return;
130-
131120
// negotiate the response format from RDF/SPARQL writable types
121+
// skip filter if (X)HTML is the selected variant - we don't offer it for proxied responses
132122
List<MediaType> writableTypes = new ArrayList<>(getMediaTypes().getWritable(Model.class));
133123
writableTypes.addAll(getMediaTypes().getWritable(ResultSet.class));
134124
List<Variant> variants = com.atomgraph.core.model.impl.Response.getVariants(
135125
writableTypes,
136126
getSystem().getSupportedLanguages(),
137127
new ArrayList<>());
138-
Variant selectedVariant = getRequest().selectVariant(variants);
139-
if (selectedVariant == null) return; // client accepts no RDF/SPARQL type
128+
129+
Variant variant = getRequest().selectVariant(variants);
130+
if (variant == null)
131+
{
132+
if (log.isTraceEnabled()) log.trace("Requested Variant {} is not on the list of acceptable Response Variants: {}", variant, variants);
133+
throw new NotAcceptableException();
134+
}
135+
136+
if (variant.getMediaType().isCompatible(MediaType.TEXT_HTML_TYPE) ||
137+
variant.getMediaType().isCompatible(MediaType.APPLICATION_XHTML_XML_TYPE)) return;
140138

141139
// strip #fragment (servers do not receive fragment identifiers)
142140
if (targetURI.getFragment() != null)
@@ -156,7 +154,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException
156154
{
157155
if (log.isDebugEnabled()) log.debug("Serving mapped URI from DataManager cache: {}", targetURI);
158156
Model model = getSystem().getDataManager().loadModel(targetURI.toString());
159-
requestContext.abortWith(getResponse(model, Response.Status.OK, selectedVariant));
157+
requestContext.abortWith(getResponse(model, Response.Status.OK, variant));
160158
return;
161159
}
162160

@@ -174,7 +172,7 @@ public void filter(ContainerRequestContext requestContext) throws IOException
174172
if (!description.isEmpty())
175173
{
176174
if (log.isDebugEnabled()) log.debug("Serving URI from namespace ontology: {}", targetURI);
177-
requestContext.abortWith(getResponse(description, Response.Status.OK, selectedVariant));
175+
requestContext.abortWith(getResponse(description, Response.Status.OK, variant));
178176
return;
179177
}
180178
}
@@ -221,7 +219,7 @@ else if (agentContext instanceof IDTokenSecurityContext idTokenSecurityContext)
221219
{
222220
// provide the target URI as a base URI hint so ModelProvider / HtmlJsonLDReader can resolve relative references
223221
clientResponse.getHeaders().putSingle(com.atomgraph.core.io.ModelProvider.REQUEST_URI_HEADER, targetURI.toString());
224-
requestContext.abortWith(getResponse(clientResponse, selectedVariant));
222+
requestContext.abortWith(getResponse(clientResponse, variant));
225223
}
226224
}
227225
catch (MessageBodyProviderNotFoundException ex)
@@ -388,7 +386,7 @@ public Optional<Ontology> getOntology()
388386
*/
389387
public MediaTypes getMediaTypes()
390388
{
391-
return MEDIA_TYPES;
389+
return mediaTypes;
392390
}
393391

394392
/**

src/main/java/com/atomgraph/linkeddatahub/vocabulary/LDH.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,6 @@ public static String getURI()
9292
/** Violation value property */
9393
public static final DatatypeProperty violationValue = m_model.createDatatypeProperty( NS + "violationValue" );
9494

95-
/** Access to property */
96-
public static final ObjectProperty access_to = m_model.createObjectProperty(NS + "access-to"); // TO-DO: move to client-side?
97-
9895
/** Request URI property */
9996
public static final ObjectProperty requestUri = m_model.createObjectProperty(NS + "requestUri");
10097

src/main/java/com/atomgraph/linkeddatahub/writer/XSLTWriterBase.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,6 @@ public <T extends XdmValue> Map<QName, XdmValue> getParameters(MultivaluedMap<St
133133
URI endpointURI = getLinkURI(headerMap, SD.endpoint);
134134
if (endpointURI != null) params.put(new QName("sd", SD.endpoint.getNameSpace(), SD.endpoint.getLocalName()), new XdmAtomicValue(endpointURI));
135135

136-
URI proxyTargetURI = (URI) getContainerRequestContext().getProperty(AC.uri.getURI());
137-
if (proxyTargetURI != null) params.put(new QName("ac", AC.uri.getNameSpace(), AC.uri.getLocalName()), new XdmAtomicValue(proxyTargetURI));
138-
139136
String forShapeURI = getUriInfo().getQueryParameters().getFirst(LDH.forShape.getLocalName());
140137
if (forShapeURI != null) params.put(new QName("ldh", LDH.forShape.getNameSpace(), LDH.forShape.getLocalName()), new XdmAtomicValue(URI.create(forShapeURI)));
141138

@@ -158,11 +155,6 @@ public <T extends XdmValue> Map<QName, XdmValue> getParameters(MultivaluedMap<St
158155
params.put(new QName("acl", ACL.mode.getNameSpace(), ACL.mode.getLocalName()),
159156
XdmValue.makeSequence(getAuthorizationContext().get().get().getModeURIs()));
160157

161-
// TO-DO: move to client-side?
162-
if (getUriInfo().getQueryParameters().containsKey(LDH.access_to.getLocalName()))
163-
params.put(new QName("ldh", LDH.access_to.getNameSpace(), LDH.access_to.getLocalName()),
164-
new XdmAtomicValue(URI.create(getUriInfo().getQueryParameters().getFirst(LDH.access_to.getLocalName()))));
165-
166158
if (getHttpHeaders().getRequestHeader(HttpHeaders.REFERER) != null)
167159
{
168160
URI referer = URI.create(getHttpHeaders().getRequestHeader(HttpHeaders.REFERER).get(0));
@@ -249,11 +241,7 @@ public StreamSource getSource(Model model) throws IOException
249241
@Override
250242
public String getSystemId()
251243
{
252-
// for proxy requests, use the external URI as the XSLT document base URI
253-
URI proxyTarget = (URI) getContainerRequestContext().getProperty(AC.uri.getURI());
254-
if (proxyTarget != null) return proxyTarget.toString();
255-
256-
return getContainerRequestContext().getUriInfo().getRequestUri().toString();
244+
return getUriInfo().getRequestUri().toString();
257245
}
258246

259247
/**

src/main/webapp/static/com/atomgraph/linkeddatahub/css/bootstrap.css

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ body { height: calc(100% - 55px); padding-top: 55px; padding-bottom: 0; }
44
#tab-bar { position: sticky; top: 55px; z-index: 1000; margin-bottom: 0; }
55
/* Override Bootstrap's overflow:auto which would make action-bar sticky relative to #tab-content instead of the viewport */
66
#tab-content { overflow: visible; }
7-
#tab-bar .navbar-inner { min-height: 36px; padding: 4px 0; background: #ccc; box-shadow: none; }
8-
#tab-bar .nav-tabs { border-bottom: none; }
9-
#tab-bar .nav-tabs > li > a { padding: 4px 10px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #555; display: inline-block; }
10-
#tab-bar .nav-tabs > li > a:hover { color: #222; }
11-
#tab-bar .nav-tabs > li.active > a, #tab-bar .nav-tabs > li.active > a:hover { color: #333; background-color: #fff; border-color: #aaa #aaa transparent; }
12-
#tab-bar .tab-close { margin-left: 4px; font-size: 10px; color: #888; cursor: pointer; vertical-align: middle; }
13-
#tab-bar .tab-close:hover { color: #333; }
7+
#tab-bar.navbar-inner { min-height: 36px; padding: 4px 0; background: #ccc; box-shadow: none; }
8+
#tab-bar-list.nav-tabs { margin-bottom: 0; border-bottom: none; }
9+
#tab-bar-list.nav-tabs > li > a { color: #007fff; padding: 8px 12px; text-shadow: none; border-color: #aaa #aaa transparent; }
10+
#tab-bar-list.nav-tabs > li.active > a, #tab-bar .nav-tabs > li.active > a:hover { color: #555555; background: rgb(223, 223, 223); border-color: #aaa #aaa transparent; }
11+
#tab-bar-list .tab-close { margin-left: 4px; font-size: 10px; color: #888; cursor: pointer; vertical-align: middle; }
12+
#tab-bar-list .tab-close:hover { color: #333; }
1413
body.embed { padding-top: 0; }
1514
ul.dropdown-menu { max-height: 26em; overflow-x: hidden; overflow-y: auto; }
1615
ul.dropdown-menu ul { margin: 0; }
@@ -120,9 +119,9 @@ li button.btn-edit-constructors, li button.btn-add-data, li button.btn-add-ontol
120119
.faceted-nav input[type=checkbox]:checked + span { font-weight: bold; }
121120
.parallax-nav a { cursor: pointer; }
122121
.content-body { min-height: calc(100% - 14em); }
123-
.content-body > [about] > [about].row-fluid { overflow-x: auto; margin-bottom: 20px; }
124-
.content-body > [about] > [about].row-fluid, .constructor-triple.row-fluid { border-bottom: 2px solid rgb(223, 223, 223); }
125-
.content-body > [about] > [about].row-fluid.drag-over { border-bottom: 4px dotted #0f82f5; }
122+
.content-body > [about].row-fluid.block { overflow-x: auto; margin-bottom: 20px; }
123+
.content-body > [about].row-fluid.block, .constructor-triple.row-fluid { border-bottom: 2px solid rgb(223, 223, 223); }
124+
.content-body > [about].row-fluid.block.drag-over { border-bottom: 4px dotted #0f82f5; }
126125
.row-fluid.block { max-height: 80em; }
127126
.row-fluid.block .drag-handle { display: none; width: 30px; background-color: #149bdf; background-image: radial-gradient(circle at 3px 3px, #0480be 1px, transparent 1.5px); background-size: 6px 6px; border-radius: 2px; cursor: move; }
128127
.list-mode.active { background-image: url('../icons/ic_navigate_before_black_24px.svg'); }

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/admin/signup.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ xmlns:bs2="http://graphity.org/xsl/bootstrap/2.3.2"
4747
exclude-result-prefixes="#all">
4848

4949
<xsl:template match="rdf:RDF[ac:absolute-path(ldh:request-uri()) = resolve-uri(encode-for-uri('sign up'), $ldt:base)]" mode="bs2:ContentBody" priority="2" use-when="system-property('xsl:product-name') = 'SAXON'">
50-
<div about="{ac:absolute-path(base-uri($main-doc))}" id="content-body" class="container-fluid">
50+
<div about="{ac:absolute-path(base-uri($main-doc))}" class="container-fluid content-body">
5151
<xsl:apply-templates select="key('resources', ac:absolute-path(base-uri($main-doc)))" mode="ldh:ContentList"/>
5252

5353
<xsl:apply-templates select="." mode="bs2:Row"/>

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/chart.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ exclude-result-prefixes="#all"
774774
<!-- TO-DO: use SPARQLBuilder to set LIMIT -->
775775
<!--<xsl:variable name="query-string" select="concat($query-string, ' LIMIT 100')" as="xs:string"/>-->
776776
<xsl:variable name="service-uri" select="xs:anyURI(key('resources', $query-uri)/ldh:service/@rdf:resource)" as="xs:anyURI?"/>
777-
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
777+
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ldh:href(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }, ()))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
778778
<xsl:variable name="endpoint" select="($service/sd:endpoint/@rdf:resource/xs:anyURI(.), sd:endpoint())[1]" as="xs:anyURI"/>
779779
<xsl:variable name="request-uri" select="ldh:href($endpoint, map{})" as="xs:anyURI"/>
780780

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/query.xsl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ exclude-result-prefixes="#all"
189189
<xsl:choose>
190190
<xsl:when test="$service-uri">
191191
<!-- need to explicitly request RDF/XML, otherwise we get HTML -->
192-
<xsl:variable name="request-uri" select="ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' })" as="xs:anyURI"/>
192+
<xsl:variable name="request-uri" select="ldh:href(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }, ())" as="xs:anyURI"/>
193193
<!-- TO-DO: refactor asynchronously -->
194194
<xsl:apply-templates select="key('resources', $service-uri, document($request-uri))" mode="ldh:Typeahead">
195195
<xsl:with-param name="forClass" select="$forClass"/>
@@ -267,7 +267,7 @@ exclude-result-prefixes="#all"
267267
<xsl:variable name="query-string" select="ixsl:call($yasqe, 'getValue', [])" as="xs:string"/> <!-- get query string from YASQE -->
268268
<xsl:variable name="service-control-group" select="descendant::div[contains-token(@class, 'control-group')][input[@name = 'pu'][@value = '&ldh;service']]" as="element()"/>
269269
<xsl:variable name="service-uri" select="$service-control-group/descendant::input[@name = 'ou']/ixsl:get(., 'value')" as="xs:anyURI?"/>
270-
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
270+
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ldh:href(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }, ()))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
271271
<xsl:variable name="endpoint" select="($service/sd:endpoint/@rdf:resource/xs:anyURI(.), sd:endpoint())[1]" as="xs:anyURI"/>
272272
<xsl:variable name="block" select="ancestor::div[contains-token(@class, 'block')][1]" as="element()"/>
273273
<xsl:variable name="container" select="$block" as="element()"/> <!-- since we're not in content mode -->
@@ -380,7 +380,7 @@ exclude-result-prefixes="#all"
380380
<xsl:variable name="yasqe" select="ixsl:get(ixsl:get(ixsl:window(), 'LinkedDataHub.yasqe'), $textarea-id)"/>
381381
<xsl:variable name="query-string" select="ixsl:call($yasqe, 'getValue', [])" as="xs:string"/> <!-- get query string from YASQE -->
382382
<xsl:variable name="service-uri" select="$form//select[contains-token(@class, 'input-query-service')]/ixsl:get(., 'value')" as="xs:anyURI?"/>
383-
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
383+
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ldh:href(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }, ()))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
384384
<xsl:variable name="endpoint" select="($service/sd:endpoint/@rdf:resource/xs:anyURI(.), sd:endpoint())[1]" as="xs:anyURI"/>
385385
<xsl:variable name="query-id" select="'id' || ac:uuid()" as="xs:string"/>
386386
<xsl:variable name="query-uri" select="xs:anyURI(ac:absolute-path(ldh:base-uri(.)) || '#' || $query-id)" as="xs:anyURI"/>
@@ -495,7 +495,7 @@ exclude-result-prefixes="#all"
495495
<xsl:variable name="yasqe" select="ixsl:get(ixsl:get(ixsl:window(), 'LinkedDataHub.yasqe'), $textarea/ixsl:get(., 'id'))"/>
496496
<xsl:variable name="query-string" select="ixsl:call($yasqe, 'getValue', [])" as="xs:string?"/> <!-- get query string from YASQE -->
497497
<xsl:variable name="service-uri" select="descendant::select[contains-token(@class, 'input-query-service')]/ixsl:get(., 'value')" as="xs:anyURI?"/>
498-
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
498+
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ldh:href(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }, ()))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
499499
<xsl:variable name="endpoint" select="($service/sd:endpoint/@rdf:resource/xs:anyURI(.), sd:endpoint())[1]" as="xs:anyURI"/>
500500
<xsl:variable name="query-type" select="ldh:query-type($query-string)" as="xs:string?"/>
501501

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/block/view.xsl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1741,7 +1741,7 @@ exclude-result-prefixes="#all"
17411741
<xsl:variable name="initial-var-name" select="$select-xml/json:map/json:array[@key = 'variables']/json:string[1]/substring-after(., '?')" as="xs:string"/>
17421742
<xsl:variable name="focus-var-name" select="$initial-var-name" as="xs:string"/>
17431743
<!-- service can be explicitly specified on content using ldh:service -->
1744-
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ac:build-uri(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
1744+
<xsl:variable name="service" select="if ($service-uri) then key('resources', $service-uri, document(ldh:href(ac:document-uri($service-uri), map{ 'accept': 'application/rdf+xml' }, ()))) else ()" as="element()?"/> <!-- TO-DO: refactor asynchronously -->
17451745
<xsl:variable name="endpoint" select="($service/sd:endpoint/@rdf:resource/xs:anyURI(.), sd:endpoint())[1]" as="xs:anyURI"/>
17461746

17471747
<xsl:choose>

src/main/webapp/static/com/atomgraph/linkeddatahub/xsl/bootstrap/2.3.2/client/functions.xsl

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -80,14 +80,11 @@ exclude-result-prefixes="#all"
8080
</xsl:function>
8181

8282
<xsl:function name="sd:endpoint" as="xs:anyURI">
83-
<!-- check for an active tab with a known endpoint; data-uri is on the <li> -->
83+
<!-- check for an active tab with a known endpoint; data-endpoint is on the <li> -->
8484
<xsl:variable name="active-tab-li" select="id('tab-bar-list', ixsl:page())/li[contains-token(@class, 'active')]" as="element()?"/>
85-
<xsl:variable name="active-tab-uri" select="if ($active-tab-li) then ixsl:get($active-tab-li, 'dataset.uri') else ()" as="xs:string?"/>
8685
<xsl:variable name="tab-endpoint" select="
87-
if ($active-tab-uri
88-
and ixsl:contains(ixsl:get(ixsl:window(), 'LinkedDataHub.tabs'), '`' || $active-tab-uri || '`')
89-
and ixsl:contains(ixsl:get(ixsl:get(ixsl:window(), 'LinkedDataHub.tabs'), '`' || $active-tab-uri || '`'), 'endpoint'))
90-
then xs:anyURI(ixsl:get(ixsl:get(ixsl:get(ixsl:window(), 'LinkedDataHub.tabs'), '`' || $active-tab-uri || '`'), 'endpoint'))
86+
if ($active-tab-li and ixsl:contains($active-tab-li, 'dataset.endpoint'))
87+
then xs:anyURI(ixsl:get($active-tab-li, 'dataset.endpoint'))
9188
else ()" as="xs:anyURI?"/>
9289
<!-- priority: active tab endpoint > global LinkedDataHub.endpoint > local SPARQL -->
9390
<xsl:sequence select="(

0 commit comments

Comments
 (0)