<commit_before>/*
 * 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 <<EMAIL>>
 */
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);
        }
    }

    /* (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: 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 {

        final Matcher clarkNotation = ptnClarkNotation.matcher(qname);

        if(clarkNotation.matches()) {
            //parse as clark notation
            final String ns = clarkNotation.group(1);
            final String localPart = clarkNotation.group(2);
            return new QName(localPart, ns);
        } else {
            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);
        }<commit_msg>[performance] Checking if a QName is specified in clark notation was too slow (up to 25% performance loss for certain library modules) due to the use of a regular expression. Put a quick check around the more expensive regex.<commit_after>/*
 * 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 <<EMAIL>>
 */
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);
        }
    }

    /* (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: 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);
{'fraction_non_alphanumeric': 0.0920332936979786, 'fraction_numerical': 0.0038049940546967895, 'mean_word_length': 2.3995149555375908, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 16, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '216098', 'n_tokens_mistral': 5855, 'n_tokens_neox': 5666, 'n_words': 1913}