Created
February 6, 2015 18:49
-
-
Save wsalesky/35fa36aafd60de571983 to your computer and use it in GitHub Desktop.
XQuery Facets POC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
xquery version "3.0"; | |
(:~ | |
: A facet library module | |
: | |
: Builds facets from nodes passed by search or browse modules. | |
: Uses group by | |
: Built for use with eXistdb, if using with eXistdb be sure to define your range | |
: indexes in your collectio.xconf file for best performance. | |
: | |
: @author Winona Salesky | |
: @version 1.0 | |
:) | |
module namespace facets="http://syriaca.org//facets"; | |
declare namespace tei="http://www.tei-c.org/ns/1.0"; | |
declare variable $facets:limit {request:get-parameter('limit', 5) cast as xs:integer}; | |
declare variable $facets:fq {request:get-parameter('fq', '') cast as xs:string}; | |
(:~ | |
: Creates the xpath string to be passed to browse or search function | |
: Assumes tei namespace | |
:) | |
declare function facets:facet-filter() as xs:string?{ | |
if($facets:fq != '') then | |
string-join( | |
for $facet in tokenize($facets:fq,'fq-') | |
let $facet-name := substring-before($facet,':') | |
let $facet-value := normalize-space(substring-after($facet,':')) | |
return | |
if($facet-value != '') then | |
concat('[descendant::tei:',$facet-name,'[normalize-space(.) = "',$facet-value,'"]]') | |
else(),'') | |
else () | |
}; | |
(:~ | |
: Build facets menu from nodes passed by search or browse module | |
: @param $facets nodes to facet on | |
: @param $facets:limit number of facets per catagory to display, defaults to 5 | |
:) | |
declare function facets:facets($facets as node()*) as element(){ | |
<div> | |
<span class="facets applied"> | |
{ | |
if($facets:fq) then facets:selected-facet-display() | |
else () | |
} | |
</span> | |
{ | |
for $facet-group in $facets | |
group by $category := node-name($facet-group) | |
return | |
let $cat := node-name($facet-group[1]) | |
return | |
<div class="category"> | |
<div> | |
<h4>{$cat}</h4> | |
{ | |
if($facets:limit) then | |
for $facet-list at $l in subsequence(facets:build-facet($facet-group,string($cat)),1,$facets:limit) | |
return $facet-list | |
else facets:build-facet($facet-group,string($cat)) | |
} | |
</div> | |
</div> | |
} | |
</div> | |
}; | |
(:~ | |
: Build facet parameters for passing to URL | |
:) | |
declare function facets:url-params() as xs:string?{ | |
string-join(for $param in request:get-parameter-names() | |
return | |
if($param = 'fq') then () | |
else if($param != '' and $param != ' ') then concat('&',$param, '=',normalize-space(request:get-parameter($param, ''))) | |
else (),'') | |
}; | |
(:~ | |
: Display selected facets and button to remove from results set | |
:) | |
declare function facets:selected-facet-display() as node()*{ | |
for $facet in tokenize($facets:fq,' fq-') | |
let $title := substring-after($facet,':') | |
let $new-fq := | |
string-join(for $facet-param in tokenize($facets:fq,' fq-') | |
return | |
if($facet-param = $facet) then () | |
else concat('fq-',$facet-param),' ') | |
let $href := concat('?fq=',$new-fq,facets:url-params()) | |
return | |
<span class="facet" title="Remove {$title}"> | |
<span class="label label-info" title="{$title}">{$title} | |
<a href="{$href}" class="facet icon"> | |
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> | |
</a> | |
</span> | |
</span> | |
}; | |
(:~ | |
: Build individual facet lists for each facet category | |
: @param $nodes nodes to facet on | |
:) | |
declare function facets:build-facet($nodes, $category) as node()*{ | |
for $facet in $nodes | |
group by $facet-grp := $facet | |
order by count($facet) descending | |
return | |
let $facet-val := $facet[1] | |
let $facet-query := concat('fq-',$category,':',normalize-space($facet-val)) | |
let $new-fq := | |
if($facets:fq) then concat('fq=',$facets:fq,' ',$facet-query) | |
else concat('fq=',$facet-query) | |
let $uri := string($facet-val/@ref) | |
return | |
<li><a href="?{$new-fq}{facets:url-params()}">{string($facet-val)}</a> ({count($facet)})</li> | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
xquery version "3.0"; | |
(:~ | |
: Simple search to demonstrate facets with TEI and eXistdb. | |
: Use with facet.xqm | |
: | |
: @author Winona Salesky | |
: @version 1.0 | |
: | |
:) | |
import module namespace facets="http://syriaca.org//facets" at "facets.xqm"; | |
declare namespace tei="http://www.tei-c.org/ns/1.0"; | |
declare namespace output = "http://www.w3.org/2010/xslt-xquery-serialization"; | |
declare option output:method "html5"; | |
declare option output:media-type "text/html"; | |
(: | |
: Search param. Simple keyword search | |
:) | |
declare variable $q {request:get-parameter('q', '') cast as xs:string?}; | |
(:~ | |
: Build query string to evaluate | |
:) | |
declare function local:query-string() as xs:string? { | |
concat("collection('PATH-TO-YOUR-DATA')//tei:body", | |
local:keyword-search(), | |
facets:facet-filter() | |
) | |
}; | |
(:~ | |
: Build simple keyword search using eXistdb full-text search functions | |
: Be sure to create a lucene index in collection.xconf | |
:) | |
declare function local:keyword-search() as xs:string?{ | |
if($q != '') then concat("[ft:query(.,'",local:clean-string($q),"')]") | |
else () | |
}; | |
(:~ | |
: Simple string function to replace some unwanted characters | |
:) | |
declare function local:clean-string($param-string as xs:string?) as xs:string?{ | |
replace(replace(replace($param-string, "(^|\W\*)|(^|\W\?)|[!@#$%^+=_]:", ""), '&', '&amp;'), '''', '&apos;') | |
}; | |
(:~ | |
: Build results page | |
:) | |
let $hits := util:eval(local:query-string()) | |
return | |
<html xmlns="http://www.w3.org/1999/xhtml"> | |
<head> | |
<!-- Latest compiled and minified CSS --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"/> | |
<!-- Optional theme --> | |
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css"/> | |
<!-- Latest compiled and minified JavaScript --> | |
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script> | |
</head> | |
<body style="margin:2em;"> | |
<!-- Call facets pass in any child elements you wish to facet on (make sure you have established a range index on them first) --> | |
<h1>Facets demo:</h1> | |
<div class="row"> | |
<div class="col-md-2"> | |
{ | |
let $facet-nodes := $hits | |
let $facets := $facet-nodes//tei:persName | $facet-nodes//tei:placeName | $facet-nodes//tei:event | |
return facets:facets($facets) | |
(:facets:facets($facets):) | |
} | |
</div> | |
<div class="col-md-10"> | |
{ | |
for $hit in $hits | |
order by ft:score($hit) descending | |
return | |
<div style="margin:.5em;border-bottom:1px solid #ccc;">{$hit}</div> | |
} | |
</div> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple proof of concept facets for eXistdb using xquery group by. Plug in your own TEI records, or borrow some from here https://github.com/srophe/srophe-eXist-app.
For syriaca.org we facet on @ref which eliminates some of the issues in faceting on tei fields such as persName which can be simple or nested elements, causing duplication in facets.
Remember to add fields you wish to facet on to your collection.xconf file before running.