package org.basex.index; import static org.basex.util.Token.*; import java.util.*; import java.util.regex.*; import org.basex.data.*; import org.basex.query.value.item.*; import org.basex.util.*; import org.basex.util.hash.*; /** * Names and namespace uris of elements/attribute to index. * * @author BaseX Team 2005-17, BSD License * @author Christian Gruen */ public final class IndexNames { /** Local names and namespace uris. All names are accepted if the list is empty. */ private final Atts qnames = new Atts(); /** Data reference. */ private final Data data; /** * Constructor. * @param type index type * @param data data reference */ public IndexNames(final IndexType type, final Data data) { this.data = data; final String names = data.meta.names(type); final HashSet<String> inc = toSet(names.trim()); for(final String entry : inc) { // global wildcard: ignore all assignments if(entry.equals("*") || entry.equals("*:*")) { qnames.reset(); return; } final String uri, ln; final Matcher m = QNm.EQNAME.matcher(entry); if(m.find()) { // Q{uri}name, Q{uri}* uri = m.group(1); ln = m.group(2).equals("*") ? null : m.group(2); } else if(entry.startsWith("*:")) { // *:name uri = null; ln = entry.substring(2); } else if(XMLToken.isNCName(token(entry))) { // name uri = ""; ln = entry; } else { // invalid Util.debug("Included name is invalid: %", entry); continue; } qnames.add(ln == null ? null : token(ln), uri == null ? null : token(uri)); } } /** * Checks if the list of names is empty. * @return result of check */ public boolean isEmpty() { return qnames.isEmpty(); } /** * Checks if the name of the addressed database entry is to be indexed. * @param pre pre value * @param text text flag * @return result of check */ public boolean contains(final int pre, final boolean text) { final byte[][] qname = text ? data.qname(data.parent(pre, Data.TEXT), Data.ELEM) : data.qname(pre, Data.ATTR); qname[0] = local(qname[0]); return contains(qname); } /** * Checks if the specified name is an index candidate. * @param qname local name and namespace uri (reference or array entries can be {@code null}) * @return result of check */ public boolean contains(final byte[][] qname) { if(isEmpty()) return true; if(qname != null) { final int ns = qnames.size(); final byte[] ln = qname[0], uri = qname[1]; for(int n = 0; n < ns; n++) { final byte[] iln = qnames.name(n); if(iln != null && (ln == null || !eq(ln, iln))) continue; final byte[] iuri = qnames.value(n); if(iuri != null && (uri == null || !eq(uri, iuri))) continue; return true; } } return false; } /** * Returns a set of all entries of the requested string (separated by commas). * @param names names * @return map */ private static HashSet<String> toSet(final String names) { final HashSet<String> set = new HashSet<>(); final StringBuilder value = new StringBuilder(); final int sl = names.length(); for(int s = 0; s < sl; s++) { final char ch = names.charAt(s); if(ch == ',') { if(s + 1 == sl || names.charAt(s + 1) != ',') { if(value.length() != 0) { set.add(value.toString().trim()); value.setLength(0); } continue; } // literal commas are escaped by a second comma s++; } value.append(ch); } if(value.length() != 0) set.add(value.toString().trim()); return set; } /** * Checks if the index names contain all relevant id or idref attributes. * @param idref idref flag * @return result of check */ public boolean containsIds(final boolean idref) { // no entries: all attributes are indexed if(isEmpty()) return true; // currently no support for documents with namespaces (cannot be resolved from name index) if(data.nspaces.isEmpty()) return false; // find id candidates final TokenSet names = new TokenSet(); final int ns = qnames.size(); for(int n = 0; n < ns; n++) { final byte[] name = qnames.name(n); if(name != null && XMLToken.isId(name, idref)) names.add(name); } // check if database name index consists of other ids for(final byte[] name : data.attrNames) { if(XMLToken.isId(name, idref && !names.contains(name))) return false; } return true; } }