package org.basex.query.func;
import static org.basex.query.util.Err.*;
import static org.basex.util.Token.*;
import org.basex.data.Data;
import org.basex.data.FTPosData;
import org.basex.data.MemData;
import org.basex.index.IndexToken.IndexType;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Expr;
import org.basex.query.ft.FTIndexAccess;
import org.basex.query.ft.FTWords;
import org.basex.query.item.AtomType;
import org.basex.query.item.DBNodeSeq;
import org.basex.query.item.Dbl;
import org.basex.query.item.Item;
import org.basex.query.item.Int;
import org.basex.query.item.Str;
import org.basex.query.iter.Iter;
import org.basex.query.iter.ValueIter;
import org.basex.query.util.DataBuilder;
import org.basex.query.util.Err;
import org.basex.query.util.IndexContext;
import org.basex.util.InputInfo;
import org.basex.util.XMLToken;
import org.basex.util.ft.FTLexer;
import org.basex.util.ft.FTOpt;
import org.basex.util.list.IntList;
/**
* Full-text functions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class FNFt extends StandardFunc {
/** Marker element. */
private static final byte[] MARK = token("mark");
/**
* Constructor.
* @param ii input info
* @param f function definition
* @param e arguments
*/
public FNFt(final InputInfo ii, final Function f, final Expr... e) {
super(ii, f, e);
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
switch(sig) {
case _FT_COUNT: return count(ctx);
default: return super.item(ctx, ii);
}
}
@Override
public Iter iter(final QueryContext ctx) throws QueryException {
switch(sig) {
case _FT_SEARCH: return search(ctx);
case _FT_SCORE: return score(ctx);
case _FT_MARK: return mark(ctx, false);
case _FT_EXTRACT: return mark(ctx, true);
case _FT_TOKENS: return tokens(ctx);
case _FT_TOKENIZE: return tokenize(ctx);
default: return super.iter(ctx);
}
}
/**
* Performs the count function.
* @param ctx query context
* @return iterator
* @throws QueryException query exception
*/
private Item count(final QueryContext ctx) throws QueryException {
final FTPosData tmp = ctx.ftpos;
ctx.ftpos = new FTPosData();
final Iter ir = ctx.iter(expr[0]);
for(Item it; (it = ir.next()) != null;) checkDBNode(it);
final int s = ctx.ftpos.size();
ctx.ftpos = tmp;
return Int.get(s);
}
/**
* Performs the mark function.
* @param ctx query context
* @param ex extract flag
* @return iterator
* @throws QueryException query exception
*/
private Iter mark(final QueryContext ctx, final boolean ex)
throws QueryException {
byte[] m = MARK;
int l = ex ? 150 : Integer.MAX_VALUE;
if(expr.length > 1) {
// name of the marker element; default is <mark/>
m = checkStr(expr[1], ctx);
if(!XMLToken.isQName(m)) Err.value(input, AtomType.QNM, m);
}
if(expr.length > 2) {
l = (int) checkItr(expr[2], ctx);
}
final byte[] mark = m;
final int len = l;
return new Iter() {
final FTPosData ftd = new FTPosData();
ValueIter vi;
Iter ir;
@Override
public Item next() throws QueryException {
while(true) {
if(vi != null) {
final Item it = vi.next();
if(it != null) return it;
vi = null;
}
final FTPosData tmp = ctx.ftpos;
ctx.ftpos = ftd;
if(ir == null) ir = ctx.iter(expr[0]);
final Item it = ir.next();
if(it != null) {
// copy node to main memory data instance
final MemData md = new MemData(ctx.context.prop);
final DataBuilder db = new DataBuilder(md);
db.ftpos(mark, ctx.ftpos, len).build(checkDBNode(it));
final IntList il = new IntList();
for(int p = 0; p < md.meta.size; p += md.size(p, md.kind(p))) {
il.add(p);
}
vi = DBNodeSeq.get(il, md, false, false).iter();
}
ctx.ftpos = tmp;
if(it == null) return null;
}
}
};
}
/**
* Performs the score function.
* @param ctx query context
* @return iterator
* @throws QueryException query exception
*/
private Iter score(final QueryContext ctx) throws QueryException {
return new Iter() {
final Iter iter = expr[0].iter(ctx);
@Override
public Dbl next() throws QueryException {
final Item item = iter.next();
return item == null ? null : Dbl.get(item.score());
}
};
}
/**
* Performs the search function.
* @param ctx query context
* @return iterator
* @throws QueryException query exception
*/
Iter search(final QueryContext ctx) throws QueryException {
return search(data(0, ctx), checkStr(expr[1], ctx), this, ctx);
}
/**
* Performs an index-based search.
* @param data data reference
* @param str search string
* @param fun calling function
* @param ctx query context
* @return iterator
* @throws QueryException query exception
*/
static Iter search(final Data data, final byte[] str,
final StandardFunc fun, final QueryContext ctx) throws QueryException {
final IndexContext ic = new IndexContext(ctx, data, null, true);
if(!data.meta.ftxtindex) NOINDEX.thrw(fun.input, data.meta.name, fun);
final FTOpt tmp = ctx.ftopt;
ctx.ftopt = new FTOpt().copy(data.meta);
final FTWords words = new FTWords(fun.input, ic.data, Str.get(str), ctx);
ctx.ftopt = tmp;
return new FTIndexAccess(fun.input, words, ic).iter(ctx);
}
/**
* Performs the tokens function.
* @param ctx query context
* @return iterator
* @throws QueryException query exception
*/
private Iter tokens(final QueryContext ctx) throws QueryException {
final Data data = data(0, ctx);
byte[] prefix = expr.length < 2 ? EMPTY : checkStr(expr[1], ctx);
if(prefix.length != 0) {
final FTLexer ftl = new FTLexer(new FTOpt().copy(data.meta));
ftl.init(prefix);
prefix = ftl.nextToken();
}
return FNIndex.entries(data, prefix, IndexType.FULLTEXT, this);
}
/**
* Performs the tokenize function.
* @param ctx query context
* @return iterator
* @throws QueryException query exception
*/
private Iter tokenize(final QueryContext ctx) throws QueryException {
final FTOpt opt = new FTOpt().copy(ctx.ftopt);
final FTLexer ftl = new FTLexer(opt).init(checkStr(expr[0], ctx));
return new Iter() {
@Override
public Str next() {
return ftl.hasNext() ? Str.get(ftl.nextToken()) : null;
}
};
}
@Override
public boolean uses(final Use u) {
// skip evaluation at compile time
return u == Use.CTX && sig == Function._FT_SEARCH || super.uses(u);
}
}