Skip to content

Instantly share code, notes, and snippets.

@wsalesky
Created February 6, 2015 18:49
Show Gist options
  • Save wsalesky/35fa36aafd60de571983 to your computer and use it in GitHub Desktop.
Save wsalesky/35fa36aafd60de571983 to your computer and use it in GitHub Desktop.
XQuery Facets POC
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('&amp;',$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>
};
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;', '&amp;amp;'), '''', '&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>
@wsalesky
Copy link
Author

wsalesky commented Feb 6, 2015

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment