Skip to content

Commit

Permalink
Merge pull request #29 from eeditiones/facets
Browse files Browse the repository at this point in the history
Clean up configuration for facets
  • Loading branch information
wolfgangmm authored Aug 28, 2024
2 parents 1128952 + 0d6f8f6 commit 202d387
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 28 deletions.
28 changes: 0 additions & 28 deletions profiles/base10/modules/config.xqm
Original file line number Diff line number Diff line change
Expand Up @@ -120,34 +120,6 @@ declare variable $config:pagination-depth := $gen:pagination-depth;
:)
declare variable $config:pagination-fill := $gen:pagination-fill;

(:
: Display configuration for facets to be shown in the sidebar. The facets themselves
: are configured in the index configuration, collection.xconf.
:)
declare variable $config:facets := [
map {
"dimension": "genre",
"heading": "facets.genre",
"max": 5,
"hierarchical": true()
},
map {
"dimension": "language",
"heading": "facets.language",
"max": 5,
"hierarchical": false(),
"output": function($label) {
switch($label)
case "de" return "German"
case "es" return "Spanish"
case "la" return "Latin"
case "fr" return "French"
case "en" return "English"
default return $label
}
}
];

(:
: The function to be called to determine the next content chunk to display.
: It takes two parameters:
Expand Down
33 changes: 33 additions & 0 deletions profiles/base10/modules/facets-config.xql
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module namespace facets-config="http://teipublisher.com/api/facets-config";

import module namespace config="http://www.tei-c.org/tei-simple/config" at "config.xqm";

declare namespace tei="http://www.tei-c.org/ns/1.0";

declare function facets-config:get-name($id as xs:string, $type as xs:string) as xs:string {
let $entity := collection($config:register-root)/id($id)
return head((
switch ($type)
case 'place' return head(($entity//tei:placeName[@type = "main"], $entity//tei:placeName))
case 'actor' return head(($entity//(tei:persName | tei:orgName)[@type = "main"], $entity//tei:placeName))
default return "ERR",
"Unresolvable entity " || $id || " of type " || $type)
)
};

declare %public variable $facets-config:facets as array(*) := [
map {
"dimension": "place",
"heading": "facets.place",
"max": 5,
"hierarchical": false(),
"output": facets-config:get-name(?, 'place')
},
map {
"dimension": "actor",
"heading": "facets.actor",
"max": 5,
"hierarchical": false(),
"output": facets-config:get-name(?, 'actor')
}
];
166 changes: 166 additions & 0 deletions profiles/base10/modules/facets.xql
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
(:
:
: Copyright (C) 2019 Wolfgang Meier
:
: This program is free software: you can redistribute it and/or modify
: it under the terms of the GNU General Public License as published by
: the Free Software Foundation, either version 3 of the License, or
: (at your option) any later version.
:
: This program is distributed in the hope that it will be useful,
: but WITHOUT ANY WARRANTY; without even the implied warranty of
: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
: GNU General Public License for more details.
:
: You should have received a copy of the GNU General Public License
: along with this program. If not, see <http://www.gnu.org/licenses/>.
:)
xquery version "3.1";

module namespace facets="http://teipublisher.com/facets";

import module namespace config="http://www.tei-c.org/tei-simple/config" at "config.xqm";

declare namespace tei="http://www.tei-c.org/ns/1.0";

declare function facets:sort($config as map(*), $lang as xs:string?, $facets as map(*)?) {
array {
if (exists($facets)) then
for $key in map:keys($facets)
let $value := map:get($facets, $key)
let $sortKey := facets:translate($config, $lang, $key)
order by $sortKey ascending
return
map { $key: $value }
else
()
}
};

declare function facets:print-table($config as map(*), $nodes as element()+, $values as xs:string*, $params as xs:string*) {
let $all := exists($config?max) and facets:get-parameter("all-" || $config?dimension)
let $lang := tokenize(facets:get-parameter("language"), '-')[1]
let $count := if ($all) then 50 else $config?max
let $facets :=
if (exists($values)) then
ft:facets($nodes, $config?dimension, $count, $values)
else
ft:facets($nodes, $config?dimension, $count)
return
if (map:size($facets) > 0) then
<table>
{
array:for-each(facets:sort($config, $lang, $facets), function($entry) {
map:for-each($entry, function($label, $freq) {
let $content := facets:translate($config, $lang, $label)
return
<tr>
<td>
<paper-checkbox class="facet" name="facet-{$config?dimension}" value="{$label}">
{ if ($label = $params) then attribute checked { "checked" } else () }
<pb-i18n key="{$content}">{$content}</pb-i18n>
</paper-checkbox>
</td>
<td>{$freq}</td>
</tr>,
if (empty($params)) then
()
else
let $nested := facets:print-table($config, $nodes, ($values, head($params)), tail($params))
return
if ($nested and head($params) eq $label) then
<tr class="nested">
<td colspan="2">
{$nested}
</td>
</tr>
else
()
})
})
}
</table>
else
()
};

declare function facets:display($config as map(*), $nodes as element()+) {
let $params := facets:get-parameter("facet-" || $config?dimension)
let $lang := tokenize(facets:get-parameter("language"), '-')[1]
let $table := facets:print-table($config, $nodes, (), $params)

let $maxcount := 50
(: maximum number shown :)
let $max := head(($config?max, 50))

(: facet count for current values selected :)
let $fcount :=
map:size(
if (count($params)) then
ft:facets($nodes, $config?dimension, $maxcount, $params)
else
ft:facets($nodes, $config?dimension, $maxcount)
)

where $table
return (
<div class="facet-dimension" data-dimension="facet-{$config?dimension}">
<h3><pb-i18n key="{$config?heading}">{$config?heading}</pb-i18n>
{
if ($fcount > $max) then
<paper-checkbox class="facet" name="all-{$config?dimension}">
{ if (facets:get-parameter("all-" || $config?dimension)) then attribute checked { "checked" } else () }
<pb-i18n key="facets.show">Show top 50</pb-i18n>
</paper-checkbox>
else
()
}
</h3>
{
$table,
(: if config specifies a property "source", output combo-box :)
if (map:contains($config, "source")) then
(: use source as URL to API endpoint from which to retrieve possible values :)
<pb-combo-box source="{$config?source}" close-after-select="" placeholder="{$config?heading}"
>
<select multiple="">
{
for $param in facets:get-parameter("facet-" || $config?dimension)
let $label := facets:translate($config, $lang, $param)
return
<option value="{$param}" data-i18n="{$label}" selected="">{$label}</option>
}
</select>
</pb-combo-box>
else
()
}
</div>
)
};

declare function facets:get-parameter($name as xs:string) {
let $param := request:get-parameter($name, ())
return
if (exists($param)) then
$param
else
let $fromSession := session:get-attribute($config:session-prefix || '.params')
return
if (exists($fromSession)) then
$fromSession?($name)
else
()
};

declare function facets:translate($config as map(*)?, $language as xs:string?, $label as xs:string) {
if (exists($config) and map:contains($config, "output")) then
let $fn := $config?output
return
if (function-arity($fn) = 2) then
$fn($label, $language)
else
$fn($label)
else
$label
};
150 changes: 150 additions & 0 deletions profiles/base10/modules/lib/api/search.xql
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
xquery version "3.1";

module namespace sapi="http://teipublisher.com/api/search";

import module namespace query="http://www.tei-c.org/tei-simple/query" at "../../query.xql";
import module namespace nav="http://www.tei-c.org/tei-simple/navigation" at "../../navigation.xql";
import module namespace config="http://www.tei-c.org/tei-simple/config" at "../../config.xqm";
import module namespace tpu="http://www.tei-c.org/tei-publisher/util" at "../util.xql";
import module namespace kwic="http://exist-db.org/xquery/kwic" at "resource:org/exist/xquery/lib/kwic.xql";
import module namespace facets="http://teipublisher.com/facets" at "../../facets.xql";
import module namespace facets-config="http://teipublisher.com/api/facets-config" at "../../facets-config.xqm";

declare namespace tei="http://www.tei-c.org/ns/1.0";

declare function sapi:autocomplete($request as map(*)) {
let $q := request:get-parameter("query", ())
let $type := request:get-parameter("field", "text")
let $doc := request:get-parameter("doc", ())
let $items :=
if ($q) then
query:autocomplete($doc, $type, $q)
else
()
return
array {
for $item in $items
group by $item
return
map {
"text": $item,
"value": $item
}
}
};

declare function sapi:search($request as map(*)) {
(:If there is no query string, fill up the map with existing values:)
if (empty($request?parameters?query))
then
sapi:show-hits($request, session:get-attribute($config:session-prefix || ".hits"), session:get-attribute($config:session-prefix || ".docs"))
else
(:Otherwise, perform the query.:)
(: Here the actual query commences. This is split into two parts, the first for a Lucene query and the second for an ngram query. :)
(:The query passed to a Luecene query in ft:query is an XML element <query> containing one or two <bool>. The <bool> contain the original query and the transliterated query, as indicated by the user in $query-scripts.:)
let $hitsAll :=
(:If the $query-scope is narrow, query the elements immediately below the lowest div in tei:text and the four major element below tei:teiHeader.:)
for $hit in query:query-default($request?parameters?field, $request?parameters?query, $request?parameters?doc, ())
order by ft:score($hit) descending
return $hit
let $hitCount := count($hitsAll)
let $hits := if ($hitCount > 1000) then subsequence($hitsAll, 1, 1000) else $hitsAll
(:Store the result in the session.:)
let $store := (
session:set-attribute($config:session-prefix || ".hits", $hitsAll),
session:set-attribute($config:session-prefix || ".hitCount", $hitCount),
session:set-attribute($config:session-prefix || ".search", $request?parameters?query),
session:set-attribute($config:session-prefix || ".field", $request?parameters?field),
session:set-attribute($config:session-prefix || ".docs", $request?parameters?doc)
)
return
sapi:show-hits($request, $hits, $request?parameters?doc)
};

declare %private function sapi:show-hits($request as map(*), $hits as item()*, $docs as xs:string*) {
response:set-header("pb-total", xs:string(count($hits))),
response:set-header("pb-start", xs:string($request?parameters?start)),
for $hit at $p in subsequence($hits, $request?parameters?start, $request?parameters?per-page)
let $config := tpu:parse-pi(root($hit), $request?parameters?view)
let $parent := query:get-parent-section($config, $hit)
let $parent-id := config:get-identifier($parent)
let $parent-id := if (exists($docs)) then replace($parent-id, "^.*?([^/]*)$", "$1") else $parent-id
let $div := query:get-current($config, $parent)
let $expanded := util:expand($hit, "add-exist-id=all")
let $docId := config:get-identifier($div)
return
<paper-card>
<header>
<div class="count">{$request?parameters?start + $p - 1}</div>
{ query:get-breadcrumbs($config, $hit, $parent-id) }
</header>
<div class="matches">
{
for $match in subsequence($expanded//exist:match, 1, 5)
let $matchId := $match/../@exist:id
let $docLink :=
if ($config?view = "page") then
(: first check if there's a pb in the expanded section before the match :)
let $pbBefore := $match/preceding::tei:pb[1]
return
if ($pbBefore) then
$pbBefore/@exist:id
else
(: no: locate the element containing the match in the source document :)
let $contextNode := util:node-by-id($hit, $matchId)
(: and get the pb preceding it :)
let $page := $contextNode/preceding::tei:pb[1]
return
if ($page) then
util:node-id($page)
else
util:node-id($div)
else
(: Check if the document has sections, otherwise don't pass root :)
if (nav:get-section-for-node($config, $div)) then util:node-id($div) else ()
let $config := <config width="60" table="no" link="{$docId}?{if ($docLink) then 'root=' || $docLink || '&amp;' else ()}action=search&amp;view={$config?view}&amp;odd={$config?odd}#{$matchId}"/>
return
kwic:get-summary($expanded, $match, $config)
}
</div>
</paper-card>
};

declare function sapi:facets($request as map(*)) {

let $hits := session:get-attribute($config:session-prefix || ".hits")
where count($hits) > 0
return
<div>
{
for $config in $facets-config:facets?*
return
facets:display($config, $hits)
}
</div>
};

declare function sapi:list-facets($request as map(*)) {
let $type := $request?parameters?type
let $lang := tokenize($request?parameters?language, '-')[1]
let $facetConfig := filter($facets-config:facets?*, function($facetCfg) {
$facetCfg?dimension = $type
})
let $hits := session:get-attribute($config:session-prefix || ".hits")
let $facets := ft:facets($hits, $type, ())
let $matches :=
for $key in if (exists($request?parameters?value)) then $request?parameters?value else map:keys($facets)
let $label := facets:translate($facetConfig, $lang, $key)
return
map {
"text": $label,
"freq": $facets($key),
"value": $key
}

let $filtered := filter($matches, function($item) {
matches($item?text, '(?:^|\W)' || $request?parameters?query, 'i')
})
return
array { $filtered }
};

0 comments on commit 202d387

Please sign in to comment.