|
3 | 3 | import java.io.IOException; |
4 | 4 | import java.io.Writer; |
5 | 5 |
|
| 6 | +import org.apache.lucene.document.IntPoint; |
6 | 7 | import org.apache.lucene.queries.spans.SpanQuery; |
| 8 | +import org.apache.lucene.search.BooleanClause; |
| 9 | +import org.apache.lucene.search.BooleanQuery; |
| 10 | +import org.apache.lucene.search.Query; |
7 | 11 | import org.apache.lucene.search.ScoreDoc; |
| 12 | +import org.apache.lucene.util.FixedBitSet; |
8 | 13 |
|
9 | 14 | import com.github.oeuvres.alix.lucene.Fluc; |
10 | 15 | import com.github.oeuvres.alix.lucene.FlucText; |
| 16 | +import com.github.oeuvres.alix.lucene.FlucYear; |
11 | 17 | import com.github.oeuvres.alix.lucene.HtmlResults; |
12 | 18 | import com.github.oeuvres.alix.lucene.LuceneIndex; |
13 | 19 | import com.github.oeuvres.alix.lucene.spans.SpanQueryParser; |
@@ -43,8 +49,73 @@ protected void page(LuceneIndex index, HttpServletRequest req, HttpServletRespon |
43 | 49 | <html> |
44 | 50 | <head> |
45 | 51 | <title>Alix, concordance</title> |
| 52 | + <link rel="stylesheet" |
| 53 | + href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.8.1/nouislider.min.css"> |
| 54 | + <style> |
| 55 | + #slider-year { margin: 1.5em 0.5em; } |
| 56 | + #slider-labels { display: flex; justify-content: space-between; font-size: 0.9em; } |
| 57 | + </style> |
46 | 58 | </head> |
47 | 59 | <body> |
| 60 | + """); |
| 61 | + // NoUiSlider |
| 62 | + FlucYear years = index.flucYear(YEAR); |
| 63 | + if (years != null) { |
| 64 | + writer.write(""" |
| 65 | + <div id="slider-year"></div> |
| 66 | + <div id="slider-labels"> |
| 67 | + <span id="label-start"></span> |
| 68 | + <span id="label-end"></span> |
| 69 | + </div> |
| 70 | + |
| 71 | + <script |
| 72 | + src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.8.1/nouislider.min.js"> |
| 73 | + </script> |
| 74 | + <script> |
| 75 | + (function () { |
| 76 | + // Corpus bounds injected by the server |
| 77 | + const MIN = %d; |
| 78 | + const MAX = %d; |
| 79 | + |
| 80 | + // Read current URL params to restore slider position |
| 81 | + const params = new URLSearchParams(location.search); |
| 82 | + const initStart = parseInt(params.get('start')) || MIN; |
| 83 | + const initEnd = parseInt(params.get('end')) || MAX; |
| 84 | + |
| 85 | + const slider = document.getElementById('slider-year'); |
| 86 | + |
| 87 | + noUiSlider.create(slider, { |
| 88 | + start: [initStart, initEnd], |
| 89 | + connect: true, |
| 90 | + step: 1, |
| 91 | + range: { min: MIN, max: MAX }, |
| 92 | + format: { |
| 93 | + to: v => Math.round(v), |
| 94 | + from: v => parseInt(v) |
| 95 | + } |
| 96 | + }); |
| 97 | + |
| 98 | + const labelStart = document.getElementById('label-start'); |
| 99 | + const labelEnd = document.getElementById('label-end'); |
| 100 | + |
| 101 | + slider.noUiSlider.on('update', function (values) { |
| 102 | + labelStart.textContent = values[0]; |
| 103 | + labelEnd.textContent = values[1]; |
| 104 | + }); |
| 105 | + |
| 106 | + // Submit on release — fires the search |
| 107 | + slider.noUiSlider.on('change', function (values) { |
| 108 | + const url = new URLSearchParams(location.search); |
| 109 | + url.set('start', values[0]); |
| 110 | + url.set('end', values[1]); |
| 111 | + location.search = url.toString(); |
| 112 | + }); |
| 113 | + }()); |
| 114 | + </script> |
| 115 | + """.formatted(years.min(), years.max())); |
| 116 | + } |
| 117 | + |
| 118 | + writer.write(""" |
48 | 119 | <form> |
49 | 120 | <textarea name="%s">%s</textarea> |
50 | 121 | <label> |
@@ -74,12 +145,12 @@ protected void page(LuceneIndex index, HttpServletRequest req, HttpServletRespon |
74 | 145 | protected void html(LuceneIndex index, HttpServletRequest req, HttpServletResponse resp) |
75 | 146 | throws IOException { |
76 | 147 | final long t0 = System.currentTimeMillis(); |
| 148 | + |
77 | 149 | final HttpPars pars = new HttpPars(req); |
78 | | - // the filter query |
79 | | - final int start = pars.getInt(START, -1); |
80 | | - final int end = pars.getInt(END, -1); |
81 | | - // the tags? |
82 | | - |
| 150 | + // Build a filter query from years and tags |
| 151 | + Query yearQuery = yearQuery(index, pars); |
| 152 | + Query filterQuery = yearQuery; |
| 153 | + |
83 | 154 | Writer writer = resp.getWriter(); |
84 | 155 | final String content = pars.getString(F, index.content()); |
85 | 156 | final FlucText fluc = index.flucText(content); |
@@ -127,6 +198,23 @@ protected void html(LuceneIndex index, HttpServletRequest req, HttpServletRespon |
127 | 198 |
|
128 | 199 | final String q = pars.getString(Q, null); |
129 | 200 |
|
| 201 | + FixedBitSet bits = null; |
| 202 | + if (filterQuery != null) { |
| 203 | + bits = new FixedBitSet(index.reader().maxDoc()); |
| 204 | + index.searcher().search(filterQuery, new SimpleCollector() { |
| 205 | + private int docBase; |
| 206 | + @Override |
| 207 | + public void collect(int docLeaf) { |
| 208 | + bits.set(docBase + docLeaf); |
| 209 | + } |
| 210 | + @Override |
| 211 | + protected void doSetNextReader(LeafReaderContext ctx) { |
| 212 | + this.docBase = ctx.docBase; |
| 213 | + } |
| 214 | + @Override |
| 215 | + public ScoreMode scoreMode() { return ScoreMode.COMPLETE_NO_SCORES; } |
| 216 | + }); |
| 217 | + } |
130 | 218 | // no query, list docs |
131 | 219 | if (q == null) { |
132 | 220 | final int rows = pars.getInt(ROWS, ROWS_RANGE, ROWS_DEFAULT, ROWS); |
@@ -159,19 +247,30 @@ protected void html(LuceneIndex index, HttpServletRequest req, HttpServletRespon |
159 | 247 | } |
160 | 248 |
|
161 | 249 | final int slop = pars.getInt(SLOP, SLOP_RANGE, SLOP_DEFAULT, SLOP); |
162 | | - SpanQuery query = new SpanQueryParser(content, slop).parse(q); |
| 250 | + SpanQuery spanQuery = new SpanQueryParser(content, slop).parse(q); |
| 251 | + |
163 | 252 |
|
164 | 253 |
|
165 | 254 | // sorted? |
166 | 255 | final boolean sorted = pars.getBoolean(SORTED, false, SORTED); |
167 | 256 | // relevance |
168 | 257 | if (!sorted) { |
| 258 | + Query query; |
| 259 | + if (filterQuery != null) { |
| 260 | + query = new BooleanQuery.Builder() |
| 261 | + .add(filterQuery, BooleanClause.Occur.FILTER) |
| 262 | + .add(spanQuery, BooleanClause.Occur.MUST) |
| 263 | + .build(); |
| 264 | + } |
| 265 | + else { |
| 266 | + query = spanQuery; |
| 267 | + } |
169 | 268 | ScoreDoc[] hits = index.searcher().search(query, 10).scoreDocs; |
170 | | - |
| 269 | + // TODO loop and use the spanVisitor to test the best spans |
171 | 270 | } |
172 | 271 |
|
173 | 272 |
|
174 | | - SpanWalker walker = new SpanWalker(index.searcher(), query, null, results); |
| 273 | + SpanWalker walker = new SpanWalker(index.searcher(), spanQuery, filterQuery, results); |
175 | 274 | writer |
176 | 275 | .append("<p class=\"statshits\">") |
177 | 276 | .append(String.valueOf(walker.hits())) |
|
0 commit comments