content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   * 
   *      http://www.apache.org/licenses/LICENSE-2.0
   * 
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package mf.org.apache.xerces.xpointer;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  
  import mf.org.apache.xerces.impl.Constants;
  import mf.org.apache.xerces.impl.XMLErrorReporter;
  import mf.org.apache.xerces.util.SymbolTable;
  import mf.org.apache.xerces.util.XMLChar;
  import mf.org.apache.xerces.util.XMLSymbols;
  import mf.org.apache.xerces.xinclude.XIncludeHandler;
  import mf.org.apache.xerces.xinclude.XIncludeNamespaceSupport;
  import mf.org.apache.xerces.xni.Augmentations;
  import mf.org.apache.xerces.xni.QName;
  import mf.org.apache.xerces.xni.XMLAttributes;
  import mf.org.apache.xerces.xni.XMLDocumentHandler;
  import mf.org.apache.xerces.xni.XMLString;
  import mf.org.apache.xerces.xni.XNIException;
  import mf.org.apache.xerces.xni.parser.XMLConfigurationException;
  import mf.org.apache.xerces.xni.parser.XMLErrorHandler;
  
  
  /**
   * <p>
   * This is a pipeline component which extends the XIncludeHandler to perform
   * XPointer specific processing specified in the W3C XPointerFramework and
   * element() Scheme Recommendations.
   * </p>
   * <p/>
   * <p>
   * This component analyzes each event in the pipeline, looking for an element
   * that matches a PointerPart in the parent XInclude element's xpointer attribute
   * value.  If the match succeeds, all children are passed by this component.
   * </p>
   * <p/>
   * <p>
   * See the <a href="http://www.w3.org/TR/xptr-framework//">XPointer Framework Recommendation</a> for
   * more information on the XPointer Framework and ShortHand Pointers.
   * See the <a href="http://www.w3.org/TR/xptr-element/">XPointer element() Scheme Recommendation</a> for
   * more information on the XPointer element() Scheme.
   * </p>
   *
   * @version $Id: XPointerHandler.java 603808 2007-12-13 03:44:48Z mrglavas $
   * @xerces.internal
   */
  public final class XPointerHandler extends XIncludeHandler implements
          XPointerProcessor {
  
      // Supported schemes
      private final String ELEMENT_SCHEME_NAME = "element";
      // Fields
      // An ArrayList of XPointerParts
      protected ArrayList fXPointerParts = null;
      // The current XPointerPart
      protected XPointerPart fXPointerPart = null;
      // Has the fXPointerPart resolved successfully
      protected boolean fFoundMatchingPtrPart = false;
      // The XPointer Error reporter
      protected XMLErrorReporter fXPointerErrorReporter;
      // The XPointer Error Handler
      protected XMLErrorHandler fErrorHandler;
      // XPointerFramework symbol table
      protected SymbolTable fSymbolTable = null;
      // Has the XPointer resolved the subresource
      protected boolean fIsXPointerResolved = false;
  
      // Fixup xml:base and xml:lang attributes
      protected boolean fFixupBase = false;
      protected boolean fFixupLang = false;
  
      // ************************************************************************
      // Constructors
      // ************************************************************************
  
      /**
       *
       */
      public XPointerHandler() {
          super();
  
          fXPointerParts = new ArrayList();
          fSymbolTable = new SymbolTable();
      }
  
      public XPointerHandler(SymbolTable symbolTable,
                             XMLErrorHandler errorHandler, XMLErrorReporter errorReporter) {
          super();
  
          fXPointerParts = new ArrayList();
          fSymbolTable = symbolTable;
          fErrorHandler = errorHandler;
          fXPointerErrorReporter = errorReporter;
          //fErrorReporter = errorReporter; // The XInclude ErrorReporter
      }
  
      @Override
      public void setDocumentHandler(XMLDocumentHandler handler) {
          fDocumentHandler = handler;
      }
  
      // ************************************************************************
      //  Implementation of the XPointerProcessor interface.
      // ************************************************************************
  
      /**
       * Parses the XPointer framework expression and delegates scheme specific parsing.
       *
       * @see mf.org.apache.xerces.xpointer.XPointerProcessor#parseXPointer(java.lang.String)
       */
      @Override
      public void parseXPointer(String xpointer) throws XNIException {
  
          // Initialize
          init();
  
          // tokens
          final Tokens tokens = new Tokens(fSymbolTable);
  
          // scanner
          Scanner scanner = new Scanner(fSymbolTable) {
              @Override
              protected void addToken(Tokens tokens, int token)
                      throws XNIException {
                  if (token == Tokens.XPTRTOKEN_OPEN_PAREN
                          || token == Tokens.XPTRTOKEN_CLOSE_PAREN
                          || token == Tokens.XPTRTOKEN_SCHEMENAME
                          || token == Tokens.XPTRTOKEN_SCHEMEDATA
                          || token == Tokens.XPTRTOKEN_SHORTHAND) {
                      super.addToken(tokens, token);
                      return;
                  }
                  reportError("InvalidXPointerToken", new Object[]{tokens
                          .getTokenString(token)});
              }
          };
  
          // scan the XPointer expression
          int length = xpointer.length();
          boolean success = scanner.scanExpr(fSymbolTable, tokens, xpointer, 0,
                  length);
  
          if (!success)
              reportError("InvalidXPointerExpression", new Object[]{xpointer});
  
          while (tokens.hasMore()) {
              int token = tokens.nextToken();
  
              switch (token) {
                  case Tokens.XPTRTOKEN_SHORTHAND: {
  
                      // The shortHand name
                      token = tokens.nextToken();
                      String shortHandPointerName = tokens.getTokenString(token);
  
                      if (shortHandPointerName == null) {
                          reportError("InvalidXPointerExpression",
                                  new Object[]{xpointer});
                      }
  
                      XPointerPart shortHandPointer = new ShortHandPointer(
                              fSymbolTable);
                      shortHandPointer.setSchemeName(shortHandPointerName);
                      fXPointerParts.add(shortHandPointer);
                      break;
                  }
                  case Tokens.XPTRTOKEN_SCHEMENAME: {
  
                      // Retreive the local name and prefix to form the scheme name
                      token = tokens.nextToken();
                      String prefix = tokens.getTokenString(token);
                      token = tokens.nextToken();
                      String localName = tokens.getTokenString(token);
  
                      String schemeName = prefix + localName;
  
                      // The next character should be an open parenthesis
                      int openParenCount = 0;
                      int closeParenCount = 0;
  
                      token = tokens.nextToken();
                      String openParen = tokens.getTokenString(token);
                      if (openParen != "XPTRTOKEN_OPEN_PAREN") {
  
                          // can not have more than one ShortHand Pointer
                          if (token == Tokens.XPTRTOKEN_SHORTHAND) {
                              reportError("MultipleShortHandPointers",
                                      new Object[]{xpointer});
                          } else {
                              reportError("InvalidXPointerExpression",
                                      new Object[]{xpointer});
                          }
                      }
                      openParenCount++;
  
                      // followed by zero or more ( and  the schemeData
                      String schemeData = null;
                      while (tokens.hasMore()) {
                          token = tokens.nextToken();
                          schemeData = tokens.getTokenString(token);
                          if (schemeData != "XPTRTOKEN_OPEN_PAREN") {
                              break;
                          }
                          openParenCount++;
                      }
                      token = tokens.nextToken();
                      schemeData = tokens.getTokenString(token);
  
                      // followed by the same number of )
                      token = tokens.nextToken();
                      String closeParen = tokens.getTokenString(token);
                      if (closeParen != "XPTRTOKEN_CLOSE_PAREN") {
                          reportError("SchemeDataNotFollowedByCloseParenthesis",
                                  new Object[]{xpointer});
                      }
                      closeParenCount++;
  
                      while (tokens.hasMore()) {
                          if (tokens.getTokenString(tokens.peekToken()) != "XPTRTOKEN_OPEN_PAREN") {
                              break;
                          }
                          closeParenCount++;
                      }
  
                      // check if the number of open parenthesis are equal to the number of close parenthesis
                      if (openParenCount != closeParenCount) {
                          reportError("UnbalancedParenthesisInXPointerExpression",
                                  new Object[]{xpointer,
                                          new Integer(openParenCount),
                                          new Integer(closeParenCount)});
                      }
  
                      // Perform scheme specific parsing of the pointer part
                      if (schemeName.equals(ELEMENT_SCHEME_NAME)) {
                          XPointerPart elementSchemePointer = new ElementSchemePointer(
                                  fSymbolTable, fErrorReporter);
                          elementSchemePointer.setSchemeName(schemeName);
                          elementSchemePointer.setSchemeData(schemeData);
  
                          // If an exception occurs while parsing the element() scheme expression
                          // ignore it and move on to the next pointer part
                          try {
                              elementSchemePointer.parseXPointer(schemeData);
                              fXPointerParts.add(elementSchemePointer);
                          } catch (XNIException e) {
                              // Re-throw the XPointer element() scheme syntax error.
                              throw new XNIException(e);
                          }
  
                      } else {
                          // ????
                          reportWarning("SchemeUnsupported",
                                  new Object[]{schemeName});
                      }
  
                      break;
                  }
                  default:
                      reportError("InvalidXPointerExpression",
                              new Object[]{xpointer});
              }
          }
  
      }
  
      /**
       * @see org.apache.xerces.xpointer.XPointerProcessor#resolveXPointer(org.apache.xerces.xni.QName, org.apache.xerces.xni.XMLAttributes, org.apache.xerces.xni.Augmentations, int event)
       */
      @Override
      public boolean resolveXPointer(QName element, XMLAttributes attributes,
                                     Augmentations augs, int event) throws XNIException {
          boolean resolved = false;
  
          // The result of the first pointer part whose evaluation identifies 
          // one or more subresources is reported by the XPointer processor as the 
          // result of the pointer as a whole, and evaluation stops.
          // In our implementation, typically the first xpointer scheme that 
          // matches an element is the document is considered.
          // If the pointer part resolved then use it, else search for the fragment
          // using next pointer part from lef-right.
          if (!fFoundMatchingPtrPart) {
  
              // for each element, attempt to resolve it against each pointer part 
              // in the XPointer expression until a matching element is found.
              for (int i = 0; i < fXPointerParts.size(); i++) {
  
                  fXPointerPart = (XPointerPart) fXPointerParts.get(i);
  
                  if (fXPointerPart.resolveXPointer(element, attributes, augs,
                          event)) {
                      fFoundMatchingPtrPart = true;
                      resolved = true;
                  }
              }
          } else {
              if (fXPointerPart.resolveXPointer(element, attributes, augs, event)) {
                  resolved = true;
              }
          }
  
          if (!fIsXPointerResolved) {
              fIsXPointerResolved = resolved;
          }
  
          return resolved;
      }
  
      /**
       * Returns true if the Node fragment is resolved.
       *
       * @see mf.org.apache.xerces.xpointer.XPointerProcessor#isFragmentResolved()
       */
      @Override
      public boolean isFragmentResolved() throws XNIException {
          boolean resolved = (fXPointerPart != null) ? fXPointerPart.isFragmentResolved()
                  : false;
  
          if (!fIsXPointerResolved) {
              fIsXPointerResolved = resolved;
          }
  
          return resolved;
      }
  
      /**
       * Returns true if the XPointer expression resolves to a non-element child
       * of the current resource fragment.
       *
       * @see mf.org.apache.xerces.xpointer.XPointerPart#isChildFragmentResolved()
       */
      public boolean isChildFragmentResolved() throws XNIException {
          boolean resolved = (fXPointerPart != null) ? fXPointerPart
                  .isChildFragmentResolved() : false;
          return resolved;
      }
  
      /**
       * Returns true if the XPointer successfully found a sub-resource .
       *
       * @see mf.org.apache.xerces.xpointer.XPointerProcessor#isFragmentResolved()
       */
      @Override
      public boolean isXPointerResolved() throws XNIException {
          return fIsXPointerResolved;
      }
  
      /**
       * Returns the pointer part used to resolve the document fragment.
       *
       * @return String - The pointer part used to resolve the document fragment.
       */
      public XPointerPart getXPointerPart() {
          return fXPointerPart;
      }
  
      /**
       * Reports XPointer Errors
       */
      private void reportError(String key, Object[] arguments)
              throws XNIException {
          /* 
          fXPointerErrorReporter.reportError(
                  XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments,
                  XMLErrorReporter.SEVERITY_ERROR);
          */
          throw new XNIException((fErrorReporter
                  .getMessageFormatter(XPointerMessageFormatter.XPOINTER_DOMAIN))
                  .formatMessage(fErrorReporter.getLocale(), key, arguments));
      }
  
      /**
       * Reports XPointer Warnings
       */
      private void reportWarning(String key, Object[] arguments)
              throws XNIException {
          fXPointerErrorReporter.reportError(
                  XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments,
                  XMLErrorReporter.SEVERITY_WARNING);
      }
  
      /**
       * Initializes error handling objects
       */
      protected void initErrorReporter() {
          if (fXPointerErrorReporter == null) {
              fXPointerErrorReporter = new XMLErrorReporter();
          }
          if (fErrorHandler == null) {
              fErrorHandler = new XPointerErrorHandler();
          }
          /*
           fXPointerErrorReporter.setProperty(Constants.XERCES_PROPERTY_PREFIX
           + Constants.ERROR_HANDLER_PROPERTY, fErrorHandler);
           */
          fXPointerErrorReporter.putMessageFormatter(
                  XPointerMessageFormatter.XPOINTER_DOMAIN,
                  new XPointerMessageFormatter());
      }
  
      /**
       * Initializes the XPointer Processor;
       */
      protected void init() {
          fXPointerParts.clear();
          fXPointerPart = null;
          fFoundMatchingPtrPart = false;
          fIsXPointerResolved = false;
          //fFixupBase = false;
          //fFixupLang = false;
  
          initErrorReporter();
      }
  
      /**
       * Returns an ArrayList of XPointerPart objects
       *
       * @return An ArrayList of XPointerPart objects.
       */
      public ArrayList getPointerParts() {
          return fXPointerParts;
      }
  
      /**
       * If the comment is a child of a matched element, then pass else return.
       *
       * @param text The text in the comment.
       * @param augs Additional information that may include infoset augmentations
       * @throws XNIException Thrown by application to signal an error.
       */
      @Override
      public void comment(XMLString text, Augmentations augs) throws XNIException {
          if (!isChildFragmentResolved()) {
              return;
          }
          super.comment(text, augs);
      }
  
      /**
       * A processing instruction. Processing instructions consist of a
       * target name and, optionally, text data. The data is only meaningful
       * to the application.
       * <p/>
       * Typically, a processing instruction's data will contain a series
       * of pseudo-attributes. These pseudo-attributes follow the form of
       * element attributes but are <strong>not</strong> parsed or presented
       * to the application as anything other than text. The application is
       * responsible for parsing the data.
       *
       * @param target The target.
       * @param data   The data or null if none specified.
       * @param augs   Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void processingInstruction(String target, XMLString data,
                                        Augmentations augs) throws XNIException {
          if (!isChildFragmentResolved()) {
              return;
          }
          super.processingInstruction(target, data, augs);
      }
  
      // ************************************************************************
      //  Overridden XMLDocumentHandler methods
      // ************************************************************************
  
      /**
       * The start of an element.
       *
       * @param element    The name of the element.
       * @param attributes The element attributes.
       * @param augs       Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void startElement(QName element, XMLAttributes attributes,
                               Augmentations augs) throws XNIException {
          if (!resolveXPointer(element, attributes, augs,
                  XPointerPart.EVENT_ELEMENT_START)) {
  
              // xml:base and xml:lang processing
              if (fFixupBase) {
                  processXMLBaseAttributes(attributes);
              }
              if (fFixupLang) {
                  processXMLLangAttributes(attributes);
              }
  
              // set the context invalid if the element till an element from the result infoset is included
              fNamespaceContext.setContextInvalid();
  
              return;
          }
          super.startElement(element, attributes, augs);
      }
  
      /**
       * An empty element.
       *
       * @param element    The name of the element.
       * @param attributes The element attributes.
       * @param augs       Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void emptyElement(QName element, XMLAttributes attributes,
                               Augmentations augs) throws XNIException {
          if (!resolveXPointer(element, attributes, augs,
                  XPointerPart.EVENT_ELEMENT_EMPTY)) {
              // xml:base and xml:lang processing
              if (fFixupBase) {
                  processXMLBaseAttributes(attributes);
              }
              if (fFixupLang) {
                  processXMLLangAttributes(attributes);
              }
              // no need to restore restoreBaseURI() for xml:base and xml:lang processing
  
              // set the context invalid if the element till an element from the result infoset is included
              fNamespaceContext.setContextInvalid();
              return;
          }
          super.emptyElement(element, attributes, augs);
      }
  
      /**
       * Character content.
       *
       * @param text The content.
       * @param augs Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void characters(XMLString text, Augmentations augs)
              throws XNIException {
          if (!isChildFragmentResolved()) {
              return;
          }
          super.characters(text, augs);
      }
  
      /**
       * Ignorable whitespace. For this method to be called, the document
       * source must have some way of determining that the text containing
       * only whitespace characters should be considered ignorable. For
       * example, the validator can determine if a length of whitespace
       * characters in the document are ignorable based on the element
       * content model.
       *
       * @param text The ignorable whitespace.
       * @param augs Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void ignorableWhitespace(XMLString text, Augmentations augs)
              throws XNIException {
          if (!isChildFragmentResolved()) {
              return;
          }
          super.ignorableWhitespace(text, augs);
      }
  
      /**
       * The end of an element.
       *
       * @param element The name of the element.
       * @param augs    Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void endElement(QName element, Augmentations augs)
              throws XNIException {
          if (!resolveXPointer(element, null, augs,
                  XPointerPart.EVENT_ELEMENT_END)) {
  
              // no need to restore restoreBaseURI() for xml:base and xml:lang processing
              return;
          }
          super.endElement(element, augs);
      }
  
      /**
       * The start of a CDATA section.
       *
       * @param augs Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void startCDATA(Augmentations augs) throws XNIException {
          if (!isChildFragmentResolved()) {
              return;
          }
          super.startCDATA(augs);
      }
  
      /**
       * The end of a CDATA section.
       *
       * @param augs Additional information that may include infoset augmentations
       * @throws XNIException Thrown by handler to signal an error.
       */
      @Override
      public void endCDATA(Augmentations augs) throws XNIException {
          if (!isChildFragmentResolved()) {
              return;
          }
          super.endCDATA(augs);
      }
  
      /**
       * <p>
       * Sets the value of a property. This method is called by the component
       * manager any time after reset when a property changes value.
       * </p>
       * <strong>Note:</strong> Components should silently ignore properties
       * that do not affect the operation of the component.
       *
       * @param propertyId The property identifier.
       * @param value      The value of the property.
       * @throws XMLConfigurationException Thrown for configuration error.
       *                                   In general, components should
       *                                   only throw this exception if
       *                                   it is <strong>really</strong>
       *                                   a critical error.
       */
      @Override
      public void setProperty(String propertyId, Object value)
              throws XMLConfigurationException {
  
          // Error reporter
          if (propertyId == Constants.XERCES_PROPERTY_PREFIX
                  + Constants.ERROR_REPORTER_PROPERTY) {
              if (value != null) {
                  fXPointerErrorReporter = (XMLErrorReporter) value;
              } else {
                  fXPointerErrorReporter = null;
              }
          }
  
          // Error handler
          if (propertyId == Constants.XERCES_PROPERTY_PREFIX
                  + Constants.ERROR_HANDLER_PROPERTY) {
              if (value != null) {
                  fErrorHandler = (XMLErrorHandler) value;
              } else {
                  fErrorHandler = null;
              }
          }
  
          // xml:lang
          if (propertyId == Constants.XERCES_FEATURE_PREFIX
                  + Constants.XINCLUDE_FIXUP_LANGUAGE_FEATURE) {
              if (value != null) {
                  fFixupLang = ((Boolean) value).booleanValue();
              } else {
                  fFixupLang = false;
              }
          }
  
          // xml:base
          if (propertyId == Constants.XERCES_FEATURE_PREFIX
                  + Constants.XINCLUDE_FIXUP_BASE_URIS_FEATURE) {
              if (value != null) {
                  fFixupBase = ((Boolean) value).booleanValue();
              } else {
                  fFixupBase = false;
              }
          }
  
          //
          if (propertyId == Constants.XERCES_PROPERTY_PREFIX
                  + Constants.NAMESPACE_CONTEXT_PROPERTY) {
              fNamespaceContext = (XIncludeNamespaceSupport) value;
          }
  
          super.setProperty(propertyId, value);
      }
  
      /**
       * List of XPointer Framework tokens.
       *
       * @xerces.internal
       */
      private final class Tokens {
  
          /**
           * XPointer Framework tokens
           * [1] Pointer     ::= Shorthand | SchemeBased
           * [2] Shorthand   ::= NCName
           * [3] SchemeBased ::= PointerPart (S? PointerPart)*
           * [4] PointerPart ::= SchemeName '(' SchemeData ')'
           * [5] SchemeName  ::= QName
           * [6] SchemeData  ::= EscapedData*
           * [7] EscapedData ::= NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
           * [8] NormalChar  ::= UnicodeChar - [()^]
           * [9] UnicodeChar ::= [#x0-#x10FFFF]
           */
          private static final int XPTRTOKEN_OPEN_PAREN = 0,
                  XPTRTOKEN_CLOSE_PAREN = 1, XPTRTOKEN_SHORTHAND = 2,
                  XPTRTOKEN_SCHEMENAME = 3, XPTRTOKEN_SCHEMEDATA = 4;
          // Token count
          private static final int INITIAL_TOKEN_COUNT = 1 << 8;
          // Token names
          private final String[] fgTokenNames = {"XPTRTOKEN_OPEN_PAREN",
                  "XPTRTOKEN_CLOSE_PAREN", "XPTRTOKEN_SHORTHAND",
                  "XPTRTOKEN_SCHEMENAME", "XPTRTOKEN_SCHEMEDATA"};
          private int[] fTokens = new int[INITIAL_TOKEN_COUNT];
  
          private int fTokenCount = 0;
  
          // Current token position
          private int fCurrentTokenIndex;
  
          private SymbolTable fSymbolTable;
  
          private HashMap fTokenNames = new HashMap();
  
          /**
           * Constructor
           *
           * @param symbolTable SymbolTable
           */
          private Tokens(SymbolTable symbolTable) {
              fSymbolTable = symbolTable;
  
              fTokenNames.put(new Integer(XPTRTOKEN_OPEN_PAREN),
                      "XPTRTOKEN_OPEN_PAREN");
              fTokenNames.put(new Integer(XPTRTOKEN_CLOSE_PAREN),
                      "XPTRTOKEN_CLOSE_PAREN");
              fTokenNames.put(new Integer(XPTRTOKEN_SHORTHAND),
                      "XPTRTOKEN_SHORTHAND");
              fTokenNames.put(new Integer(XPTRTOKEN_SCHEMENAME),
                      "XPTRTOKEN_SCHEMENAME");
              fTokenNames.put(new Integer(XPTRTOKEN_SCHEMEDATA),
                      "XPTRTOKEN_SCHEMEDATA");
          }
  
          /**
           * Returns the token String
           *
           * @param token The index of the token
           * @return String The token string
           */
          private String getTokenString(int token) {
              return (String) fTokenNames.get(new Integer(token));
          }
  
          /**
           * Add the specified string as a token
           *
           * @param token The token string
           */
          private void addToken(String tokenStr) {
              Integer tokenInt = (Integer) fTokenNames.get(tokenStr);
              if (tokenInt == null) {
                  tokenInt = new Integer(fTokenNames.size());
                  fTokenNames.put(tokenInt, tokenStr);
              }
              addToken(tokenInt.intValue());
          }
  
          /**
           * Add the specified int token
           *
           * @param token The int specifying the token
           */
          private void addToken(int token) {
              try {
                  fTokens[fTokenCount] = token;
              } catch (ArrayIndexOutOfBoundsException ex) {
                  int[] oldList = fTokens;
                  fTokens = new int[fTokenCount << 1];
                  System.arraycopy(oldList, 0, fTokens, 0, fTokenCount);
                  fTokens[fTokenCount] = token;
              }
              fTokenCount++;
          }
  
          /**
           * Resets the current position to the head of the token list.
           */
          private void rewind() {
              fCurrentTokenIndex = 0;
          }
  
          /**
           * Returns true if the {@link #getNextToken()} method
           * returns a valid token.
           */
          private boolean hasMore() {
              return fCurrentTokenIndex < fTokenCount;
          }
  
          /**
           * Obtains the token at the current position, then advance
           * the current position by one.
           * <p/>
           * throws If there's no such next token, this method throws
           * <tt>new XNIException("XPointerProcessingError");</tt>.
           */
          private int nextToken() throws XNIException {
              if (fCurrentTokenIndex == fTokenCount) {
                  reportError("XPointerProcessingError", null);
              }
              return fTokens[fCurrentTokenIndex++];
          }
  
          /**
           * Obtains the token at the current position, without advancing
           * the current position.
           * <p/>
           * If there's no such next token, this method throws
           * <tt>new XNIException("XPointerProcessingError");</tt>.
           */
          private int peekToken() throws XNIException {
              if (fCurrentTokenIndex == fTokenCount) {
                  reportError("XPointerProcessingError", null);
              }
              return fTokens[fCurrentTokenIndex];
          }
  
          /**
           * Obtains the token at the current position as a String.
           * <p/>
           * If there's no current token or if the current token
           * is not a string token, this method throws
           * If there's no such next token, this method throws
           * <tt>new XNIException("XPointerProcessingError");</tt>.
           */
          private String nextTokenAsString() throws XNIException {
              String tokenStrint = getTokenString(nextToken());
              if (tokenStrint == null) {
                  reportError("XPointerProcessingError", null);
              }
              return tokenStrint;
          }
      }
  
      // ************************************************************************
      // Overridden XMLComponent methods
      // ************************************************************************
  
      /**
       * The XPointer expression scanner.  Scans the XPointer framework expression.
       *
       * @xerces.internal
       */
      private class Scanner {
  
          /**
           * 7-bit ASCII subset
           * <p/>
           * 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
           * 0,  0,  0,  0,  0,  0,  0,  0,  0, HT, LF,  0,  0, CR,  0,  0,  // 0
           * 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
           * SP,  !,  ",  #,  $,  %,  &,  ',  (,  ),  *,  +,  ,,  -,  .,  /,  // 2
           * 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  :,  ;,  <,  =,  >,  ?,  // 3
           *
           * @, A,  B,  C,  D,  E,  F,  G,  H,  I,  J,  K,  L,  M,  N,  O,  // 4
           * P,  Q,  R,  S,  T,  U,  V,  W,  X,  Y,  Z,  [,  \,  ],  ^,  _,  // 5
           * `,  a,  b,  c,  d,  e,  f,  g,  h,  i,  j,  k,  l,  m,  n,  o,  // 6
           * p,  q,  r,  s,  t,  u,  v,  w,  x,  y,  z,  {,  |,  },  ~, DEL  // 7
           */
          private static final byte CHARTYPE_INVALID = 0, // invalid XML character
                  CHARTYPE_OTHER = 1, // not special - one of "#%&;?\`{}~" or DEL
                  CHARTYPE_WHITESPACE = 2, // one of "\t\n\r " (0x09, 0x0A, 0x0D, 0x20)
                  CHARTYPE_CARRET = 3, // ^
                  CHARTYPE_OPEN_PAREN = 4, // '(' (0x28)
                  CHARTYPE_CLOSE_PAREN = 5, // ')' (0x29)
                  CHARTYPE_MINUS = 6, // '-' (0x2D)
                  CHARTYPE_PERIOD = 7, // '.' (0x2E)
                  CHARTYPE_SLASH = 8, // '/' (0x2F)
                  CHARTYPE_DIGIT = 9, // '0'-'9' (0x30 to 0x39)
                  CHARTYPE_COLON = 10, // ':' (0x3A)
                  CHARTYPE_EQUAL = 11, // '=' (0x3D)
                  CHARTYPE_LETTER = 12, // 'A'-'Z' or 'a'-'z' (0x41 to 0x5A and 0x61 to 0x7A)
                  CHARTYPE_UNDERSCORE = 13, // '_' (0x5F)
                  CHARTYPE_NONASCII = 14; // Non-ASCII Unicode codepoint (>= 0x80)
  
          private final byte[] fASCIICharMap = {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
                  0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                  2, 1, 1, 1, 1, 1, 1, 1, 4, 5, 1, 1, 1, 6, 7, 8, 9, 9, 9, 9, 9,
                  9, 9, 9, 9, 9, 10, 1, 1, 11, 1, 1, 1, 12, 12, 12, 12, 12, 12,
                  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
                  12, 12, 12, 12, 1, 1, 1, 3, 13, 1, 12, 12, 12, 12, 12, 12, 12,
                  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
                  12, 12, 12, 1, 1, 1, 1, 1};
  
          //
          // Data
          //
          /**
           * Symbol table.
           */
          private SymbolTable fSymbolTable;
  
          /**
           * Constructs an XPointer Framework expression scanner.
           *
           * @param symbolTable SymbolTable
           */
          private Scanner(SymbolTable symbolTable) {
              // save pool and tokens
              fSymbolTable = symbolTable;
  
          } // <init>(SymbolTable)
  
          /**
           * Scans the XPointer Expression
           */
          private boolean scanExpr(SymbolTable symbolTable, Tokens tokens,
                                   String data, int currentOffset, int endOffset)
                  throws XNIException {
  
              int ch;
              int openParen = 0;
              int closeParen = 0;
              int nameOffset, dataOffset;
              boolean isQName = false;
              String name = null;
              String prefix = null;
              String schemeData = null;
              StringBuffer schemeDataBuff = new StringBuffer();
  
              while (true) {
  
                  if (currentOffset == endOffset) {
                      break;
                  }
                  ch = data.charAt(currentOffset);
  
                  //
                  while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
                      if (++currentOffset == endOffset) {
                          break;
                      }
                      ch = data.charAt(currentOffset);
                  }
                  if (currentOffset == endOffset) {
                      break;
                  }
  
                  //
                  // [1]    Pointer      ::=    Shorthand | SchemeBased
                  // [2]    Shorthand    ::=    NCName
                  // [3]    SchemeBased  ::=    PointerPart (S? PointerPart)*
                  // [4]    PointerPart  ::=    SchemeName '(' SchemeData ')'
                  // [5]    SchemeName   ::=    QName
                  // [6]    SchemeData   ::=    EscapedData*
                  // [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
                  // [8]    NormalChar   ::=    UnicodeChar - [()^]
                  // [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
                  // [?]    QName        ::=    (NCName ':')? NCName
                  // [?]    NCName       ::=    (Letter | '_') (NCNameChar)*
                  // [?]    NCNameChar   ::=    Letter | Digit | '.' | '-' | '_'  (ascii subset of 'NCNameChar')
                  // [?]    Letter       ::=    [A-Za-z]                              (ascii subset of 'Letter')
                  // [?]    Digit        ::=    [0-9]                                  (ascii subset of 'Digit')
                  //
                  byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
                          : fASCIICharMap[ch];
  
                  switch (chartype) {
  
                      case CHARTYPE_OPEN_PAREN: // '('
                          addToken(tokens, Tokens.XPTRTOKEN_OPEN_PAREN);
                          openParen++;
                          ++currentOffset;
                          break;
  
                      case CHARTYPE_CLOSE_PAREN: // ')'
                          addToken(tokens, Tokens.XPTRTOKEN_CLOSE_PAREN);
                          closeParen++;
                          ++currentOffset;
                          break;
  
                      case CHARTYPE_CARRET:
                      case CHARTYPE_COLON:
                      case CHARTYPE_DIGIT:
                      case CHARTYPE_EQUAL:
                      case CHARTYPE_LETTER:
                      case CHARTYPE_MINUS:
                      case CHARTYPE_NONASCII:
                      case CHARTYPE_OTHER:
                      case CHARTYPE_PERIOD:
                      case CHARTYPE_SLASH:
                      case CHARTYPE_UNDERSCORE:
                      case CHARTYPE_WHITESPACE:
                          // Scanning SchemeName | Shorthand
                          if (openParen == 0) {
                              nameOffset = currentOffset;
                              currentOffset = scanNCName(data, endOffset,
                                      currentOffset);
  
                              if (currentOffset == nameOffset) {
                                  reportError("InvalidShortHandPointer",
                                          new Object[]{data});
                                  return false;
                              }
  
                              if (currentOffset < endOffset) {
                                  ch = data.charAt(currentOffset);
                              } else {
                                  ch = -1;
                              }
  
                              name = symbolTable.addSymbol(data.substring(nameOffset,
                                      currentOffset));
                              prefix = XMLSymbols.EMPTY_STRING;
  
                              // The name is a QName => a SchemeName
                              if (ch == ':') {
                                  if (++currentOffset == endOffset) {
                                      return false;
                                  }
  
                                  ch = data.charAt(currentOffset);
                                  prefix = name;
                                  nameOffset = currentOffset;
                                  currentOffset = scanNCName(data, endOffset,
                                          currentOffset);
  
                                  if (currentOffset == nameOffset) {
                                      return false;
                                  }
  
                                  if (currentOffset < endOffset) {
                                      ch = data.charAt(currentOffset);
                                  } else {
                                      ch = -1;
                                  }
  
                                  isQName = true;
                                  name = symbolTable.addSymbol(data.substring(
                                          nameOffset, currentOffset));
                              }
  
                              // REVISIT:
                              if (currentOffset != endOffset) {
                                  addToken(tokens, Tokens.XPTRTOKEN_SCHEMENAME);
                                  tokens.addToken(prefix);
                                  tokens.addToken(name);
                                  isQName = false;
                              } else if (currentOffset == endOffset) {
                                  // NCName => Shorthand
                                  addToken(tokens, Tokens.XPTRTOKEN_SHORTHAND);
                                  tokens.addToken(name);
                                  isQName = false;
                              }
  
                              // reset open/close paren for the next pointer part
                              closeParen = 0;
  
                              break;
  
                          } else if (openParen > 0 && closeParen == 0 && name != null) {
                              // Scanning SchemeData
                              dataOffset = currentOffset;
                              currentOffset = scanData(data, schemeDataBuff,
                                      endOffset, currentOffset);
  
                              if (currentOffset == dataOffset) {
                                  reportError("InvalidSchemeDataInXPointer",
                                          new Object[]{data});
                                  return false;
                              }
  
                              if (currentOffset < endOffset) {
                                  ch = data.charAt(currentOffset);
                              } else {
                                  ch = -1;
                              }
  
                              schemeData = symbolTable.addSymbol(schemeDataBuff
                                      .toString());
                              addToken(tokens, Tokens.XPTRTOKEN_SCHEMEDATA);
                              tokens.addToken(schemeData);
  
                              // reset open/close paren for the next pointer part
                              openParen = 0;
                              schemeDataBuff.delete(0, schemeDataBuff.length());
  
                          } else {
                              // ex. schemeName()
                              // Should we throw an exception with a more suitable message instead??
                              return false;
                          }
                  }
              } // end while
              return true;
          }
  
          /**
           * Scans a NCName.
           * From Namespaces in XML
           * [5] NCName ::= (Letter | '_') (NCNameChar)*
           * [6] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
           *
           * @param data          A String containing the XPointer expression
           * @param endOffset     The int XPointer expression length
           * @param currentOffset An int representing the current position of the XPointer expression pointer
           */
          private int scanNCName(String data, int endOffset, int currentOffset) {
              int ch = data.charAt(currentOffset);
              if (ch >= 0x80) {
                  if (!XMLChar.isNameStart(ch)) {
                      return currentOffset;
                  }
              } else {
                  byte chartype = fASCIICharMap[ch];
                  if (chartype != CHARTYPE_LETTER
                          && chartype != CHARTYPE_UNDERSCORE) {
                      return currentOffset;
                  }
              }
  
              //while (currentOffset++ < endOffset) {
              while (++currentOffset < endOffset) {
                  ch = data.charAt(currentOffset);
                  if (ch >= 0x80) {
                      if (!XMLChar.isName(ch)) {
                          break;
                      }
                  } else {
                      byte chartype = fASCIICharMap[ch];
                      if (chartype != CHARTYPE_LETTER
                              && chartype != CHARTYPE_DIGIT
                              && chartype != CHARTYPE_PERIOD
                              && chartype != CHARTYPE_MINUS
                              && chartype != CHARTYPE_UNDERSCORE) {
                          break;
                      }
                  }
              }
              return currentOffset;
          }
  
          /**
           * Scans the SchemeData.
           * [6]    SchemeData   ::=    EscapedData*
           * [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
           * [8]    NormalChar   ::=    UnicodeChar - [()^]
           * [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
           */
          private int scanData(String data, StringBuffer schemeData,
                               int endOffset, int currentOffset) {
              while (true) {
  
                  if (currentOffset == endOffset) {
                      break;
                  }
  
                  int ch = data.charAt(currentOffset);
                  byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
                          : fASCIICharMap[ch];
  
                  if (chartype == CHARTYPE_OPEN_PAREN) {
                      schemeData.append(ch);
                      //schemeData.append(Tokens.XPTRTOKEN_OPEN_PAREN);
                      currentOffset = scanData(data, schemeData, endOffset,
                              ++currentOffset);
                      if (currentOffset == endOffset) {
                          return currentOffset;
                      }
  
                      ch = data.charAt(currentOffset);
                      chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
                              : fASCIICharMap[ch];
  
                      if (chartype != CHARTYPE_CLOSE_PAREN) {
                          return endOffset;
                      }
                      schemeData.append((char) ch);
                      ++currentOffset;//
  
                  } else if (chartype == CHARTYPE_CLOSE_PAREN) {
                      return currentOffset;
  
                  } else if (chartype == CHARTYPE_CARRET) {
                      ch = data.charAt(++currentOffset);
                      chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
                              : fASCIICharMap[ch];
  
                      if (chartype != CHARTYPE_CARRET
                              && chartype != CHARTYPE_OPEN_PAREN
                              && chartype != CHARTYPE_CLOSE_PAREN) {
                          break;
                      }
                      schemeData.append((char) ch);
                      ++currentOffset;
  
                  } else {
                      schemeData.append((char) ch);
                      ++currentOffset;//
                  }
              }
  
              return currentOffset;
          }
  
          //
          // Protected methods
          //
  
          /**
           * This method adds the specified token to the token list. By
           * default, this method allows all tokens. However, subclasses
           * of the XPathExprScanner can override this method in order
           * to disallow certain tokens from being used in the scanned
           * XPath expression. This is a convenient way of allowing only
           * a subset of XPath.
           */
          protected void addToken(Tokens tokens, int token) throws XNIException {
              tokens.addToken(token);
          } // addToken(int)
  
      } // class Scanner
  
  }