1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Licensed to the Apache Software Foundation (ASF) under one or more
   7  * contributor license agreements.  See the NOTICE file distributed with
   8  * this work for additional information regarding copyright ownership.
   9  * The ASF licenses this file to You under the Apache License, Version 2.0
  10  * (the "License"); you may not use this file except in compliance with
  11  * the License.  You may obtain a copy of the License at
  12  *
  13  *      http://www.apache.org/licenses/LICENSE-2.0
  14  *
  15  * Unless required by applicable law or agreed to in writing, software
  16  * distributed under the License is distributed on an "AS IS" BASIS,
  17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18  * See the License for the specific language governing permissions and
  19  * limitations under the License.
  20  */
  21 
  22 package com.sun.org.apache.xerces.internal.xpointer;
  23 
  24 import java.util.ArrayList;
  25 import java.util.HashMap;
  26 
  27 import com.sun.org.apache.xerces.internal.impl.Constants;
  28 import com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
  29 import com.sun.org.apache.xerces.internal.util.SymbolTable;
  30 import com.sun.org.apache.xerces.internal.util.XMLChar;
  31 import com.sun.org.apache.xerces.internal.util.XMLSymbols;
  32 import com.sun.org.apache.xerces.internal.xinclude.XIncludeHandler;
  33 import com.sun.org.apache.xerces.internal.xinclude.XIncludeNamespaceSupport;
  34 import com.sun.org.apache.xerces.internal.xni.Augmentations;
  35 import com.sun.org.apache.xerces.internal.xni.QName;
  36 import com.sun.org.apache.xerces.internal.xni.XMLAttributes;
  37 import com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
  38 import com.sun.org.apache.xerces.internal.xni.XMLString;
  39 import com.sun.org.apache.xerces.internal.xni.XNIException;
  40 import com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
  41 import com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler;
  42 
  43 /**
  44  * <p>
  45  * This is a pipeline component which extends the XIncludeHandler to perform
  46  * XPointer specific processing specified in the W3C XPointerFramework and
  47  * element() Scheme Recommendations.
  48  * </p>
  49  *
  50  * <p>
  51  * This component analyzes each event in the pipeline, looking for an element
  52  * that matches a PointerPart in the parent XInclude element's xpointer attribute
  53  * value.  If the match succeeds, all children are passed by this component.
  54  * </p>
  55  *
  56  * <p>
  57  * See the <a href="http://www.w3.org/TR/xptr-framework//">XPointer Framework Recommendation</a> for
  58  * more information on the XPointer Framework and ShortHand Pointers.
  59  * See the <a href="http://www.w3.org/TR/xptr-element/">XPointer element() Scheme Recommendation</a> for
  60  * more information on the XPointer element() Scheme.
  61  * </p>
  62  *
  63  * @xerces.internal
  64  *
  65  */
  66 public final class XPointerHandler extends XIncludeHandler implements
  67         XPointerProcessor {
  68 
  69     // Fields
  70     // An ArrayList of XPointerParts
  71     protected ArrayList<XPointerPart> fXPointerParts = null;
  72 
  73     // The current XPointerPart
  74     protected XPointerPart fXPointerPart = null;
  75 
  76     // Has the fXPointerPart resolved successfully
  77     protected boolean fFoundMatchingPtrPart = false;
  78 
  79     // The XPointer Error reporter
  80     protected XMLErrorReporter fXPointerErrorReporter;
  81 
  82     // The XPointer Error Handler
  83     protected XMLErrorHandler fErrorHandler;
  84 
  85     // XPointerFramework symbol table
  86     protected SymbolTable fSymbolTable = null;
  87 
  88     // Supported schemes
  89     private final String ELEMENT_SCHEME_NAME = "element";
  90 
  91    // Has the XPointer resolved the subresource
  92     protected boolean fIsXPointerResolved = false;
  93 
  94     // Fixup xml:base and xml:lang attributes
  95     protected boolean fFixupBase = false;
  96     protected boolean fFixupLang = false;
  97 
  98     // ************************************************************************
  99     // Constructors
 100     // ************************************************************************
 101 
 102     /**
 103      *
 104      */
 105     public XPointerHandler() {
 106         super();
 107 
 108         fXPointerParts = new ArrayList<>();
 109         fSymbolTable = new SymbolTable();
 110     }
 111 
 112     public XPointerHandler(SymbolTable symbolTable,
 113             XMLErrorHandler errorHandler, XMLErrorReporter errorReporter) {
 114         super();
 115 
 116         fXPointerParts = new ArrayList<>();
 117         fSymbolTable = symbolTable;
 118         fErrorHandler = errorHandler;
 119         fXPointerErrorReporter = errorReporter;
 120         //fErrorReporter = errorReporter; // The XInclude ErrorReporter
 121     }
 122 
 123     public void setDocumentHandler(XMLDocumentHandler handler) {
 124         fDocumentHandler = handler;
 125     }
 126     
 127     // ************************************************************************
 128     //  Implementation of the XPointerProcessor interface.
 129     // ************************************************************************
 130 
 131     /**
 132      * Parses the XPointer framework expression and delegates scheme specific parsing.
 133      *
 134      * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#parseXPointer(java.lang.String)
 135      */
 136     public void parseXPointer(String xpointer) throws XNIException {
 137 
 138         // Initialize
 139         init();
 140 
 141         // tokens
 142         final Tokens tokens = new Tokens(fSymbolTable);
 143 
 144         // scanner
 145         Scanner scanner = new Scanner(fSymbolTable) {
 146             protected void addToken(Tokens tokens, int token)
 147                     throws XNIException {
 148                 if (token == Tokens.XPTRTOKEN_OPEN_PAREN
 149                         || token == Tokens.XPTRTOKEN_CLOSE_PAREN
 150                         || token == Tokens.XPTRTOKEN_SCHEMENAME
 151                         || token == Tokens.XPTRTOKEN_SCHEMEDATA
 152                         || token == Tokens.XPTRTOKEN_SHORTHAND) {
 153                     super.addToken(tokens, token);
 154                     return;
 155                 }
 156                 reportError("InvalidXPointerToken", new Object[] { tokens
 157                         .getTokenString(token) });
 158             }
 159         };
 160 
 161         // scan the XPointer expression
 162         int length = xpointer.length();
 163         boolean success = scanner.scanExpr(fSymbolTable, tokens, xpointer, 0,
 164                 length);
 165 
 166         if (!success)
 167             reportError("InvalidXPointerExpression", new Object[] { xpointer });
 168 
 169         while (tokens.hasMore()) {
 170             int token = tokens.nextToken();
 171 
 172             switch (token) {
 173             case Tokens.XPTRTOKEN_SHORTHAND: {
 174 
 175                 // The shortHand name
 176                 token = tokens.nextToken();
 177                 String shortHandPointerName = tokens.getTokenString(token);
 178 
 179                 if (shortHandPointerName == null) {
 180                     reportError("InvalidXPointerExpression",
 181                             new Object[] { xpointer });
 182                 }
 183 
 184                 XPointerPart shortHandPointer = new ShortHandPointer(
 185                         fSymbolTable);
 186                 shortHandPointer.setSchemeName(shortHandPointerName);
 187                 fXPointerParts.add(shortHandPointer);
 188                 break;
 189             }
 190             case Tokens.XPTRTOKEN_SCHEMENAME: {
 191 
 192                 // Retreive the local name and prefix to form the scheme name
 193                 token = tokens.nextToken();
 194                 String prefix = tokens.getTokenString(token);
 195                 token = tokens.nextToken();
 196                 String localName = tokens.getTokenString(token);
 197 
 198                 String schemeName = prefix + localName;
 199 
 200                 // The next character should be an open parenthesis
 201                 int openParenCount = 0;
 202                 int closeParenCount = 0;
 203 
 204                 token = tokens.nextToken();
 205                 String openParen = tokens.getTokenString(token);
 206                 if (openParen != "XPTRTOKEN_OPEN_PAREN") {
 207 
 208                     // can not have more than one ShortHand Pointer
 209                     if (token == Tokens.XPTRTOKEN_SHORTHAND) {
 210                         reportError("MultipleShortHandPointers",
 211                                 new Object[] { xpointer });
 212                     } else {
 213                         reportError("InvalidXPointerExpression",
 214                                 new Object[] { xpointer });
 215                     }
 216                 }
 217                 openParenCount++;
 218 
 219                 // followed by zero or more ( and  the schemeData
 220                 String schemeData = null;
 221                 while (tokens.hasMore()) {
 222                     token = tokens.nextToken();
 223                     schemeData = tokens.getTokenString(token);
 224                     if (schemeData != "XPTRTOKEN_OPEN_PAREN") {
 225                         break;
 226                     }
 227                     openParenCount++;
 228                 }
 229                 token = tokens.nextToken();
 230                 schemeData = tokens.getTokenString(token);
 231 
 232                 // followed by the same number of )
 233                 token = tokens.nextToken();
 234                 String closeParen = tokens.getTokenString(token);
 235                 if (closeParen != "XPTRTOKEN_CLOSE_PAREN") {
 236                     reportError("SchemeDataNotFollowedByCloseParenthesis",
 237                             new Object[] { xpointer });
 238                 }
 239                 closeParenCount++;
 240 
 241                 while (tokens.hasMore()) {
 242                     if (tokens.getTokenString(tokens.peekToken()) != "XPTRTOKEN_OPEN_PAREN") {
 243                         break;
 244                     }
 245                     closeParenCount++;
 246                 }
 247 
 248                 // check if the number of open parenthesis are equal to the number of close parenthesis
 249                 if (openParenCount != closeParenCount) {
 250                     reportError("UnbalancedParenthesisInXPointerExpression",
 251                             new Object[] { xpointer,
 252                                     new Integer(openParenCount),
 253                                     new Integer(closeParenCount) });
 254                 }
 255 
 256                 // Perform scheme specific parsing of the pointer part
 257                 if (schemeName.equals(ELEMENT_SCHEME_NAME)) {
 258                     XPointerPart elementSchemePointer = new ElementSchemePointer(
 259                             fSymbolTable, fErrorReporter);
 260                     elementSchemePointer.setSchemeName(schemeName);
 261                     elementSchemePointer.setSchemeData(schemeData);
 262 
 263                     // If an exception occurs while parsing the element() scheme expression
 264                     // ignore it and move on to the next pointer part
 265                     try {
 266                         elementSchemePointer.parseXPointer(schemeData);
 267                         fXPointerParts.add(elementSchemePointer);
 268                     } catch (XNIException e) {
 269                         // Re-throw the XPointer element() scheme syntax error.
 270                         throw new XNIException (e);
 271                     }
 272 
 273                 } else {
 274                     // ????
 275                     reportWarning("SchemeUnsupported",
 276                             new Object[] { schemeName });
 277                 }
 278 
 279                 break;
 280             }
 281             default:
 282                 reportError("InvalidXPointerExpression",
 283                         new Object[] { xpointer });
 284             }
 285         }
 286 
 287     }
 288 
 289     /**
 290      *
 291      * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#resolveXPointer(com.sun.org.apache.xerces.internal.xni.QName, com.sun.org.apache.xerces.internal.xni.XMLAttributes, com.sun.org.apache.xerces.internal.xni.Augmentations, int event)
 292      */
 293     public boolean resolveXPointer(QName element, XMLAttributes attributes,
 294             Augmentations augs, int event) throws XNIException {
 295         boolean resolved = false;
 296 
 297         // The result of the first pointer part whose evaluation identifies
 298         // one or more subresources is reported by the XPointer processor as the
 299         // result of the pointer as a whole, and evaluation stops.
 300         // In our implementation, typically the first xpointer scheme that
 301         // matches an element is the document is considered.
 302         // If the pointer part resolved then use it, else search for the fragment
 303         // using next pointer part from lef-right.
 304         if (!fFoundMatchingPtrPart) {
 305 
 306             // for each element, attempt to resolve it against each pointer part
 307             // in the XPointer expression until a matching element is found.
 308             for (int i = 0; i < fXPointerParts.size(); i++) {
 309 
 310                 fXPointerPart = fXPointerParts.get(i);
 311 
 312                 if (fXPointerPart.resolveXPointer(element, attributes, augs,
 313                         event)) {
 314                     fFoundMatchingPtrPart = true;
 315                     resolved = true;
 316                 }
 317             }
 318         } else {
 319             if (fXPointerPart.resolveXPointer(element, attributes, augs, event)) {
 320                 resolved = true;
 321             }
 322         }
 323 
 324         if (!fIsXPointerResolved) {
 325             fIsXPointerResolved = resolved;
 326         }
 327 
 328         return resolved;
 329     }
 330 
 331     /**
 332      * Returns true if the Node fragment is resolved.
 333      *
 334      * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#isFragmentResolved()
 335      */
 336     public boolean isFragmentResolved() throws XNIException {
 337         boolean resolved = (fXPointerPart != null) ? fXPointerPart.isFragmentResolved()
 338                 : false;
 339 
 340         if (!fIsXPointerResolved) {
 341             fIsXPointerResolved = resolved;
 342         }
 343 
 344         return resolved;
 345     }
 346 
 347     /**
 348      * Returns true if the XPointer expression resolves to a non-element child
 349      * of the current resource fragment.
 350      *
 351      * @see com.sun.org.apache.xerces.internal.xpointer.XPointerPart#isChildFragmentResolved()
 352      *
 353      */
 354     public boolean isChildFragmentResolved() throws XNIException {
 355         boolean resolved = (fXPointerPart != null) ? fXPointerPart
 356                                 .isChildFragmentResolved() : false;
 357                 return resolved;
 358     }
 359 
 360     /**
 361      * Returns true if the XPointer successfully found a sub-resource .
 362      *
 363      * @see com.sun.org.apache.xerces.internal.xpointer.XPointerProcessor#isFragmentResolved()
 364      */
 365     public boolean isXPointerResolved() throws XNIException {
 366         return fIsXPointerResolved;
 367     }
 368 
 369     /**
 370      * Returns the pointer part used to resolve the document fragment.
 371      *
 372      * @return String - The pointer part used to resolve the document fragment.
 373      */
 374     public XPointerPart getXPointerPart() {
 375         return fXPointerPart;
 376     }
 377 
 378     /**
 379      * Reports XPointer Errors
 380      *
 381      */
 382     private void reportError(String key, Object[] arguments)
 383             throws XNIException {
 384         /*
 385         fXPointerErrorReporter.reportError(
 386                 XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments,
 387                 XMLErrorReporter.SEVERITY_ERROR);
 388         */
 389         throw new XNIException((fErrorReporter
 390                                 .getMessageFormatter(XPointerMessageFormatter.XPOINTER_DOMAIN))
 391                                 .formatMessage(fErrorReporter.getLocale(), key, arguments));
 392     }
 393 
 394     /**
 395      * Reports XPointer Warnings
 396      *
 397      */
 398     private void reportWarning(String key, Object[] arguments)
 399             throws XNIException {
 400         fXPointerErrorReporter.reportError(
 401                 XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments,
 402                 XMLErrorReporter.SEVERITY_WARNING);
 403     }
 404 
 405     /**
 406      * Initializes error handling objects
 407      *
 408      */
 409     protected void initErrorReporter() {
 410         if (fXPointerErrorReporter == null) {
 411             fXPointerErrorReporter = new XMLErrorReporter();
 412         }
 413         if (fErrorHandler == null) {
 414             fErrorHandler = new XPointerErrorHandler();
 415         }
 416         /*
 417          fXPointerErrorReporter.setProperty(Constants.XERCES_PROPERTY_PREFIX
 418          + Constants.ERROR_HANDLER_PROPERTY, fErrorHandler);
 419          */
 420         fXPointerErrorReporter.putMessageFormatter(
 421                 XPointerMessageFormatter.XPOINTER_DOMAIN,
 422                 new XPointerMessageFormatter());
 423     }
 424 
 425     /**
 426      * Initializes the XPointer Processor;
 427      */
 428     protected void init() {
 429         fXPointerParts.clear();
 430         fXPointerPart = null;
 431         fFoundMatchingPtrPart = false;
 432         fIsXPointerResolved = false;
 433         //fFixupBase = false;
 434         //fFixupLang = false;
 435 
 436         initErrorReporter();
 437     }
 438 
 439     /**
 440      * Returns an ArrayList of XPointerPart objects
 441      *
 442      * @return An ArrayList of XPointerPart objects.
 443      */
 444     public ArrayList<XPointerPart> getPointerParts() {
 445         return fXPointerParts;
 446     }
 447 
 448     /**
 449      * List of XPointer Framework tokens.
 450      *
 451      * @xerces.internal
 452      *
 453      */
 454     private final class Tokens {
 455 
 456         /**
 457          * XPointer Framework tokens
 458          * [1] Pointer     ::= Shorthand | SchemeBased
 459          * [2] Shorthand   ::= NCName
 460          * [3] SchemeBased ::= PointerPart (S? PointerPart)*
 461          * [4] PointerPart ::= SchemeName '(' SchemeData ')'
 462          * [5] SchemeName  ::= QName
 463          * [6] SchemeData  ::= EscapedData*
 464          * [7] EscapedData ::= NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
 465          * [8] NormalChar  ::= UnicodeChar - [()^]
 466          * [9] UnicodeChar ::= [#x0-#x10FFFF]
 467          *
 468          */
 469         private static final int XPTRTOKEN_OPEN_PAREN = 0,
 470                 XPTRTOKEN_CLOSE_PAREN = 1, XPTRTOKEN_SHORTHAND = 2,
 471                 XPTRTOKEN_SCHEMENAME = 3, XPTRTOKEN_SCHEMEDATA = 4;
 472 
 473         // Token names
 474         private final String[] fgTokenNames = { "XPTRTOKEN_OPEN_PAREN",
 475                 "XPTRTOKEN_CLOSE_PAREN", "XPTRTOKEN_SHORTHAND",
 476                 "XPTRTOKEN_SCHEMENAME", "XPTRTOKEN_SCHEMEDATA" };
 477 
 478         // Token count
 479         private static final int INITIAL_TOKEN_COUNT = 1 << 8;
 480 
 481         private int[] fTokens = new int[INITIAL_TOKEN_COUNT];
 482 
 483         private int fTokenCount = 0;
 484 
 485         // Current token position
 486         private int fCurrentTokenIndex;
 487 
 488         private SymbolTable fSymbolTable;
 489 
 490         private HashMap<Integer, String> fTokenNames = new HashMap<>();
 491 
 492         /**
 493          * Constructor
 494          *
 495          * @param symbolTable SymbolTable
 496          */
 497         private Tokens(SymbolTable symbolTable) {
 498             fSymbolTable = symbolTable;
 499 
 500             fTokenNames.put(new Integer(XPTRTOKEN_OPEN_PAREN),
 501                     "XPTRTOKEN_OPEN_PAREN");
 502             fTokenNames.put(new Integer(XPTRTOKEN_CLOSE_PAREN),
 503                     "XPTRTOKEN_CLOSE_PAREN");
 504             fTokenNames.put(new Integer(XPTRTOKEN_SHORTHAND),
 505                     "XPTRTOKEN_SHORTHAND");
 506             fTokenNames.put(new Integer(XPTRTOKEN_SCHEMENAME),
 507                     "XPTRTOKEN_SCHEMENAME");
 508             fTokenNames.put(new Integer(XPTRTOKEN_SCHEMEDATA),
 509                     "XPTRTOKEN_SCHEMEDATA");
 510         }
 511 
 512         /**
 513          * Returns the token String
 514          * @param token The index of the token
 515          * @return String The token string
 516          */
 517         private String getTokenString(int token) {
 518             return fTokenNames.get(new Integer(token));
 519         }
 520 
 521         /**
 522          * Add the specified string as a token
 523          *
 524          * @param token The token string
 525          */
 526         private void addToken(String tokenStr) {
 527             if (!fTokenNames.containsValue(tokenStr)) {
 528                 Integer tokenInt = new Integer(fTokenNames.size());
 529                 fTokenNames.put(tokenInt, tokenStr);
 530                 addToken(tokenInt.intValue());
 531             }
 532         }
 533 
 534         /**
 535          * Add the specified int token
 536          *
 537          * @param token The int specifying the token
 538          */
 539         private void addToken(int token) {
 540             try {
 541                 fTokens[fTokenCount] = token;
 542             } catch (ArrayIndexOutOfBoundsException ex) {
 543                 int[] oldList = fTokens;
 544                 fTokens = new int[fTokenCount << 1];
 545                 System.arraycopy(oldList, 0, fTokens, 0, fTokenCount);
 546                 fTokens[fTokenCount] = token;
 547             }
 548             fTokenCount++;
 549         }
 550 
 551         /**
 552          * Resets the current position to the head of the token list.
 553          */
 554         private void rewind() {
 555             fCurrentTokenIndex = 0;
 556         }
 557 
 558         /**
 559          * Returns true if the {@link #getNextToken()} method
 560          * returns a valid token.
 561          */
 562         private boolean hasMore() {
 563             return fCurrentTokenIndex < fTokenCount;
 564         }
 565 
 566         /**
 567          * Obtains the token at the current position, then advance
 568          * the current position by one.
 569          *
 570          * throws If there's no such next token, this method throws
 571          * <tt>new XNIException("XPointerProcessingError");</tt>.
 572          */
 573         private int nextToken() throws XNIException {
 574             if (fCurrentTokenIndex == fTokenCount) {
 575                 reportError("XPointerProcessingError", null);
 576             }
 577             return fTokens[fCurrentTokenIndex++];
 578         }
 579 
 580         /**
 581          * Obtains the token at the current position, without advancing
 582          * the current position.
 583          *
 584          * If there's no such next token, this method throws
 585          * <tt>new XNIException("XPointerProcessingError");</tt>.
 586          */
 587         private int peekToken() throws XNIException {
 588             if (fCurrentTokenIndex == fTokenCount) {
 589                 reportError("XPointerProcessingError", null);
 590             }
 591             return fTokens[fCurrentTokenIndex];
 592         }
 593 
 594         /**
 595          * Obtains the token at the current position as a String.
 596          *
 597          * If there's no current token or if the current token
 598          * is not a string token, this method throws
 599          * If there's no such next token, this method throws
 600          * <tt>new XNIException("XPointerProcessingError");</tt>.
 601          */
 602         private String nextTokenAsString() throws XNIException {
 603             String tokenStrint = getTokenString(nextToken());
 604             if (tokenStrint == null) {
 605                 reportError("XPointerProcessingError", null);
 606             }
 607             return tokenStrint;
 608         }
 609     }
 610 
 611     /**
 612      *
 613      * The XPointer expression scanner.  Scans the XPointer framework expression.
 614      *
 615      * @xerces.internal
 616      *
 617      */
 618     private class Scanner {
 619 
 620         /**
 621          * 7-bit ASCII subset
 622          *
 623          *  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
 624          *  0,  0,  0,  0,  0,  0,  0,  0,  0, HT, LF,  0,  0, CR,  0,  0,  // 0
 625          *  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
 626          * SP,  !,  ",  #,  $,  %,  &,  ',  (,  ),  *,  +,  ,,  -,  .,  /,  // 2
 627          *  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  :,  ;,  <,  =,  >,  ?,  // 3
 628          *  @,  A,  B,  C,  D,  E,  F,  G,  H,  I,  J,  K,  L,  M,  N,  O,  // 4
 629          *  P,  Q,  R,  S,  T,  U,  V,  W,  X,  Y,  Z,  [,  \,  ],  ^,  _,  // 5
 630          *  `,  a,  b,  c,  d,  e,  f,  g,  h,  i,  j,  k,  l,  m,  n,  o,  // 6
 631          *  p,  q,  r,  s,  t,  u,  v,  w,  x,  y,  z,  {,  |,  },  ~, DEL  // 7
 632          */
 633         private static final byte CHARTYPE_INVALID = 0, // invalid XML character
 634                 CHARTYPE_OTHER = 1, // not special - one of "#%&;?\`{}~" or DEL
 635                 CHARTYPE_WHITESPACE = 2, // one of "\t\n\r " (0x09, 0x0A, 0x0D, 0x20)
 636                 CHARTYPE_CARRET = 3, // ^
 637                 CHARTYPE_OPEN_PAREN = 4, // '(' (0x28)
 638                 CHARTYPE_CLOSE_PAREN = 5, // ')' (0x29)
 639                 CHARTYPE_MINUS = 6, // '-' (0x2D)
 640                 CHARTYPE_PERIOD = 7, // '.' (0x2E)
 641                 CHARTYPE_SLASH = 8, // '/' (0x2F)
 642                 CHARTYPE_DIGIT = 9, // '0'-'9' (0x30 to 0x39)
 643                 CHARTYPE_COLON = 10, // ':' (0x3A)
 644                 CHARTYPE_EQUAL = 11, // '=' (0x3D)
 645                 CHARTYPE_LETTER = 12, // 'A'-'Z' or 'a'-'z' (0x41 to 0x5A and 0x61 to 0x7A)
 646                 CHARTYPE_UNDERSCORE = 13, // '_' (0x5F)
 647                 CHARTYPE_NONASCII = 14; // Non-ASCII Unicode codepoint (>= 0x80)
 648 
 649         private final byte[] fASCIICharMap = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
 650                 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 651                 2, 1, 1, 1, 1, 1, 1, 1, 4, 5, 1, 1, 1, 6, 7, 8, 9, 9, 9, 9, 9,
 652                 9, 9, 9, 9, 9, 10, 1, 1, 11, 1, 1, 1, 12, 12, 12, 12, 12, 12,
 653                 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
 654                 12, 12, 12, 12, 1, 1, 1, 3, 13, 1, 12, 12, 12, 12, 12, 12, 12,
 655                 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
 656                 12, 12, 12, 1, 1, 1, 1, 1 };
 657 
 658         //
 659         // Data
 660         //
 661         /** Symbol table. */
 662         private SymbolTable fSymbolTable;
 663 
 664         /**
 665          * Constructs an XPointer Framework expression scanner.
 666          *
 667          * @param symbolTable SymbolTable
 668          */
 669         private Scanner(SymbolTable symbolTable) {
 670             // save pool and tokens
 671             fSymbolTable = symbolTable;
 672 
 673         } // <init>(SymbolTable)
 674 
 675         /**
 676          * Scans the XPointer Expression
 677          *
 678          */
 679         private boolean scanExpr(SymbolTable symbolTable, Tokens tokens,
 680                 String data, int currentOffset, int endOffset)
 681                 throws XNIException {
 682 
 683             int ch;
 684             int openParen = 0;
 685             int closeParen = 0;
 686             int nameOffset, dataOffset;
 687             boolean isQName = false;
 688             String name = null;
 689             String prefix = null;
 690             String schemeData = null;
 691             StringBuffer schemeDataBuff = new StringBuffer();
 692 
 693             while (true) {
 694 
 695                 if (currentOffset == endOffset) {
 696                     break;
 697                 }
 698                 ch = data.charAt(currentOffset);
 699 
 700                 //
 701                 while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
 702                     if (++currentOffset == endOffset) {
 703                         break;
 704                     }
 705                     ch = data.charAt(currentOffset);
 706                 }
 707                 if (currentOffset == endOffset) {
 708                     break;
 709                 }
 710 
 711                 //
 712                 // [1]    Pointer      ::=    Shorthand | SchemeBased
 713                 // [2]    Shorthand    ::=    NCName
 714                 // [3]    SchemeBased  ::=    PointerPart (S? PointerPart)*
 715                 // [4]    PointerPart  ::=    SchemeName '(' SchemeData ')'
 716                 // [5]    SchemeName   ::=    QName
 717                 // [6]    SchemeData   ::=    EscapedData*
 718                 // [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
 719                 // [8]    NormalChar   ::=    UnicodeChar - [()^]
 720                 // [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
 721                 // [?]    QName        ::=    (NCName ':')? NCName
 722                 // [?]    NCName       ::=    (Letter | '_') (NCNameChar)*
 723                 // [?]    NCNameChar   ::=    Letter | Digit | '.' | '-' | '_'  (ascii subset of 'NCNameChar')
 724                 // [?]    Letter       ::=    [A-Za-z]                              (ascii subset of 'Letter')
 725                 // [?]    Digit        ::=    [0-9]                                  (ascii subset of 'Digit')
 726                 //
 727                 byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
 728                         : fASCIICharMap[ch];
 729 
 730                 switch (chartype) {
 731 
 732                 case CHARTYPE_OPEN_PAREN: // '('
 733                     addToken(tokens, Tokens.XPTRTOKEN_OPEN_PAREN);
 734                     openParen++;
 735                     ++currentOffset;
 736                     break;
 737 
 738                 case CHARTYPE_CLOSE_PAREN: // ')'
 739                     addToken(tokens, Tokens.XPTRTOKEN_CLOSE_PAREN);
 740                     closeParen++;
 741                     ++currentOffset;
 742                     break;
 743 
 744                 case CHARTYPE_CARRET:
 745                 case CHARTYPE_COLON:
 746                 case CHARTYPE_DIGIT:
 747                 case CHARTYPE_EQUAL:
 748                 case CHARTYPE_LETTER:
 749                 case CHARTYPE_MINUS:
 750                 case CHARTYPE_NONASCII:
 751                 case CHARTYPE_OTHER:
 752                 case CHARTYPE_PERIOD:
 753                 case CHARTYPE_SLASH:
 754                 case CHARTYPE_UNDERSCORE:
 755                 case CHARTYPE_WHITESPACE:
 756                     // Scanning SchemeName | Shorthand
 757                     if (openParen == 0) {
 758                         nameOffset = currentOffset;
 759                         currentOffset = scanNCName(data, endOffset,
 760                                 currentOffset);
 761 
 762                         if (currentOffset == nameOffset) {
 763                             reportError("InvalidShortHandPointer",
 764                                     new Object[] { data });
 765                             return false;
 766                         }
 767 
 768                         if (currentOffset < endOffset) {
 769                             ch = data.charAt(currentOffset);
 770                         } else {
 771                             ch = -1;
 772                         }
 773 
 774                         name = symbolTable.addSymbol(data.substring(nameOffset,
 775                                 currentOffset));
 776                         prefix = XMLSymbols.EMPTY_STRING;
 777 
 778                         // The name is a QName => a SchemeName
 779                         if (ch == ':') {
 780                             if (++currentOffset == endOffset) {
 781                                 return false;
 782                             }
 783 
 784                             ch = data.charAt(currentOffset);
 785                             prefix = name;
 786                             nameOffset = currentOffset;
 787                             currentOffset = scanNCName(data, endOffset,
 788                                     currentOffset);
 789 
 790                             if (currentOffset == nameOffset) {
 791                                 return false;
 792                             }
 793 
 794                             if (currentOffset < endOffset) {
 795                                 ch = data.charAt(currentOffset);
 796                             } else {
 797                                 ch = -1;
 798                             }
 799 
 800                             isQName = true;
 801                             name = symbolTable.addSymbol(data.substring(
 802                                     nameOffset, currentOffset));
 803                         }
 804 
 805                         // REVISIT:
 806                         if (currentOffset != endOffset) {
 807                             addToken(tokens, Tokens.XPTRTOKEN_SCHEMENAME);
 808                             tokens.addToken(prefix);
 809                             tokens.addToken(name);
 810                             isQName = false;
 811                         } else if (currentOffset == endOffset) {
 812                             // NCName => Shorthand
 813                             addToken(tokens, Tokens.XPTRTOKEN_SHORTHAND);
 814                             tokens.addToken(name);
 815                             isQName = false;
 816                         }
 817 
 818                         // reset open/close paren for the next pointer part
 819                         closeParen = 0;
 820 
 821                         break;
 822 
 823                     } else if (openParen > 0 && closeParen == 0 && name != null) {
 824                         // Scanning SchemeData
 825                         dataOffset = currentOffset;
 826                         currentOffset = scanData(data, schemeDataBuff,
 827                                 endOffset, currentOffset);
 828 
 829                         if (currentOffset == dataOffset) {
 830                             reportError("InvalidSchemeDataInXPointer",
 831                                     new Object[] { data });
 832                             return false;
 833                         }
 834 
 835                         if (currentOffset < endOffset) {
 836                             ch = data.charAt(currentOffset);
 837                         } else {
 838                             ch = -1;
 839                         }
 840 
 841                         schemeData = symbolTable.addSymbol(schemeDataBuff
 842                                 .toString());
 843                         addToken(tokens, Tokens.XPTRTOKEN_SCHEMEDATA);
 844                         tokens.addToken(schemeData);
 845 
 846                         // reset open/close paren for the next pointer part
 847                         openParen = 0;
 848                         schemeDataBuff.delete(0, schemeDataBuff.length());
 849 
 850                     } else {
 851                         // ex. schemeName()
 852                         // Should we throw an exception with a more suitable message instead??
 853                         return false;
 854                     }
 855                 }
 856             } // end while
 857             return true;
 858         }
 859 
 860         /**
 861          * Scans a NCName.
 862          * From Namespaces in XML
 863          * [5] NCName ::= (Letter | '_') (NCNameChar)*
 864          * [6] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
 865          *
 866          * @param data A String containing the XPointer expression
 867          * @param endOffset The int XPointer expression length
 868          * @param currentOffset An int representing the current position of the XPointer expression pointer
 869          */
 870         private int scanNCName(String data, int endOffset, int currentOffset) {
 871             int ch = data.charAt(currentOffset);
 872             if (ch >= 0x80) {
 873                 if (!XMLChar.isNameStart(ch)) {
 874                     return currentOffset;
 875                 }
 876             } else {
 877                 byte chartype = fASCIICharMap[ch];
 878                 if (chartype != CHARTYPE_LETTER
 879                         && chartype != CHARTYPE_UNDERSCORE) {
 880                     return currentOffset;
 881                 }
 882             }
 883 
 884             //while (currentOffset++ < endOffset) {
 885             while (++currentOffset < endOffset) {
 886                 ch = data.charAt(currentOffset);
 887                 if (ch >= 0x80) {
 888                     if (!XMLChar.isName(ch)) {
 889                         break;
 890                     }
 891                 } else {
 892                     byte chartype = fASCIICharMap[ch];
 893                     if (chartype != CHARTYPE_LETTER
 894                             && chartype != CHARTYPE_DIGIT
 895                             && chartype != CHARTYPE_PERIOD
 896                             && chartype != CHARTYPE_MINUS
 897                             && chartype != CHARTYPE_UNDERSCORE) {
 898                         break;
 899                     }
 900                 }
 901             }
 902             return currentOffset;
 903         }
 904 
 905         /**
 906          * Scans the SchemeData.
 907          * [6]    SchemeData   ::=    EscapedData*
 908          * [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
 909          * [8]    NormalChar   ::=    UnicodeChar - [()^]
 910          * [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
 911          *
 912          */
 913         private int scanData(String data, StringBuffer schemeData,
 914                 int endOffset, int currentOffset) {
 915             while (true) {
 916 
 917                 if (currentOffset == endOffset) {
 918                     break;
 919                 }
 920 
 921                 int ch = data.charAt(currentOffset);
 922                 byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
 923                         : fASCIICharMap[ch];
 924 
 925                 if (chartype == CHARTYPE_OPEN_PAREN) {
 926                     schemeData.append(ch);
 927                     //schemeData.append(Tokens.XPTRTOKEN_OPEN_PAREN);
 928                     currentOffset = scanData(data, schemeData, endOffset,
 929                             ++currentOffset);
 930                     if (currentOffset == endOffset) {
 931                         return currentOffset;
 932                     }
 933 
 934                     ch = data.charAt(currentOffset);
 935                     chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
 936                             : fASCIICharMap[ch];
 937 
 938                     if (chartype != CHARTYPE_CLOSE_PAREN) {
 939                         return endOffset;
 940                     }
 941                     schemeData.append((char) ch);
 942                     ++currentOffset;//
 943 
 944                 } else if (chartype == CHARTYPE_CLOSE_PAREN) {
 945                     return currentOffset;
 946 
 947                 } else  if (chartype == CHARTYPE_CARRET) {
 948                     ch = data.charAt(++currentOffset);
 949                     chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
 950                             : fASCIICharMap[ch];
 951 
 952                     if (chartype != CHARTYPE_CARRET
 953                             && chartype != CHARTYPE_OPEN_PAREN
 954                             && chartype != CHARTYPE_CLOSE_PAREN) {
 955                         break;
 956                     }
 957                     schemeData.append((char) ch);
 958                     ++currentOffset;
 959 
 960                 } else {
 961                     schemeData.append((char) ch);
 962                     ++currentOffset;//
 963                 }
 964             }
 965 
 966             return currentOffset;
 967         }
 968 
 969         //
 970         // Protected methods
 971         //
 972 
 973         /**
 974          * This method adds the specified token to the token list. By
 975          * default, this method allows all tokens. However, subclasses
 976          * of the XPathExprScanner can override this method in order
 977          * to disallow certain tokens from being used in the scanned
 978          * XPath expression. This is a convenient way of allowing only
 979          * a subset of XPath.
 980          */
 981         protected void addToken(Tokens tokens, int token) throws XNIException {
 982             tokens.addToken(token);
 983         } // addToken(int)
 984 
 985     } // class Scanner
 986 
 987     // ************************************************************************
 988     //  Overridden XMLDocumentHandler methods
 989     // ************************************************************************
 990     /**
 991      * If the comment is a child of a matched element, then pass else return.
 992      *
 993      * @param text   The text in the comment.
 994      * @param augs   Additional information that may include infoset augmentations
 995      *
 996      * @exception XNIException
 997      *                   Thrown by application to signal an error.
 998      */
 999     public void comment(XMLString text, Augmentations augs) throws XNIException {
1000         if (!isChildFragmentResolved()) {
1001             return;
1002         }
1003         super.comment(text, augs);
1004     }
1005 
1006     /**
1007      * A processing instruction. Processing instructions consist of a
1008      * target name and, optionally, text data. The data is only meaningful
1009      * to the application.
1010      * <p>
1011      * Typically, a processing instruction's data will contain a series
1012      * of pseudo-attributes. These pseudo-attributes follow the form of
1013      * element attributes but are <strong>not</strong> parsed or presented
1014      * to the application as anything other than text. The application is
1015      * responsible for parsing the data.
1016      *
1017      * @param target The target.
1018      * @param data   The data or null if none specified.
1019      * @param augs   Additional information that may include infoset augmentations
1020      *
1021      * @exception XNIException
1022      *                   Thrown by handler to signal an error.
1023      */
1024     public void processingInstruction(String target, XMLString data,
1025             Augmentations augs) throws XNIException {
1026         if (!isChildFragmentResolved()) {
1027             return;
1028         }
1029         super.processingInstruction(target, data, augs);
1030     }
1031 
1032     /**
1033      * The start of an element.
1034      *
1035      * @param element    The name of the element.
1036      * @param attributes The element attributes.
1037      * @param augs       Additional information that may include infoset augmentations
1038      *
1039      * @exception XNIException
1040      *                   Thrown by handler to signal an error.
1041      */
1042     public void startElement(QName element, XMLAttributes attributes,
1043             Augmentations augs) throws XNIException {
1044         if (!resolveXPointer(element, attributes, augs,
1045                 XPointerPart.EVENT_ELEMENT_START)) {
1046 
1047             // xml:base and xml:lang processing
1048                 if (fFixupBase) {
1049                 processXMLBaseAttributes(attributes);
1050                 }
1051             if (fFixupLang) {
1052                 processXMLLangAttributes(attributes);
1053             }
1054 
1055             // set the context invalid if the element till an element from the result infoset is included
1056             fNamespaceContext.setContextInvalid();
1057 
1058             return;
1059         }
1060         super.startElement(element, attributes, augs);
1061     }
1062 
1063     /**
1064      * An empty element.
1065      *
1066      * @param element    The name of the element.
1067      * @param attributes The element attributes.
1068      * @param augs       Additional information that may include infoset augmentations
1069      *
1070      * @exception XNIException
1071      *                   Thrown by handler to signal an error.
1072      */
1073     public void emptyElement(QName element, XMLAttributes attributes,
1074             Augmentations augs) throws XNIException {
1075         if (!resolveXPointer(element, attributes, augs,
1076                 XPointerPart.EVENT_ELEMENT_EMPTY)) {
1077             // xml:base and xml:lang processing
1078                 if (fFixupBase) {
1079                 processXMLBaseAttributes(attributes);
1080                 }
1081             if (fFixupLang) {
1082                 processXMLLangAttributes(attributes);
1083             }
1084             // no need to restore restoreBaseURI() for xml:base and xml:lang processing
1085 
1086             // set the context invalid if the element till an element from the result infoset is included
1087             fNamespaceContext.setContextInvalid();
1088             return;
1089         }
1090         super.emptyElement(element, attributes, augs);
1091     }
1092 
1093     /**
1094      * Character content.
1095      *
1096      * @param text   The content.
1097      * @param augs   Additional information that may include infoset augmentations
1098      *
1099      * @exception XNIException
1100      *                   Thrown by handler to signal an error.
1101      */
1102     public void characters(XMLString text, Augmentations augs)
1103             throws XNIException {
1104         if (!isChildFragmentResolved()) {
1105             return;
1106         }
1107         super.characters(text, augs);
1108     }
1109 
1110     /**
1111      * Ignorable whitespace. For this method to be called, the document
1112      * source must have some way of determining that the text containing
1113      * only whitespace characters should be considered ignorable. For
1114      * example, the validator can determine if a length of whitespace
1115      * characters in the document are ignorable based on the element
1116      * content model.
1117      *
1118      * @param text   The ignorable whitespace.
1119      * @param augs   Additional information that may include infoset augmentations
1120      *
1121      * @exception XNIException
1122      *                   Thrown by handler to signal an error.
1123      */
1124     public void ignorableWhitespace(XMLString text, Augmentations augs)
1125             throws XNIException {
1126         if (!isChildFragmentResolved()) {
1127             return;
1128         }
1129         super.ignorableWhitespace(text, augs);
1130     }
1131 
1132     /**
1133      * The end of an element.
1134      *
1135      * @param element The name of the element.
1136      * @param augs    Additional information that may include infoset augmentations
1137      *
1138      * @exception XNIException
1139      *                   Thrown by handler to signal an error.
1140      */
1141     public void endElement(QName element, Augmentations augs)
1142             throws XNIException {
1143         if (!resolveXPointer(element, null, augs,
1144                 XPointerPart.EVENT_ELEMENT_END)) {
1145 
1146             // no need to restore restoreBaseURI() for xml:base and xml:lang processing
1147             return;
1148         }
1149         super.endElement(element, augs);
1150     }
1151 
1152     /**
1153      * The start of a CDATA section.
1154      *
1155      * @param augs   Additional information that may include infoset augmentations
1156      *
1157      * @exception XNIException
1158      *                   Thrown by handler to signal an error.
1159      */
1160     public void startCDATA(Augmentations augs) throws XNIException {
1161         if (!isChildFragmentResolved()) {
1162             return;
1163         }
1164         super.startCDATA(augs);
1165     }
1166 
1167     /**
1168      * The end of a CDATA section.
1169      *
1170      * @param augs   Additional information that may include infoset augmentations
1171      *
1172      * @exception XNIException
1173      *                   Thrown by handler to signal an error.
1174      */
1175     public void endCDATA(Augmentations augs) throws XNIException {
1176         if (!isChildFragmentResolved()) {
1177             return;
1178         }
1179         super.endCDATA(augs);
1180     }
1181 
1182     // ************************************************************************
1183     // Overridden XMLComponent methods
1184     // ************************************************************************
1185     /**
1186      * <p>
1187      * Sets the value of a property. This method is called by the component
1188      * manager any time after reset when a property changes value.
1189      * </p>
1190      * <strong>Note:</strong> Components should silently ignore properties
1191      * that do not affect the operation of the component.
1192      *
1193      * @param propertyId The property identifier.
1194      * @param value      The value of the property.
1195      *
1196      * @throws XMLConfigurationException Thrown for configuration error.
1197      *                                  In general, components should
1198      *                                  only throw this exception if
1199      *                                  it is <strong>really</strong>
1200      *                                  a critical error.
1201      */
1202     public void setProperty(String propertyId, Object value)
1203             throws XMLConfigurationException {
1204 
1205         // Error reporter
1206         if (propertyId == Constants.XERCES_PROPERTY_PREFIX
1207                 + Constants.ERROR_REPORTER_PROPERTY) {
1208             if (value != null) {
1209                 fXPointerErrorReporter = (XMLErrorReporter) value;
1210             } else {
1211                 fXPointerErrorReporter = null;
1212             }
1213         }
1214 
1215         // Error handler
1216         if (propertyId == Constants.XERCES_PROPERTY_PREFIX
1217                 + Constants.ERROR_HANDLER_PROPERTY) {
1218             if (value != null) {
1219                 fErrorHandler = (XMLErrorHandler) value;
1220             } else {
1221                 fErrorHandler = null;
1222             }
1223         }
1224 
1225         // xml:lang
1226         if (propertyId == Constants.XERCES_FEATURE_PREFIX
1227                 + Constants.XINCLUDE_FIXUP_LANGUAGE_FEATURE) {
1228             if (value != null) {
1229                 fFixupLang = ((Boolean)value).booleanValue();
1230             } else {
1231                 fFixupLang = false;
1232             }
1233         }
1234 
1235         // xml:base
1236         if (propertyId == Constants.XERCES_FEATURE_PREFIX
1237                 + Constants.XINCLUDE_FIXUP_BASE_URIS_FEATURE) {
1238             if (value != null) {
1239                 fFixupBase = ((Boolean)value).booleanValue();
1240             } else {
1241                 fFixupBase = false;
1242             }
1243         }
1244 
1245         //
1246         if (propertyId == Constants.XERCES_PROPERTY_PREFIX
1247                 + Constants.NAMESPACE_CONTEXT_PROPERTY) {
1248             fNamespaceContext = (XIncludeNamespaceSupport) value;
1249         }
1250 
1251         super.setProperty(propertyId, value);
1252     }
1253 
1254 }