package org.basex.query.util;
import static org.basex.util.Token.*;
import org.basex.data.Data;
import org.basex.data.FTPosData;
import org.basex.data.MemData;
import org.basex.query.QueryContext;
import org.basex.query.item.ANode;
import org.basex.query.item.DBNode;
import org.basex.query.item.NodeType;
import org.basex.query.item.QNm;
import org.basex.query.iter.AxisIter;
import org.basex.query.iter.NodeCache;
import org.basex.util.Atts;
import org.basex.util.list.TokenList;
/**
* Class for building memory-based database nodes.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class DataBuilder {
/** Target data instance. */
private final MemData data;
/** Full-text position data. */
private DataFTBuilder ftbuilder;
/** Index reference of marker tag. */
private int marker;
/**
* Constructor.
* @param md target data
*/
public DataBuilder(final MemData md) {
data = md;
}
/**
* Attaches full-text position data.
* @param tag name of marker tag
* @param pos full-text position data
* @param len length of extract
* @return self reference
*/
public DataBuilder ftpos(final byte[] tag, final FTPosData pos,
final int len) {
ftbuilder = new DataFTBuilder(pos, len);
marker = data.tagindex.index(tag, null, false);
return this;
}
/**
* Fills the data instance with the specified node.
* @param n node
*/
public void build(final ANode n) {
build(new NodeCache(new ANode[] { n }, 1));
}
/**
* Fills the data instance with the specified nodes.
* @param nc node iterator
*/
public void build(final NodeCache nc) {
int pre = 1;
for(ANode n; (n = nc.next()) != null;) pre = addNode(n, pre, 0, null);
}
/**
* Adds a fragment to a database instance.
* Document nodes are ignored.
* @param nd node to be added
* @param pre node position
* @param par node parent
* @return pre value of next node
* @param ndPar parent of node to be added
*/
private int addNode(final ANode nd, final int pre, final int par,
final ANode ndPar) {
switch(nd.nodeType()) {
case DOC: return addDoc(nd, pre);
case ELM: return addElem(nd, pre, par);
case TXT: return pre + addText(nd, pre, par, ndPar);
case ATT: return pre + addAttr(nd, pre, par);
case COM: return pre + addComm(nd, pre, par);
// will always be processing instruction
default: return pre + addPI(nd, pre, par);
}
}
/**
* Adds a document node.
* @param nd node to be added
* @param pre pre reference
* @return number of added nodes
*/
private int addDoc(final ANode nd, final int pre) {
final int ms = data.meta.size;
data.doc(ms, size(nd, false), nd.baseURI());
data.insert(ms);
int p = pre + 1;
final AxisIter ai = nd.children();
for(ANode ch; (ch = ai.next()) != null;) p = addNode(ch, p, pre, null);
return p;
}
/**
* Adds an attribute.
* @param nd node to be added
* @param pre pre reference
* @param par parent reference
* @return number of added nodes
*/
private int addAttr(final ANode nd, final int pre, final int par) {
final int ms = data.meta.size;
final QNm q = nd.qname();
final byte[] uri = q.uri();
int u = 0;
final boolean ne = uri.length != 0;
if(ne) {
if(par == 0) data.nspaces.add(ms, pre - par, q.prefix(), uri);
u = data.nspaces.addURI(uri);
}
final int n = data.atnindex.index(q.string(), null, false);
// attribute namespace flag is only set in main memory instance
data.attr(ms, pre - par, n, nd.string(), u, ne);
data.insert(ms);
return 1;
}
/**
* Adds a text node.
* @param nd node to be added
* @param pre pre reference
* @param par parent reference
* @param ndPar parent node
* @return number of added nodes
*/
private int addText(final ANode nd, final int pre, final int par,
final ANode ndPar) {
// check full-text mode
final int dist = pre - par;
final TokenList tl = ftbuilder != null ? ftbuilder.build(nd) : null;
if(tl == null) return addText(nd.string(), dist);
// adopt namespace from parent
final int u = ndPar != null ? data.nspaces.uri(ndPar.name(), true) : 0;
for(int i = 0; i < tl.size(); i++) {
byte[] text = tl.get(i);
final boolean elem = text == null;
if(elem) {
// open element
data.elem(dist + i, marker, 1, 2, u, false);
data.insert(data.meta.size);
text = tl.get(++i);
}
addText(text, elem ? 1 : dist + i);
}
return tl.size();
}
/**
* Adds a text.
* @param txt text node
* @param dist distance
* @return number of added nodes
*/
private int addText(final byte[] txt, final int dist) {
final int ms = data.meta.size;
data.text(ms, dist, txt, Data.TEXT);
data.insert(ms);
return 1;
}
/**
* Adds a processing instruction.
* @param nd node to be added
* @param pre pre reference
* @param par parent reference
* @return number of added nodes
*/
private int addPI(final ANode nd, final int pre, final int par) {
final int ms = data.meta.size;
final byte[] v = trim(concat(nd.name(), SPACE, nd.string()));
data.text(ms, pre - par, v, Data.PI);
data.insert(ms);
return 1;
}
/**
* Adds a comment.
* @param nd node to be added
* @param pre pre reference
* @param par parent reference
* @return number of added nodes
*/
private int addComm(final ANode nd, final int pre, final int par) {
final int ms = data.meta.size;
data.text(ms, pre - par, nd.string(), Data.COMM);
data.insert(ms);
return 1;
}
/**
* Adds an element node.
* @param nd node to be added
* @param pre pre reference
* @param par parent reference
* @return number of added nodes
*/
private int addElem(final ANode nd, final int pre, final int par) {
final int ms = data.meta.size;
data.nspaces.open();
// add new namespaces
final Atts ns = nd.nsScope();
final boolean ne = ns.size() > 0;
for(int a = 0, as = ns.size(); a < as; a++)
data.nspaces.add(ns.name(a), ns.string(a), ms);
final QNm q = nd.qname();
final byte[] uri = q.uri();
final int u = uri.length != 0 ? data.nspaces.addURI(uri) : 0;
final int tn = data.tagindex.index(q.string(), null, false);
final int s = size(nd, false);
// add element node
data.elem(pre - par, tn, size(nd, true), s, u, ne);
data.insert(ms);
int p = pre + 1;
// add attributes
AxisIter ai = nd.attributes();
for(ANode ch; (ch = ai.next()) != null;) p = addNode(ch, p, pre, nd);
// add children
ai = nd.children();
for(ANode ch; (ch = ai.next()) != null;) p = addNode(ch, p, pre, nd);
data.nspaces.close(ms);
// update size if additional nodes have been added by the descendants
if(s != p - pre) data.size(ms, Data.ELEM, p - pre);
return p;
}
/**
* Determines the number of descendants of a fragment.
* @param n fragment node
* @param a count attribute size of node
* @return number of descendants + 1 or attribute size + 1
*/
private static int size(final ANode n, final boolean a) {
if(n instanceof DBNode) {
final DBNode dbn = (DBNode) n;
final int k = n.kind();
return a ? dbn.data.attSize(dbn.pre, k) : dbn.data.size(dbn.pre, k);
}
int s = 1;
AxisIter ai = n.attributes();
while(ai.next() != null) ++s;
if(!a) {
ai = n.children();
for(ANode i; (i = ai.next()) != null;) s += size(i, a);
}
return s;
}
/**
* Returns a new node without the specified namespace.
* @param node node to be copied
* @param ns namespace to be stripped
* @param ctx query context
* @return new node
*/
public static ANode stripNS(final ANode node, final byte[] ns,
final QueryContext ctx) {
if(node.type != NodeType.ELM) return node;
final MemData md = new MemData(ctx.context.prop);
final DataBuilder db = new DataBuilder(md);
db.build(node);
// flag indicating if namespace should be completely removed
boolean del = true;
// loop through all nodes
for(int pre = 0; pre < md.meta.size; pre++) {
// only check elements and attributes
final int kind = md.kind(pre);
if(kind != Data.ELEM && kind != Data.ATTR) continue;
// check if namespace is referenced
final byte[] uri = md.nspaces.uri(md.uri(pre, kind));
if(uri == null || !eq(uri, ns)) continue;
final byte[] name = md.name(pre, kind);
if(prefix(name).length == 0) {
// no prefix: remove namespace from element
if(kind == Data.ELEM) {
md.update(pre, kind, name, EMPTY);
md.nsFlag(pre, false);
}
} else {
// prefix: retain namespace
del = false;
}
}
if(del) md.nspaces.delete(ns);
return new DBNode(md, 0);
}
}