Skip to content

Commit 9169159

Browse files
committed
On step more for yearQuery
1 parent 00d9486 commit 9169159

3 files changed

Lines changed: 149 additions & 10 deletions

File tree

web/src/main/java/com/github/oeuvres/alix/web/Op.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package com.github.oeuvres.alix.web;
22

3-
import static com.github.oeuvres.alix.web.Pars.Q;
3+
import static com.github.oeuvres.alix.web.Pars.END;
4+
import static com.github.oeuvres.alix.web.Pars.START;
5+
import static com.github.oeuvres.alix.web.Pars.YEAR;
46

57
import java.io.IOException;
6-
import java.io.Writer;
8+
9+
import org.apache.lucene.document.IntPoint;
10+
import org.apache.lucene.search.Query;
711

812
import com.google.gson.stream.JsonWriter;
913

1014
import jakarta.servlet.http.HttpServletRequest;
1115
import jakarta.servlet.http.HttpServletResponse;
1216

17+
import com.github.oeuvres.alix.lucene.FlucYear;
1318
import com.github.oeuvres.alix.lucene.LuceneIndex;
1419
import com.github.oeuvres.alix.web.util.HttpPars;
1520

@@ -125,4 +130,38 @@ protected static void prepareHtmlStream(final HttpServletResponse resp)
125130
resp.setHeader("Content-Encoding", "identity");
126131
resp.setHeader("X-Content-Type-Options", "nosniff");
127132
}
133+
134+
/**
135+
* Build a year query for all ops from normalized params across app
136+
* @return
137+
* @throws IOException
138+
*/
139+
Query yearQuery(LuceneIndex index, HttpPars pars) throws IOException {
140+
int start = pars.getInt(START, Integer.MIN_VALUE);
141+
int end = pars.getInt(END, Integer.MAX_VALUE);
142+
if (start == Integer.MIN_VALUE && end == Integer.MAX_VALUE) return null;
143+
// swap if inverted — be lenient with the UI
144+
if (start != Integer.MIN_VALUE && end != Integer.MAX_VALUE && start > end) {
145+
final int tmp = end; end = start; start = tmp;
146+
}
147+
// a bit hard coded name for now
148+
FlucYear years = index.flucYear(YEAR);
149+
if (years == null) {
150+
// no need to inform html consumer
151+
// problem may come from a generic interface
152+
return null;
153+
}
154+
final int min = (int) years.min();
155+
final int max = (int) years.max();
156+
// resolve open bounds to corpus bounds
157+
if (start == Integer.MIN_VALUE) start = min;
158+
if (end == Integer.MAX_VALUE) end = max;
159+
// clamp to corpus bounds
160+
start = Math.max(start, min);
161+
end = Math.min(end, max);
162+
// after clamping, range may have collapsed out of corpus
163+
if (start > end) return null;
164+
if (start == end) return IntPoint.newExactQuery(YEAR, start);
165+
return IntPoint.newRangeQuery(YEAR, start, end);
166+
}
128167
}

web/src/main/java/com/github/oeuvres/alix/web/OpResults.java

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
import java.io.IOException;
44
import java.io.Writer;
55

6+
import org.apache.lucene.document.IntPoint;
67
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;
711
import org.apache.lucene.search.ScoreDoc;
12+
import org.apache.lucene.util.FixedBitSet;
813

914
import com.github.oeuvres.alix.lucene.Fluc;
1015
import com.github.oeuvres.alix.lucene.FlucText;
16+
import com.github.oeuvres.alix.lucene.FlucYear;
1117
import com.github.oeuvres.alix.lucene.HtmlResults;
1218
import com.github.oeuvres.alix.lucene.LuceneIndex;
1319
import com.github.oeuvres.alix.lucene.spans.SpanQueryParser;
@@ -43,8 +49,73 @@ protected void page(LuceneIndex index, HttpServletRequest req, HttpServletRespon
4349
<html>
4450
<head>
4551
<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>
4658
</head>
4759
<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("""
48119
<form>
49120
<textarea name="%s">%s</textarea>
50121
<label>
@@ -74,12 +145,12 @@ protected void page(LuceneIndex index, HttpServletRequest req, HttpServletRespon
74145
protected void html(LuceneIndex index, HttpServletRequest req, HttpServletResponse resp)
75146
throws IOException {
76147
final long t0 = System.currentTimeMillis();
148+
77149
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+
83154
Writer writer = resp.getWriter();
84155
final String content = pars.getString(F, index.content());
85156
final FlucText fluc = index.flucText(content);
@@ -127,6 +198,23 @@ protected void html(LuceneIndex index, HttpServletRequest req, HttpServletRespon
127198

128199
final String q = pars.getString(Q, null);
129200

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+
}
130218
// no query, list docs
131219
if (q == null) {
132220
final int rows = pars.getInt(ROWS, ROWS_RANGE, ROWS_DEFAULT, ROWS);
@@ -159,19 +247,30 @@ protected void html(LuceneIndex index, HttpServletRequest req, HttpServletRespon
159247
}
160248

161249
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+
163252

164253

165254
// sorted?
166255
final boolean sorted = pars.getBoolean(SORTED, false, SORTED);
167256
// relevance
168257
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+
}
169268
ScoreDoc[] hits = index.searcher().search(query, 10).scoreDocs;
170-
269+
// TODO loop and use the spanVisitor to test the best spans
171270
}
172271

173272

174-
SpanWalker walker = new SpanWalker(index.searcher(), query, null, results);
273+
SpanWalker walker = new SpanWalker(index.searcher(), spanQuery, filterQuery, results);
175274
writer
176275
.append("<p class=\"statshits\">")
177276
.append(String.valueOf(walker.hits()))

web/src/main/java/com/github/oeuvres/alix/web/Pars.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ private Pars() {}
2626
public static final int SPANS_DEFAULT = 10;
2727
public static final int[] SPANS_RANGE = {-1, 100};
2828
public static final String START = "start";
29+
public static final String YEAR = "year";
2930
}

0 commit comments

Comments
 (0)