EQNameUtils.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.core.metapath;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.XMLConstants;
import javax.xml.namespace.QName;

import edu.umd.cs.findbugs.annotations.NonNull;

public final class EQNameUtils {
  private static final Pattern URI_QUALIFIED_NAME = Pattern.compile("^Q\\{([^{}]*)\\}(.+)$");
  private static final Pattern LEXICAL_NAME = Pattern.compile("^(?:([^:]+):)?(.+)$");
  private static final Pattern NCNAME = Pattern.compile(String.format("^(\\p{L}|_)(\\p{L}|\\p{N}|[.\\-_])*$"));

  private EQNameUtils() {
    // disable construction
  }

  /**
   * Parse a name as a qualified name.
   * <p>
   * The name can be:
   * <ul>
   * <li>A URI qualified name of the form <code>Q{URI}name</code>, where the URI
   * represents the namespace</li>
   * <li>A lexical name of the forms <code>prefix:name</code> or
   * <code>name</code>, where the prefix represents the namespace</li>
   * </ul>
   *
   * @param name
   *          the name to parse
   * @param resolver
   *          the prefix resolver to use to determine the namespace for a given
   *          prefix
   * @return the parsed qualified name
   */
  @NonNull
  public static QName parseName(
      @NonNull String name,
      @NonNull IEQNamePrefixResolver resolver) {
    Matcher matcher = URI_QUALIFIED_NAME.matcher(name);
    return matcher.matches()
        ? newUriQualifiedName(matcher)
        : parseLexicalQName(name, resolver);
  }

  /**
   * Parse a URI qualified name.
   * <p>
   * The name is expected to be a URI qualified name of the form
   * <code>{URI}name</code>, where the URI represents the namespace.
   *
   * @param name
   *          the name to parse
   * @return the parsed qualified name
   */
  @NonNull
  public static QName parseUriQualifiedName(@NonNull String name) {
    Matcher matcher = URI_QUALIFIED_NAME.matcher(name);
    if (!matcher.matches()) {
      throw new IllegalArgumentException(
          String.format("The name '%s' is not a valid BracedURILiteral of the form: Q{URI}local-name", name));
    }
    return newUriQualifiedName(matcher);
  }

  @NonNull
  private static QName newUriQualifiedName(@NonNull Matcher matcher) {
    return new QName(matcher.group(1), matcher.group(2));
  }

  /**
   * Parse a lexical name as a qualified name.
   * <p>
   * The name is expected to be a lexical name of the forms
   * <code>prefix:name</code> or <code>name</code>, where the prefix represents
   * the namespace.
   *
   * @param name
   *          the name to parse
   * @param resolver
   *          the prefix resolver to use to determine the namespace for a given
   *          prefix
   * @return the parsed qualified name
   */
  @NonNull
  public static QName parseLexicalQName(
      @NonNull String name,
      @NonNull IEQNamePrefixResolver resolver) {
    Matcher matcher = LEXICAL_NAME.matcher(name);
    if (!matcher.matches()) {
      throw new IllegalArgumentException(
          String.format("The name '%s' is not a valid lexical QName of the form: prefix:local-name or local-name",
              name));
    }
    String prefix = matcher.group(1);

    if (prefix == null) {
      prefix = XMLConstants.DEFAULT_NS_PREFIX;
    }

    String namespace = resolver.resolve(prefix);
    return new QName(namespace, matcher.group(2), prefix);
  }

  /**
   * Determine if the name is a non-colonized name.
   *
   * @param name
   *          the name to test
   * @return {@code true} if the name is not colonized, or {@code false} otherwise
   */
  public static boolean isNcName(@NonNull String name) {
    return NCNAME.matcher(name).matches();
  }

  /**
   * Provides a callback for resolving namespace prefixes.
   */
  @FunctionalInterface
  public interface IEQNamePrefixResolver {
    /**
     * Get the URI string for the provided namespace prefix.
     *
     * @param prefix
     *          the namespace prefix
     * @return the URI string
     */
    @NonNull
    String resolve(@NonNull String prefix);
  }
}