/* * eXist Open Source Native XML Database * Copyright (C) 2001-2014 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ package org.exist.dom; import org.exist.interpreter.Context; import org.exist.storage.ElementValue; import org.exist.util.XMLChar; import org.exist.xquery.Constants; import org.exist.xquery.ErrorCodes; import org.exist.xquery.XPathException; import javax.xml.XMLConstants; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Represents a QName, consisting of a local name, a namespace URI and a prefix. * * @author Wolfgang <wolfgang@exist-db.org> */ public class QName implements Comparable<QName> { private static final String WILDCARD = "*"; private static final char COLON = ':'; public static final QName EMPTY_QNAME = new QName("", XMLConstants.NULL_NS_URI); public static final QName DOCUMENT_QNAME = EMPTY_QNAME; public static final QName TEXT_QNAME = EMPTY_QNAME; public static final QName COMMENT_QNAME = EMPTY_QNAME; public static final QName DOCTYPE_QNAME = EMPTY_QNAME; private final String localPart; private final String namespaceURI; private final String prefix; //TODO : use ElementValue.UNKNOWN and type explicitly ? private final byte nameType; // = ElementValue.ELEMENT; public QName(final String localPart, final String namespaceURI, final String prefix, final byte nameType) { this.localPart = localPart; if(namespaceURI == null) { this.namespaceURI = XMLConstants.NULL_NS_URI; } else { this.namespaceURI = namespaceURI; } this.prefix = prefix; this.nameType = nameType; } /** * Construct a QName. The prefix might be null for the default namespace or if no prefix * has been defined for the QName. The namespace URI should be set to the empty * string, if no namespace URI is defined. * * @param localPart * @param namespaceURI * @param prefix */ public QName(final String localPart, final String namespaceURI, final String prefix) { this(localPart, namespaceURI, prefix, ElementValue.ELEMENT); } public QName(final String localPart, final String namespaceURI, final byte nameType) { this(localPart, namespaceURI, null, nameType); } public QName(final String localPart, final String namespaceURI) { this(localPart, namespaceURI, null); } public QName(final QName other, final byte nameType) { this(other.localPart, other.namespaceURI, other.prefix, nameType); } public QName(final QName other) { this(other.localPart, other.namespaceURI, other.prefix, other.nameType); } public QName(final String name) { this(extractLocalName(name), XMLConstants.NULL_NS_URI, extractPrefix(name)); } public String getLocalPart() { return localPart; } public String getNamespaceURI() { return namespaceURI; } /** * Returns true if the QName defines a non-default namespace * */ public boolean hasNamespace() { return !namespaceURI.equals(XMLConstants.NULL_NS_URI); } public String getPrefix() { return prefix; } public byte getNameType() { return nameType; } public String getStringValue() { if (prefix != null && prefix.length() > 0) { return prefix + COLON + localPart; } return localPart; } /** * (deprecated) : use for debugging purpose only, * use getStringValue() for production */ @Override public String toString() { //TODO : remove this copy of getStringValue() return getStringValue(); //TODO : replace by something like this /* if (prefix != null && prefix.length() > 0) return prefix + COLON + localPart; if (hasNamespace()) { if (prefix != null && prefix.length() > 0) return "{" + namespaceURI + "}" + prefix + COLON + localPart; return "{" + namespaceURI + "}" + localPart; } else return localPart; */ } /** * Compares two QNames by comparing namespace URI * and local names. The prefixes are not relevant. * * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(final QName other) { final int c = namespaceURI.compareTo(other.namespaceURI); return c == Constants.EQUAL ? localPart.compareTo(other.localPart) : c; } /** * Checks two QNames for equality. Two QNames are equal * if their namespace URIs and local names are equal. * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(final Object other) { if(other == this) { return true; } else if(!(other instanceof QName)) { return false; } else { final QName qnOther = (QName)other; return this.namespaceURI.equals(qnOther.namespaceURI) && this.localPart.equals(qnOther.localPart); } } /** * Determines whether two QNames match * similar to {@link #equals(Object)} but * incorporates wildcards on either side * * @param qnOther Another QName to compare against this * @return true if two qnames match */ public boolean matches(final QName qnOther) { if(equals(qnOther)) { return true; } else { if(this == WildcardQName.instance || qnOther == WildcardQName.instance) { return true; } else if((localPart.equals(WILDCARD) || qnOther.localPart.equals(WILDCARD)) && this.namespaceURI.equals(qnOther.namespaceURI)) { return true; } else if((namespaceURI.equals(WILDCARD) || qnOther.namespaceURI.equals(WILDCARD)) && this.localPart.equals(qnOther.localPart)) { return true; } else if((namespaceURI.equals(WILDCARD) && localPart.equals(WILDCARD)) || (qnOther.namespaceURI.equals(WILDCARD) || qnOther.localPart.equals(WILDCARD))) { return true; } else { return false; } } } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return namespaceURI.hashCode() ^ localPart.hashCode(); } public javax.xml.namespace.QName toJavaQName() { return new javax.xml.namespace.QName( namespaceURI, localPart, prefix == null ? XMLConstants.DEFAULT_NS_PREFIX : prefix); } /** * Extract the prefix from a QName string. * * @param qname * @return the prefix, if found * @exception IllegalArgumentException if the qname starts with a leading : */ public static String extractPrefix(final String qname) throws IllegalArgumentException { final int p = qname.indexOf(COLON); if (p == Constants.STRING_NOT_FOUND) { return null; } else if (p == 0) { throw new IllegalArgumentException("Illegal QName: starts with a :"); } else if (Character.isDigit(qname.substring(0,1).charAt(0))) { throw new IllegalArgumentException("Illegal QName: starts with a digit"); } return qname.substring(0, p); } /** * Extract the local name from a QName string. * * @param qname * @exception IllegalArgumentException if the qname starts with a leading : or ends with a : */ public static String extractLocalName(final String qname) throws IllegalArgumentException { final int p = qname.indexOf(COLON); if (p == Constants.STRING_NOT_FOUND) { return qname; } else if (p == 0) { throw new IllegalArgumentException("Illegal QName: starts with a ':'"); } else if (p == qname.length()) { throw new IllegalArgumentException("Illegal QName: ends with a ':'"); } else if (!isQName(qname)) { throw new IllegalArgumentException("Illegal QName: '" + qname + "' not a valid local name."); } return qname.substring(p + 1); } /** * Extract a QName from a namespace and qualified name string * * @param namespaceURI A namespace URI * @param qname A qualified named as a string e.g. 'my:name' or a local name e.g. 'name' * * @return The QName */ public static QName parse(final String namespaceURI, final String qname) { final int p = qname.indexOf(COLON); if (p == Constants.STRING_NOT_FOUND) { return new QName(qname, namespaceURI); } else if(!isQName(qname)) { throw new IllegalArgumentException("Illegal QName: '" + qname + "'"); } else { return new QName(qname.substring(p + 1), namespaceURI, qname.substring(0, p)); } } private final static Pattern ptnClarkNotation = Pattern.compile("\\{([^&{}]*)\\}([^&{}:]+)"); /** * Parses the given string into a QName. The method uses context to look up * a namespace URI for an existing prefix. * * @param context * @param qname The QName may be either in Clark Notation * e.g. `{namespace}local-part` or XDM literal qname form e.g. `prefix:local-part`. * @param defaultNS the default namespace to use if no namespace prefix is present. * @return QName * @exception IllegalArgumentException if no namespace URI is mapped to the prefix */ public static QName parse(final Context context, final String qname, final String defaultNS) throws XPathException { // quick test if qname is in clark notation if (qname.length() > 0 && qname.charAt(0) == '{') { final Matcher clarkNotation = ptnClarkNotation.matcher(qname); // more expensive check if (clarkNotation.matches()) { //parse as clark notation final String ns = clarkNotation.group(1); final String localPart = clarkNotation.group(2); return new QName(localPart, ns); } } final String prefix = extractPrefix(qname); String namespaceURI; if (prefix != null) { namespaceURI = context.getURIForPrefix(prefix); if (namespaceURI == null) { throw new XPathException(ErrorCodes.XPST0081, "No namespace defined for prefix " + prefix); } } else { namespaceURI = defaultNS; } if (namespaceURI == null) { namespaceURI = XMLConstants.NULL_NS_URI; } return new QName(extractLocalName(qname), namespaceURI, prefix); } /** * Parses the given string into a QName. The method uses context to look up * a namespace URI for an optional existing prefix. * * This method uses the default element namespace for qnames without prefix. * * @param context * @param qname The QName may be either in Clark Notation * e.g. `{namespace}local-part` or XDM literal qname form * e.g. `prefix:local-part` or `local-part`. * @exception IllegalArgumentException if no namespace URI is mapped to the prefix */ public static QName parse(final Context context, final String qname) throws XPathException { return parse(context, qname, context.getURIForPrefix(XMLConstants.DEFAULT_NS_PREFIX)); } public final void isValid() throws XPathException { if ((!(this instanceof WildcardLocalPartQName)) && !XMLChar.isValidNCName(localPart)) { throw new XPathException(ErrorCodes.XPTY0004, "Invalid localPart '" + localPart + "' for QName '" + this + "'."); } if (prefix != null && !XMLChar.isValidNCName(prefix)) { throw new XPathException(ErrorCodes.XPTY0004, "Invalid prefix '" + prefix + "' for QName '" + this + "'."); } } public static final boolean isQName(final String name) { final int colon = name.indexOf(COLON); if (colon == Constants.STRING_NOT_FOUND) { return XMLChar.isValidNCName(name); } else if (colon == 0 || colon == name.length() - 1) { return false; } else if (!XMLChar.isValidNCName(name.substring(0, colon))) { return false; } else if (!XMLChar.isValidNCName(name.substring(colon + 1))) { return false; } return true; } public static QName fromJavaQName(final javax.xml.namespace.QName jQn) { return new QName(jQn.getLocalPart(), jQn.getNamespaceURI(), jQn.getPrefix()); } public interface PartialQName{} public static class WildcardQName extends QName implements PartialQName { private final static WildcardQName instance = new WildcardQName(); public static WildcardQName getInstance() { return instance; } private WildcardQName() { super(WILDCARD, WILDCARD, WILDCARD); } } public static class WildcardNamespaceURIQName extends QName implements PartialQName { public WildcardNamespaceURIQName(final String localPart) { super(localPart, WILDCARD); } public WildcardNamespaceURIQName(final String localPart, final byte nameType) { super(localPart, WILDCARD, nameType); } } public static class WildcardLocalPartQName extends QName implements PartialQName { public WildcardLocalPartQName(final String namespaceURI) { super(WILDCARD, namespaceURI); } public WildcardLocalPartQName(final String namespaceURI, final byte nameType) { super(WILDCARD, namespaceURI, nameType); } public WildcardLocalPartQName(final String namespaceURI, final String prefix) { super(WILDCARD, namespaceURI, prefix); } /** * Parses the given prefix into a WildcardLocalPartQName. The method uses context to look up * a namespace URI for an existing prefix. * * @param context * @param prefix The namepspace prefix * @param defaultNS the default namespace to use if no namespace prefix is present. * @return WildcardLocalPartQName * @exception IllegalArgumentException if no namespace URI is mapped to the prefix */ public static WildcardLocalPartQName parseFromPrefix(final Context context, final String prefix, final String defaultNS) throws XPathException { String namespaceURI; if (prefix != null) { namespaceURI = context.getURIForPrefix(prefix); if (namespaceURI == null) { throw new XPathException(ErrorCodes.XPST0081, "No namespace defined for prefix " + prefix); } } else { namespaceURI = defaultNS; } if (namespaceURI == null) { namespaceURI = XMLConstants.NULL_NS_URI; } return new WildcardLocalPartQName(namespaceURI, prefix); } /** * Parses the given prefix into a WildcardLocalPartQName. The method uses context to look up * a namespace URI for an existing prefix. * * @param context * @param prefix The namepspace prefix * @return WildcardLocalPartQName * @exception IllegalArgumentException if no namespace URI is mapped to the prefix */ public static WildcardLocalPartQName parseFromPrefix(final Context context, final String prefix) throws XPathException { return parseFromPrefix(context, prefix, context.getURIForPrefix(XMLConstants.DEFAULT_NS_PREFIX)); } } }