Skip to content

Commit 1636bed

Browse files
committed
Update proximity search.
1 parent 14c464d commit 1636bed

5 files changed

Lines changed: 126 additions & 36 deletions

File tree

srophe-app/modules/browse.xqm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ declare function browse:display-hits($hits){
309309
string($data/descendant::tei:ref/@target)
310310
else ()
311311
else $data/descendant::tei:idno[@type='URI'][1]/text()
312-
return tei2html:summary-view($data, $entryLink,())
312+
return tei2html:summary-view($data, $entryLink)
313313
};
314314

315315
(: Display map :)

srophe-app/modules/content-negotiation/tei2html.xqm

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -93,31 +93,27 @@ declare function tei2html:tei2html($nodes as node()*) as item()* {
9393
(:
9494
: Used for short views of records, browse, search or related items display.
9595
:)
96-
declare function tei2html:summary-view($nodes as node()*,$id as xs:string?, $kwic as node()*) as item()* {
96+
declare function tei2html:summary-view($nodes as node()*,$id as xs:string?) as item()* {
9797
if($nodes/descendant-or-self::tei:ab[@type='crossreference']) then
9898
tei2html:summary-view-crossref($nodes,$id)
99-
else tei2html:summary-view-generic($nodes,$id,$kwic)
99+
else tei2html:summary-view-generic($nodes,$id)
100100
};
101101

102102
(: Generic short view template :)
103-
declare function tei2html:summary-view-generic($nodes as node()*, $id as xs:string?, $kwic as node()*) as item()* {
103+
declare function tei2html:summary-view-generic($nodes as node()*, $id as xs:string?) as item()* {
104104
let $recID := tokenize($id,'/')[last()]
105105
return
106106
<div class="results-list {if($nodes[@type = ('subsection','subSubsection')]) then 'indent' else ()}">
107107
<span class="sort-title">
108108
<a href="{$global:nav-base}/{$recID}">{$nodes/tei:head}</a>
109109
<span class="type">{$nodes/tei:ab[@type='infobox']}</span>
110110
</span>
111-
{(if($nodes/descendant::tei:byline) then
111+
{if($nodes/descendant::tei:byline) then
112112
<span class="results-list-desc sort-title">
113113
<span>Contributor: </span>
114114
<i>{$nodes/descendant::tei:byline/tei:persName}</i>
115115
</span>
116-
else (),
117-
if($kwic != '') then
118-
<span class="results-list-desc type">{tei2html:output-kwic($kwic, $id)}</span>
119-
else ()
120-
)}
116+
else ()}
121117
<span class="results-list-desc uri">
122118
<span class="srp-label">URI: </span>
123119
<a href="{$global:nav-base}/{$recID}">{$id}</a>
@@ -149,21 +145,29 @@ declare function tei2html:summary-view-crossref($nodes as node()*, $id as xs:str
149145
else concat('&amp;',$param, '=',request:get-parameter($param, '')),'')
150146
:)
151147
declare function tei2html:output-kwic($nodes as node()*, $id as xs:string*){
152-
let $results := <results xmlns="http://www.w3.org/1999/xhtml">{tei2html:kwic-format($nodes)}</results>
148+
let $results := <results xmlns="http://www.w3.org/1999/xhtml">{
149+
if(request:get-parameter('keywordProximity', '') castable as xs:integer) then
150+
let $wordList :=
151+
string-join(for $word in tokenize(request:get-parameter('q', ''),'\s')
152+
return $word,concat('\W+(\w+\W+){1,',request:get-parameter('keywordProximity', ''),'}?'))
153+
let $pattern := concat('(',$wordList,')\W+(\w+\W+){1,',request:get-parameter('keywordProximity', ''),'}?(',$wordList,')')
154+
let $highlight := function($string as xs:string) { <match xmlns="http://www.w3.org/1999/xhtml">{$string}</match> }
155+
return tei2html:highlight-matches($nodes, $wordList, $highlight)
156+
else tei2html:kwic-format($nodes)
157+
}</results>
153158
let $count := count($results//*:match)
154159
for $node at $p in subsequence($results//*:match,1,8)
155160
let $prev := $node/preceding-sibling::text()[1]
156161
let $next := $node/following-sibling::text()[1]
157162
let $prevString :=
158-
if(string-length($prev) gt 60) then
159-
concat(' ...',substring($prev,string-length($prev) - 100, 100))
160-
else $prev
163+
if(string-length($prev) gt 60) then
164+
concat(' ...',substring($prev,string-length($prev) - 100, 100))
165+
else $prev
161166
let $nextString :=
162-
if(string-length($next) lt 100 ) then ()
163-
else concat(substring($next,1,100),'... ')
167+
if(string-length($next) lt 100 ) then ()
168+
else concat(substring($next,1,100),'... ')
164169
let $link := concat($global:nav-base,'/',tokenize($id,'/')[last()],'#',$node/@n)
165-
return
166-
<span>{$prevString}&#160;<span class="match" style="background-color:yellow;"><a href="{$link}">{$node/text()}</a></span>&#160;{$nextString}</span>
170+
return <span>{$prevString}&#160;<span class="match" style="background-color:yellow;"><a href="{$link}">{$node/text()}</a></span>&#160;{$nextString}</span>
167171
};
168172

169173
(:~
@@ -176,13 +180,31 @@ declare function tei2html:kwic-format($nodes as node()*){
176180
typeswitch($node)
177181
case text() return $node
178182
case comment() return ()
179-
case element(exist:match) return
180-
let $n := if($node/ancestor-or-self::*[@n]) then concat('Head-id.',$node/ancestor-or-self::*[@n][1]/@n) else ()
181-
return
183+
case element(exist:match) return
182184
<match xmlns="http://www.w3.org/1999/xhtml">
183-
{(if($n != '') then attribute n {$n} else (),
184-
$node/node()
185-
)}
186-
</match>
185+
{(if($node/*[@n]) then attribute n {concat('n-id.',$node/@n)} else (), $node/node())}</match>
187186
default return tei2html:kwic-format($node/node())
187+
};
188+
189+
(:~
190+
: Highlight matches for proximity search.
191+
: @see https://gist.github.com/joewiz/5937897
192+
:)
193+
declare function tei2html:highlight-matches($nodes as node()*, $pattern as xs:string, $highlight as function(xs:string) as item()* ) {
194+
for $node in $nodes
195+
return
196+
typeswitch ( $node )
197+
case element() return
198+
tei2html:highlight-matches($node/node(), $pattern, $highlight)
199+
case text() return
200+
for $segment in analyze-string($node, $pattern, 'i')/node()
201+
return
202+
if ($segment instance of element(fn:match)) then
203+
$highlight($segment/string())
204+
else
205+
$segment/string()
206+
case document-node() return
207+
document { tei2html:highlight-matches($node/node(), $pattern, $highlight) }
208+
default return
209+
$node
188210
};

srophe-app/modules/lib/global.xqm

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
xquery version "3.0";
22
(: Global app variables and functions. :)
33
module namespace global="http://syriaca.org/global";
4+
import module namespace functx="http://www.functx.com";
5+
46
declare namespace http="http://expath.org/ns/http-client";
57
declare namespace repo="http://exist-db.org/xquery/repo";
68
declare namespace tei="http://www.tei-c.org/ns/1.0";
@@ -169,7 +171,7 @@ return $final-id
169171
declare function global:get-rec($id as xs:string){
170172
for $rec in collection($global:data-root)//tei:div[tei:ab/tei:idno[normalize-space(.) = $id]]
171173
let $header := $rec/ancestor::tei:TEI/tei:teiHeader
172-
return <tei:TEI xmlns="http://www.tei-c.org/ns/1.0">{$header, $rec}</tei:TEI>
174+
return <TEI xmlns="http://www.tei-c.org/ns/1.0">{$header, $rec}</TEI>
173175
};
174176

175177
(:~
@@ -247,4 +249,4 @@ declare function global:get-syriaca-refs($url as xs:string*){
247249
} catch * {
248250
concat($err:code, ": ", $err:description)
249251
}
250-
};
252+
};

srophe-app/modules/search/search.xqm

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import module namespace tei2html="http://syriaca.org/tei2html" at "../content-ne
88
import module namespace global="http://syriaca.org/global" at "../lib/global.xqm";
99
import module namespace kwic="http://exist-db.org/xquery/kwic";
1010
import module namespace templates="http://exist-db.org/xquery/templates" ;
11+
import module namespace functx="http://www.functx.com";
1112

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

@@ -54,7 +55,8 @@ declare %templates:wrap function search:get-results($node as node(), $model as m
5455
declare function search:query-string($collection as xs:string?) as xs:string?{
5556
if($collection !='') then
5657
concat("collection('",$global:data-root,"/",$collection,"')//tei:body",
57-
common:keyword(),
58+
(:common:keyword(),:)
59+
search:keyword(),
5860
search:author(),
5961
search:persName(),
6062
search:placeName(),
@@ -63,7 +65,8 @@ if($collection !='') then
6365
)
6466
else
6567
concat("collection('",$global:data-root,"')//tei:div[@type=('entry','crossreference','section','subsection')][tei:ab[@type='idnos']]",
66-
common:keyword(),
68+
(: common:keyword(),:)
69+
search:keyword(),
6770
search:author(),
6871
search:persName(),
6972
search:placeName(),
@@ -112,7 +115,7 @@ declare function search:idno(){
112115

113116
declare function search:search-string(){
114117
<span xmlns="http://www.w3.org/1999/xhtml">
115-
{(
118+
{
116119
let $parameters := request:get-parameter-names()
117120
for $parameter in $parameters
118121
return
@@ -127,7 +130,7 @@ declare function search:search-string(){
127130
else if($parameter = 'placeName') then
128131
(<span class="param">Place: </span>,<span class="match">{$search:placeName}&#160;</span>)
129132
else (<span class="param">{replace(concat(upper-case(substring($parameter,1,1)),substring($parameter,2)),'-',' ')}: </span>,<span class="match">{request:get-parameter($parameter, '')}</span>)
130-
else ())
133+
else ()
131134
}
132135
</span>
133136
};
@@ -227,7 +230,6 @@ function search:show-hits($node as node()*, $model as map(*), $collection as xs:
227230
<div>{search:build-geojson($node,$model)}</div>
228231
{
229232
for $hit at $p in subsequence($model("hits"), $search:start, $search:perpage)
230-
let $kwic := kwic:expand($hit)
231233
let $uri := if($hit/@type='crossreference') then
232234
string($hit/descendant::tei:ref/@target)
233235
else $hit/descendant::tei:idno[@type='URI'][1]/text()
@@ -238,7 +240,16 @@ function search:show-hits($node as node()*, $model as map(*), $collection as xs:
238240
<span class="label label-default">{$search:start + $p - 1}</span>
239241
</div>
240242
<div class="col-md-9" xml:lang="en">
241-
{tei2html:summary-view($hit, $uri, $kwic)}
243+
{tei2html:summary-view-generic($hit, $uri)}
244+
{if(request:get-parameter('keywordProximity', '') castable as xs:integer) then
245+
tei2html:output-kwic($hit,$uri)
246+
else
247+
let $expanded := util:expand($hit)
248+
return
249+
if($expanded//exist:match) then
250+
tei2html:output-kwic($expanded, $uri)
251+
else ()
252+
}
242253
</div>
243254
</div>
244255
</div>
@@ -277,10 +288,18 @@ declare function search:search-form() {
277288
<div class="row">
278289
<div class="col-md-7">
279290
<!-- Keyword -->
280-
<div class="form-group">
291+
<div class="form-group">
281292
<label for="q" class="col-sm-2 col-md-3 control-label">Keyword: </label>
282293
<div class="col-sm-10 col-md-9 ">
283-
<input type="text" id="q" name="q" class="form-control"/>
294+
<div class="input-group">
295+
<input type="text" id="qs" name="q" class="form-control keyboard"/>
296+
<div class="input-group-btn">
297+
<input type="text" id="keywordProximity" name="keywordProximity" class="form-control"/>
298+
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="Proximity Operator">
299+
&#160; Proximity *
300+
</button>
301+
</div>
302+
</div>
284303
</div>
285304
</div>
286305
<div class="form-group">
@@ -329,3 +348,50 @@ declare function search:search-form() {
329348
</div>
330349
</form>
331350
};
351+
352+
(: e-gedsh search functions :)
353+
declare function search:strip-chars($string){
354+
let $query-string := $string
355+
let $query-string :=
356+
if (functx:number-of-matches($query-string, '"') mod 2) then
357+
replace($query-string, '"', ' ')
358+
else $query-string (:if there is an uneven number of quotation marks, delete all quotation marks.:)
359+
let $query-string :=
360+
if ((functx:number-of-matches($query-string, '\(') + functx:number-of-matches($query-string, '\)')) mod 2 eq 0)
361+
then $query-string
362+
else translate($query-string, '()', ' ') (:if there is an uneven number of parentheses, delete all parentheses.:)
363+
let $query-string :=
364+
if ((functx:number-of-matches($query-string, '\[') + functx:number-of-matches($query-string, '\]')) mod 2 eq 0)
365+
then $query-string
366+
else translate($query-string, '[]', ' ') (:if there is an uneven number of brackets, delete all brackets.:)
367+
let $query-string := replace($string,"'","''")
368+
return
369+
if(matches($query-string,"(^\*$)|(^\?$)")) then 'Invalid Search String, please try again.' (: Must enter some text with wildcard searches:)
370+
else replace(replace($query-string,'<|>|@',''), '(\.|\[|\]|\\|\||\-|\^|\$|\+|\{|\}|\(|\)|(/))','\\$1') (: Escape special characters. Fixes error, but does not return correct results on URIs see: http://viaf.org/viaf/sourceID/SRP|person_308 :)
371+
};
372+
373+
(:~
374+
: Search options passed to ft:query functions
375+
:)
376+
declare function search:options($proximity){
377+
let $phrase-slop := if($proximity castable as xs:integer) then xs:integer($proximity) else xs:integer(1)
378+
return
379+
<options>
380+
<default-operator>and</default-operator>
381+
<phrase-slop>{$phrase-slop}</phrase-slop>
382+
<leading-wildcard>yes</leading-wildcard>
383+
<filter-rewrite>yes</filter-rewrite>
384+
</options>
385+
};
386+
387+
(:
388+
: Build full-text keyword search over full record data
389+
:)
390+
declare function search:keyword(){
391+
if(request:get-parameter('q', '') != '') then
392+
let $string := if(request:get-parameter('keywordProximity', '') castable as xs:integer) then
393+
concat('"',search:strip-chars(request:get-parameter('q', '')),'"','~',request:get-parameter('keywordProximity', ''))
394+
else search:strip-chars(request:get-parameter('q', ''))
395+
return concat("[ft:query(descendant::*,'",$string,"')]")
396+
else ()
397+
};

srophe-app/repo-config.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<!-- Web Site URL -->
2323
<url/>
2424
<!-- Root of app for building dynamic links. Default is eXist app root -->
25-
<nav-base>/exist/apps/e-gedsh</nav-base>
25+
<nav-base>https://gedsh.bethmardutho.org</nav-base>
2626
<!-- eXist app root for app deployment-->
2727
<app-root>e-gedsh</app-root>
2828
<!-- eXist data app root for gazetteer data -->

0 commit comments

Comments
 (0)