Subversion Repositories ES

Rev

View as "text/plain" | Blame | Last modification | View Log | RSS feed

1
/*
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the "License"). You may not use this file except
 * in compliance with the License.
 *
 * You can obtain a copy of the license at
 * https://jaxp.dev.java.net/CDDLv1.0.html.
 * See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * HEADER in each file and include the License file at
 * https://jaxp.dev.java.net/CDDLv1.0.html
 * If applicable add the following below this CDDL HEADER
 * with the fields enclosed by brackets "[]" replaced with
 * your own identifying information: Portions Copyright
 * [year] [name of copyright owner]
 */


/*
 * $Id: NamespaceContextHelper.java,v 1.2 2006-03-28 20:54:02 ndw Exp $
 * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
 * Portions Copyright 2010 Thomas 'PointedEars' Lahn
 */


package com.nwalsh.namespace;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

import javax.xml.namespace.NamespaceContext;

/*
 * NOTE: Skip XML 1.1 validity tests because we don't want to recompile
 * org.apache.xerces for Android -- PointedEars
 */

// import org.apache.xerces.util.XML11Char;

/**
 * Helper implementation of the javax.xml.namespace.NamespaceContext interface.
 *
 * <p>
 * This class implements the JAXP (1.3+)
 * {@link javax.xml.namespace.NamespaceContext} interface. This is the interface
 * used by the JAXP XPath APIs to establish namespace bindings for XPath
 * expressions.
 * <p>
 *
 * <p>
 * There are two errors (in retrospect) with respect to the namespace context in
 * the XPath API. First, there's no way to construct a new one. Given an XPath
 * you can find out what context it is using, but if you want to construct a new
 * expression, there's no standard class that you can instantiate to build a new
 * context. Second, the {@link javax.xml.namespace.NamespaceContext} interface
 * is obviously (again, in retrospect) missing a method that returns an iterator
 * that will allow you to find <emph>all</emph> the namespace URIs (and/or
 * prefixes, which would be equivalent) in the context.
 * </p>
 *
 * <p>
 * This class addresses the first error by providing an object that you can
 * instantiate that implements the {@link javax.xml.namespace.NamespaceContext}
 * interface. It's not a <emph>standard</emph> class, but it at least saves you
 * the trouble of writing it yourself. (Feel free to move it into your own
 * package, of course.)
 * </p>
 *
 * <p>
 * There's really no way to address the second error. An interface, like
 * {@link javax.xml.namespace.NamespaceContext}, is immutable once released into
 * the wild in the Java platform. (This is a consequence of backwards
 * compatibility rules.) To really address the problem, we'll have to invent a
 * new interface or provide an alternative abstract class that implementations
 * will be required to use, or something. However, as an experiment, this class
 * implements a couple of extra methods that we might wish had been in the
 * interface. These methods are carefully identified as non-standard. Having
 * them here really isn't all that useful because your underlying XPath
 * implementation isn't likely to return instances of this class.
 * </p>
 *
 * <p>
 * There are three ways to instantiate this class:
 * </p>
 *
 * <ol>
 * <li>The no-argument constructor produces an initially empty namespace
 * context.</li>
 * <li>Another constructor takes a prefix and URI and produces a namespace
 * context with that binding.</li>
 * <li>Finally, there's a constructor that takes a hash of namespace/uri pairs
 * and produces a namespace context with those initial bindings.</li>
 * <li>The obvious constructor, one that takes an existing
 * {@link javax.xml.namespace.NamespaceContext} so that you can extend it, isn't
 * there because you can't get the current bindings from that interface; see the
 * aforementioned bug.</li>
 * </ol>
 *
 * <p>
 * After the object has been instantiated, you can call the
 * {@link #add(String,String)} method to add additional bindings to the
 * namespace context. Because I'm not sure how and where the XPath API
 * implementations might save pointers to the context object, I've imposed a
 * number of rules designed to make sure that the context remains coherent:
 * </p>
 *
 * <ul>
 * <li>Namespace bindings can only be added, not removed.</li>
 * <li>Once a prefix is bound, its binding cannot be changed.</li>
 * <li>The XML restrictions on the 'xml' prefix, the 'xmlns' prefix, and their
 * respective namespace URIs are enforced.</li>
 * <li>Namespace prefixes must be valid NCNames (or "" for the default
 * namespace). Note that unprefixed element and attribute names in an XPath
 * expression can <em>never</em> match a name that's in a namespace. In
 * particular, setting the default namespace won't have that effect.</li>
 * </ul>
 *
 * <p>
 * Even with these rules, you can't assume that the context is thread safe.
 * Don't allow it to be changed while someone else is reading it.
 * </p>
 *
 * <p>
 * <b>Other notes:</b>
 * </p>
 *
 * <ul>
 * <li>There's no <code>getNamespaceURIs(String prefix)</code> method because
 * there can be at most one URI bound to any given prefix. Wrapping an interator
 * around the mapping available with {@link #getNamespaceURI(String)} seemed
 * silly.</li>
 * <li>This class relies on {@link org.apache.xerces.util.XML11Char} to test
 * that the prefixes are valid NCNames. Note that this means that they're valid
 * XML 1.1 names. XML 1.1 names are a superset of XML 1.0 names and it didn't
 * seem worth the extra effort that would be required to allow the user to
 * choose XML 1.0 or XML 1.1. You might not think it's worth the effort to check
 * at all. Fair enough.</li>
 * <li>I've used generics here and there to make the JDK 1.5 compiler stop
 * complaining. Just delete them for JDK 1.4 and everything should work fine.</li>
 * </ul>
 *
 * @author <a href="mailto:Norman.Walsh@Sun.COM">Norman Walsh</a>
 * @version $Revision: 1.2 $, $Date: 2006-03-28 20:54:02 $
 * @see <a href="http://jaxp.dev.java.net/">Java API for XML Processing</a>
 * @see <a href="http://www.w3.org/TR/REC-xml-names/#ns-qualnames"> Namespaces
 *      in XML</a>
 * @see <a href="http://www.w3.org/XML/xml-names-19990114-errata"> Namespaces in
 *      XML Errata</a>
 */

public class NamespaceContextHelper implements NamespaceContext
{
  private final Hashtable<String, String> ns = new Hashtable<String, String>();

  /**
   * Creates a new instance of NamespaceContextHelper.
   *
   * <p>
   * Creates an empty namespace context.
   * </p>
   */

  public NamespaceContextHelper()
  {
  }

  /**
   * Creates a new instance of NamespaceContextHelper.
   *
   * <p>
   * Creates a namespace context with the bindings specified in
   * <code>initialNamespaces</code>.
   * </p>
   */

  public NamespaceContextHelper(Hashtable initialNamespaces)
  {
    Enumeration keys = initialNamespaces.keys();
    while (keys.hasMoreElements())
    {
      String prefix = (String) keys.nextElement();
      String uri = (String) initialNamespaces.get(prefix);

      this.add(prefix, uri);
    }
  }

  /**
   * Creates a new instance of NamespaceContextHelper.
   *
   * <p>
   * Creates a namespace context with the specified <code>prefix</code> bound to
   * <code>uri</code>.
   * </p>
   */

  public NamespaceContextHelper(String prefix, String uri)
  {
    this.add(prefix, uri);
  }

  /**
   * Adds a new prefix/uri binding to the namespace context.
   *
   * @throws NullPointerException
   *           if the <code>prefix</code> or <code>uri</code> is
   *           <code>null</code>.
   * @throws IllegalArgumentException
   *           if the caller attempts to change the binding of
   *           <code>prefix</code>, if the caller attempts to bind the prefix "
   *           <code>xml</code>"
   *           or the namespace "
   *           <code>http://www.w3.org/XML/1998/namespace</code>" incorrectly,
   *           if the caller attempts to bind the prefix "<code>xmlns</code>" or
   *           the namespace
   *           "<code>http://www.w3.org/2000/xmlns</code>", or if the
   *           <code>prefix</code> is
   *           not a valid NCName.
   */

  public void add(String prefix, String uri)
  {
    if (prefix == null || uri == null)
    {
      throw new NullPointerException(
        "Null prefix or uri passed to NamespaceContextHelper");
    }

    if (this.ns.containsKey(prefix))
    {
      String curURI = this.ns.get(prefix);
      if (uri.equals(curURI))
      {
        return;
      }
      throw new IllegalArgumentException(
        "Attempt to change binding in NamespaceContextHelper");
    }

    if ("xml".equals(prefix)
      && !"http://www.w3.org/XML/1998/namespace".equals(uri))
    {
      throw new IllegalArgumentException(
        "The prefix 'xml' can only be bound to 'http://www.w3.org/XML/1998/namespace' in NamespaceContextHelper");
    }

    if ("http://www.w3.org/XML/1998/namespace".equals(uri)
      && !"xml".equals(prefix))
    {
      throw new IllegalArgumentException(
        "The namespace 'http://www.w3.org/XML/1998/namespace' can only have the prefix 'xml' in NamespaceContextHelper");
    }

    if ("xmlns".equals(prefix)
      || "http://www.w3.org/2000/xmlns".equals(uri))
    {
      throw new IllegalArgumentException(
        "Neither the prefix 'xmlns' nor the URI 'http://www.w3.org/2000/xmlns' can be bound in NamespaceContextHelper");
    }

    if ("".equals(prefix))
    {
      this.ns.put(prefix, uri);
    }
    else
    {
      /*
       * NOTE: Skip XML 1.1 validity tests because we don't want to recompile
       * org.apache.xerces for Android -- PointedEars
       */

      // if (XML11Char.isXML11ValidNCName (prefix)) {
      this.ns.put(prefix, uri);
      // } else {
      // throw new IllegalArgumentException
      // ("Prefix is not a valid NCName in NamespaceContextHelper");
      // }
    }
  }

  /** Implements the NamespaceContext getNamespaceURI method. */
  public String getNamespaceURI(String prefix)
  {
    return this.ns.get(prefix);
  }

  /** Implements the NamespaceContext getPrefix method. */
  public String getPrefix(String namespaceURI)
  {
    if (this.ns.containsValue(namespaceURI))
    {
      Enumeration<String> keys = this.ns.keys();
      while (keys.hasMoreElements())
      {
        String pfx = keys.nextElement();
        String uri = this.ns.get(pfx);
        if (namespaceURI.equals(uri))
        {
          return pfx;
        }
      }
    }
    return null;
  }

  /**
   * Implements a <emph>NON STANDARD</emph> method for finding all of the
   * prefixes
   * in the namespace context.
   *
   * <p>
   * Returns an iterator over all of the prefixes in the namespace context. Note
   * that multiple prefixes may be bound to the same URI.
   * </p>
   */

  public Iterator getPrefixes()
  {
    return this.getPrefixes(null);
  }

  /** Implements the NamespaceContext getPrefixes method. */
  public Iterator getPrefixes(String namespaceURI)
  {
    return new NSIterator(this.ns, namespaceURI);
  }

  /**
   * Implements a <emph>NON STANDARD</emph> method for finding all of the
   * namespace URIs
   * in the namespace context.
   *
   * <p>
   * Returns an iterator over all of the namespace URIs in the namespace
   * context. Note that each namespace URI is returned exactly once, even if it
   * is bound to several different prefixes.
   * </p>
   */

  public Iterator getNamespaceURIs()
  {
    // Make sure each URI is returned at most once...
    Hashtable<String, String> uriHash = new Hashtable<String, String>();
    Enumeration<String> keys = this.ns.keys();
    while (keys.hasMoreElements())
    {
      String pfx = keys.nextElement();
      String uri = this.ns.get(pfx);
      if (!uriHash.containsKey(uri))
      {
        uriHash.put(uri, pfx);
      }
    }

    return new NSIterator(uriHash, null);
  }

  /** Implements the Iterator interface over namespace bindings. */
  private class NSIterator implements Iterator
  {
    private Enumeration<String> keys;

    public NSIterator(Hashtable<String, String> hash, String value)
    {
      this.keys = hash.keys();
      if (value != null)
      {
        // We have to copy the hash to get only the keys that have the specified
        // value
        Hashtable<String, String> vHash = new Hashtable<String, String>();
        while (this.keys.hasMoreElements())
        {
          String key = this.keys.nextElement();
          String val = hash.get(key);
          if (val.equals(value))
          {
            vHash.put(key, val);
          }
        }
        this.keys = vHash.keys();
      }
    }

    public boolean hasNext()
    {
      return this.keys.hasMoreElements();
    }

    public String next()
    {
      return this.keys.nextElement();
    }

    public void remove()
    {
      throw new UnsupportedOperationException(
        "Cannot remove prefix in NamespaceContextHelper");
    }
  }

}