XPointerHandler.java revision 798:00fa5efc9ace
1259701Sdim/*
2259701Sdim * reserved comment block
3259701Sdim * DO NOT REMOVE OR ALTER!
4259701Sdim */
5259701Sdim/*
6259701Sdim * Licensed to the Apache Software Foundation (ASF) under one or more
7259701Sdim * contributor license agreements.  See the NOTICE file distributed with
8259701Sdim * this work for additional information regarding copyright ownership.
9259701Sdim * The ASF licenses this file to You under the Apache License, Version 2.0
10259701Sdim * (the "License"); you may not use this file except in compliance with
11259701Sdim * the License.  You may obtain a copy of the License at
12259701Sdim *
13259701Sdim *      http://www.apache.org/licenses/LICENSE-2.0
14259701Sdim *
15259701Sdim * Unless required by applicable law or agreed to in writing, software
16259701Sdim * distributed under the License is distributed on an "AS IS" BASIS,
17259701Sdim * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18259701Sdim * See the License for the specific language governing permissions and
19259701Sdim * limitations under the License.
20259701Sdim */
21259701Sdim
22259701Sdimpackage com.sun.org.apache.xerces.internal.xpointer;
23259701Sdim
24259701Sdimimport java.util.ArrayList;
25259701Sdimimport java.util.HashMap;
26259701Sdim
27259701Sdimimport com.sun.org.apache.xerces.internal.impl.Constants;
28259701Sdimimport com.sun.org.apache.xerces.internal.impl.XMLErrorReporter;
29259701Sdimimport com.sun.org.apache.xerces.internal.util.SymbolTable;
30259701Sdimimport com.sun.org.apache.xerces.internal.util.XMLChar;
31259701Sdimimport com.sun.org.apache.xerces.internal.util.XMLSymbols;
32259701Sdimimport com.sun.org.apache.xerces.internal.xinclude.XIncludeHandler;
33259701Sdimimport com.sun.org.apache.xerces.internal.xinclude.XIncludeNamespaceSupport;
34259701Sdimimport com.sun.org.apache.xerces.internal.xni.Augmentations;
35259701Sdimimport com.sun.org.apache.xerces.internal.xni.QName;
36259701Sdimimport com.sun.org.apache.xerces.internal.xni.XMLAttributes;
37259701Sdimimport com.sun.org.apache.xerces.internal.xni.XMLDocumentHandler;
38259701Sdimimport com.sun.org.apache.xerces.internal.xni.XMLString;
39259701Sdimimport com.sun.org.apache.xerces.internal.xni.XNIException;
40259701Sdimimport com.sun.org.apache.xerces.internal.xni.parser.XMLConfigurationException;
41259701Sdimimport com.sun.org.apache.xerces.internal.xni.parser.XMLErrorHandler;
42259701Sdim
43259701Sdim/**
44259701Sdim * <p>
45259701Sdim * This is a pipeline component which extends the XIncludeHandler to perform
46259701Sdim * XPointer specific processing specified in the W3C XPointerFramework and
47259701Sdim * element() Scheme Recommendations.
48259701Sdim * </p>
49259701Sdim *
50259701Sdim * <p>
51259701Sdim * This component analyzes each event in the pipeline, looking for an element
52259701Sdim * that matches a PointerPart in the parent XInclude element's xpointer attribute
53259701Sdim * value.  If the match succeeds, all children are passed by this component.
54259701Sdim * </p>
55259701Sdim *
56259701Sdim * <p>
57259701Sdim * See the <a href="http://www.w3.org/TR/xptr-framework//">XPointer Framework Recommendation</a> for
58259701Sdim * more information on the XPointer Framework and ShortHand Pointers.
59259701Sdim * See the <a href="http://www.w3.org/TR/xptr-element/">XPointer element() Scheme Recommendation</a> for
60259701Sdim * more information on the XPointer element() Scheme.
61259701Sdim * </p>
62259701Sdim *
63259701Sdim * @xerces.internal
64259701Sdim *
65259701Sdim */
66259701Sdimpublic final class XPointerHandler extends XIncludeHandler implements
67259701Sdim        XPointerProcessor {
68259701Sdim
69259701Sdim    // Fields
70259701Sdim    // An ArrayList of XPointerParts
71259701Sdim    protected ArrayList<XPointerPart> fXPointerParts = null;
72259701Sdim
73259701Sdim    // The current XPointerPart
74259701Sdim    protected XPointerPart fXPointerPart = null;
75259701Sdim
76259701Sdim    // Has the fXPointerPart resolved successfully
77259701Sdim    protected boolean fFoundMatchingPtrPart = false;
78259701Sdim
79259701Sdim    // The XPointer Error reporter
80259701Sdim    protected XMLErrorReporter fXPointerErrorReporter;
81259701Sdim
82259701Sdim    // The XPointer Error Handler
83259701Sdim    protected XMLErrorHandler fErrorHandler;
84259701Sdim
85259701Sdim    // XPointerFramework symbol table
86259701Sdim    protected SymbolTable fSymbolTable = null;
87259701Sdim
88259701Sdim    // Supported schemes
89259701Sdim    private final String ELEMENT_SCHEME_NAME = "element";
90259701Sdim
91259701Sdim   // Has the XPointer resolved the subresource
92259701Sdim    protected boolean fIsXPointerResolved = false;
93259701Sdim
94259701Sdim    // Fixup xml:base and xml:lang attributes
95259701Sdim    protected boolean fFixupBase = false;
96259701Sdim    protected boolean fFixupLang = false;
97259701Sdim
98259701Sdim    // ************************************************************************
99259701Sdim    // Constructors
100259701Sdim    // ************************************************************************
101259701Sdim
102259701Sdim    /**
103259701Sdim     *
104259701Sdim     */
105259701Sdim    public XPointerHandler() {
106259701Sdim        super();
107259701Sdim
108259701Sdim        fXPointerParts = new ArrayList<>();
109259701Sdim        fSymbolTable = new SymbolTable();
110259701Sdim    }
111259701Sdim
112259701Sdim    public XPointerHandler(SymbolTable symbolTable,
113259701Sdim            XMLErrorHandler errorHandler, XMLErrorReporter errorReporter) {
114259701Sdim        super();
115259701Sdim
116259701Sdim        fXPointerParts = new ArrayList<>();
117259701Sdim        fSymbolTable = symbolTable;
118259701Sdim        fErrorHandler = errorHandler;
119259701Sdim        fXPointerErrorReporter = errorReporter;
120259701Sdim        //fErrorReporter = errorReporter; // The XInclude ErrorReporter
121259701Sdim    }
122259701Sdim
123259701Sdim    public void setDocumentHandler(XMLDocumentHandler handler) {
124259701Sdim        fDocumentHandler = handler;
125259701Sdim    }
126259701Sdim
127259701Sdim    // ************************************************************************
128259701Sdim    //  Implementation of the XPointerProcessor interface.
129259701Sdim    // ************************************************************************
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            String str = fTokenNames.get(tokenStr);
528            Integer tokenInt = str == null ? null : Integer.parseInt(str);
529            if (tokenInt == null) {
530                tokenInt = new Integer(fTokenNames.size());
531                fTokenNames.put(tokenInt, tokenStr);
532            }
533            addToken(tokenInt.intValue());
534        }
535
536        /**
537         * Add the specified int token
538         *
539         * @param token The int specifying the token
540         */
541        private void addToken(int token) {
542            try {
543                fTokens[fTokenCount] = token;
544            } catch (ArrayIndexOutOfBoundsException ex) {
545                int[] oldList = fTokens;
546                fTokens = new int[fTokenCount << 1];
547                System.arraycopy(oldList, 0, fTokens, 0, fTokenCount);
548                fTokens[fTokenCount] = token;
549            }
550            fTokenCount++;
551        }
552
553        /**
554         * Resets the current position to the head of the token list.
555         */
556        private void rewind() {
557            fCurrentTokenIndex = 0;
558        }
559
560        /**
561         * Returns true if the {@link #getNextToken()} method
562         * returns a valid token.
563         */
564        private boolean hasMore() {
565            return fCurrentTokenIndex < fTokenCount;
566        }
567
568        /**
569         * Obtains the token at the current position, then advance
570         * the current position by one.
571         *
572         * throws If there's no such next token, this method throws
573         * <tt>new XNIException("XPointerProcessingError");</tt>.
574         */
575        private int nextToken() throws XNIException {
576            if (fCurrentTokenIndex == fTokenCount) {
577                reportError("XPointerProcessingError", null);
578            }
579            return fTokens[fCurrentTokenIndex++];
580        }
581
582        /**
583         * Obtains the token at the current position, without advancing
584         * the current position.
585         *
586         * If there's no such next token, this method throws
587         * <tt>new XNIException("XPointerProcessingError");</tt>.
588         */
589        private int peekToken() throws XNIException {
590            if (fCurrentTokenIndex == fTokenCount) {
591                reportError("XPointerProcessingError", null);
592            }
593            return fTokens[fCurrentTokenIndex];
594        }
595
596        /**
597         * Obtains the token at the current position as a String.
598         *
599         * If there's no current token or if the current token
600         * is not a string token, this method throws
601         * If there's no such next token, this method throws
602         * <tt>new XNIException("XPointerProcessingError");</tt>.
603         */
604        private String nextTokenAsString() throws XNIException {
605            String tokenStrint = getTokenString(nextToken());
606            if (tokenStrint == null) {
607                reportError("XPointerProcessingError", null);
608            }
609            return tokenStrint;
610        }
611    }
612
613    /**
614     *
615     * The XPointer expression scanner.  Scans the XPointer framework expression.
616     *
617     * @xerces.internal
618     *
619     */
620    private class Scanner {
621
622        /**
623         * 7-bit ASCII subset
624         *
625         *  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
626         *  0,  0,  0,  0,  0,  0,  0,  0,  0, HT, LF,  0,  0, CR,  0,  0,  // 0
627         *  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  // 1
628         * SP,  !,  ",  #,  $,  %,  &,  ',  (,  ),  *,  +,  ,,  -,  .,  /,  // 2
629         *  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  :,  ;,  <,  =,  >,  ?,  // 3
630         *  @,  A,  B,  C,  D,  E,  F,  G,  H,  I,  J,  K,  L,  M,  N,  O,  // 4
631         *  P,  Q,  R,  S,  T,  U,  V,  W,  X,  Y,  Z,  [,  \,  ],  ^,  _,  // 5
632         *  `,  a,  b,  c,  d,  e,  f,  g,  h,  i,  j,  k,  l,  m,  n,  o,  // 6
633         *  p,  q,  r,  s,  t,  u,  v,  w,  x,  y,  z,  {,  |,  },  ~, DEL  // 7
634         */
635        private static final byte CHARTYPE_INVALID = 0, // invalid XML character
636                CHARTYPE_OTHER = 1, // not special - one of "#%&;?\`{}~" or DEL
637                CHARTYPE_WHITESPACE = 2, // one of "\t\n\r " (0x09, 0x0A, 0x0D, 0x20)
638                CHARTYPE_CARRET = 3, // ^
639                CHARTYPE_OPEN_PAREN = 4, // '(' (0x28)
640                CHARTYPE_CLOSE_PAREN = 5, // ')' (0x29)
641                CHARTYPE_MINUS = 6, // '-' (0x2D)
642                CHARTYPE_PERIOD = 7, // '.' (0x2E)
643                CHARTYPE_SLASH = 8, // '/' (0x2F)
644                CHARTYPE_DIGIT = 9, // '0'-'9' (0x30 to 0x39)
645                CHARTYPE_COLON = 10, // ':' (0x3A)
646                CHARTYPE_EQUAL = 11, // '=' (0x3D)
647                CHARTYPE_LETTER = 12, // 'A'-'Z' or 'a'-'z' (0x41 to 0x5A and 0x61 to 0x7A)
648                CHARTYPE_UNDERSCORE = 13, // '_' (0x5F)
649                CHARTYPE_NONASCII = 14; // Non-ASCII Unicode codepoint (>= 0x80)
650
651        private final byte[] fASCIICharMap = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
652                0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
653                2, 1, 1, 1, 1, 1, 1, 1, 4, 5, 1, 1, 1, 6, 7, 8, 9, 9, 9, 9, 9,
654                9, 9, 9, 9, 9, 10, 1, 1, 11, 1, 1, 1, 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, 12, 1, 1, 1, 3, 13, 1, 12, 12, 12, 12, 12, 12, 12,
657                12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
658                12, 12, 12, 1, 1, 1, 1, 1 };
659
660        //
661        // Data
662        //
663        /** Symbol table. */
664        private SymbolTable fSymbolTable;
665
666        /**
667         * Constructs an XPointer Framework expression scanner.
668         *
669         * @param symbolTable SymbolTable
670         */
671        private Scanner(SymbolTable symbolTable) {
672            // save pool and tokens
673            fSymbolTable = symbolTable;
674
675        } // <init>(SymbolTable)
676
677        /**
678         * Scans the XPointer Expression
679         *
680         */
681        private boolean scanExpr(SymbolTable symbolTable, Tokens tokens,
682                String data, int currentOffset, int endOffset)
683                throws XNIException {
684
685            int ch;
686            int openParen = 0;
687            int closeParen = 0;
688            int nameOffset, dataOffset;
689            boolean isQName = false;
690            String name = null;
691            String prefix = null;
692            String schemeData = null;
693            StringBuffer schemeDataBuff = new StringBuffer();
694
695            while (true) {
696
697                if (currentOffset == endOffset) {
698                    break;
699                }
700                ch = data.charAt(currentOffset);
701
702                //
703                while (ch == ' ' || ch == 0x0A || ch == 0x09 || ch == 0x0D) {
704                    if (++currentOffset == endOffset) {
705                        break;
706                    }
707                    ch = data.charAt(currentOffset);
708                }
709                if (currentOffset == endOffset) {
710                    break;
711                }
712
713                //
714                // [1]    Pointer      ::=    Shorthand | SchemeBased
715                // [2]    Shorthand    ::=    NCName
716                // [3]    SchemeBased  ::=    PointerPart (S? PointerPart)*
717                // [4]    PointerPart  ::=    SchemeName '(' SchemeData ')'
718                // [5]    SchemeName   ::=    QName
719                // [6]    SchemeData   ::=    EscapedData*
720                // [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
721                // [8]    NormalChar   ::=    UnicodeChar - [()^]
722                // [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
723                // [?]    QName        ::=    (NCName ':')? NCName
724                // [?]    NCName       ::=    (Letter | '_') (NCNameChar)*
725                // [?]    NCNameChar   ::=    Letter | Digit | '.' | '-' | '_'  (ascii subset of 'NCNameChar')
726                // [?]    Letter       ::=    [A-Za-z]                              (ascii subset of 'Letter')
727                // [?]    Digit        ::=    [0-9]                                  (ascii subset of 'Digit')
728                //
729                byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
730                        : fASCIICharMap[ch];
731
732                switch (chartype) {
733
734                case CHARTYPE_OPEN_PAREN: // '('
735                    addToken(tokens, Tokens.XPTRTOKEN_OPEN_PAREN);
736                    openParen++;
737                    ++currentOffset;
738                    break;
739
740                case CHARTYPE_CLOSE_PAREN: // ')'
741                    addToken(tokens, Tokens.XPTRTOKEN_CLOSE_PAREN);
742                    closeParen++;
743                    ++currentOffset;
744                    break;
745
746                case CHARTYPE_CARRET:
747                case CHARTYPE_COLON:
748                case CHARTYPE_DIGIT:
749                case CHARTYPE_EQUAL:
750                case CHARTYPE_LETTER:
751                case CHARTYPE_MINUS:
752                case CHARTYPE_NONASCII:
753                case CHARTYPE_OTHER:
754                case CHARTYPE_PERIOD:
755                case CHARTYPE_SLASH:
756                case CHARTYPE_UNDERSCORE:
757                case CHARTYPE_WHITESPACE:
758                    // Scanning SchemeName | Shorthand
759                    if (openParen == 0) {
760                        nameOffset = currentOffset;
761                        currentOffset = scanNCName(data, endOffset,
762                                currentOffset);
763
764                        if (currentOffset == nameOffset) {
765                            reportError("InvalidShortHandPointer",
766                                    new Object[] { data });
767                            return false;
768                        }
769
770                        if (currentOffset < endOffset) {
771                            ch = data.charAt(currentOffset);
772                        } else {
773                            ch = -1;
774                        }
775
776                        name = symbolTable.addSymbol(data.substring(nameOffset,
777                                currentOffset));
778                        prefix = XMLSymbols.EMPTY_STRING;
779
780                        // The name is a QName => a SchemeName
781                        if (ch == ':') {
782                            if (++currentOffset == endOffset) {
783                                return false;
784                            }
785
786                            ch = data.charAt(currentOffset);
787                            prefix = name;
788                            nameOffset = currentOffset;
789                            currentOffset = scanNCName(data, endOffset,
790                                    currentOffset);
791
792                            if (currentOffset == nameOffset) {
793                                return false;
794                            }
795
796                            if (currentOffset < endOffset) {
797                                ch = data.charAt(currentOffset);
798                            } else {
799                                ch = -1;
800                            }
801
802                            isQName = true;
803                            name = symbolTable.addSymbol(data.substring(
804                                    nameOffset, currentOffset));
805                        }
806
807                        // REVISIT:
808                        if (currentOffset != endOffset) {
809                            addToken(tokens, Tokens.XPTRTOKEN_SCHEMENAME);
810                            tokens.addToken(prefix);
811                            tokens.addToken(name);
812                            isQName = false;
813                        } else if (currentOffset == endOffset) {
814                            // NCName => Shorthand
815                            addToken(tokens, Tokens.XPTRTOKEN_SHORTHAND);
816                            tokens.addToken(name);
817                            isQName = false;
818                        }
819
820                        // reset open/close paren for the next pointer part
821                        closeParen = 0;
822
823                        break;
824
825                    } else if (openParen > 0 && closeParen == 0 && name != null) {
826                        // Scanning SchemeData
827                        dataOffset = currentOffset;
828                        currentOffset = scanData(data, schemeDataBuff,
829                                endOffset, currentOffset);
830
831                        if (currentOffset == dataOffset) {
832                            reportError("InvalidSchemeDataInXPointer",
833                                    new Object[] { data });
834                            return false;
835                        }
836
837                        if (currentOffset < endOffset) {
838                            ch = data.charAt(currentOffset);
839                        } else {
840                            ch = -1;
841                        }
842
843                        schemeData = symbolTable.addSymbol(schemeDataBuff
844                                .toString());
845                        addToken(tokens, Tokens.XPTRTOKEN_SCHEMEDATA);
846                        tokens.addToken(schemeData);
847
848                        // reset open/close paren for the next pointer part
849                        openParen = 0;
850                        schemeDataBuff.delete(0, schemeDataBuff.length());
851
852                    } else {
853                        // ex. schemeName()
854                        // Should we throw an exception with a more suitable message instead??
855                        return false;
856                    }
857                }
858            } // end while
859            return true;
860        }
861
862        /**
863         * Scans a NCName.
864         * From Namespaces in XML
865         * [5] NCName ::= (Letter | '_') (NCNameChar)*
866         * [6] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
867         *
868         * @param data A String containing the XPointer expression
869         * @param endOffset The int XPointer expression length
870         * @param currentOffset An int representing the current position of the XPointer expression pointer
871         */
872        private int scanNCName(String data, int endOffset, int currentOffset) {
873            int ch = data.charAt(currentOffset);
874            if (ch >= 0x80) {
875                if (!XMLChar.isNameStart(ch)) {
876                    return currentOffset;
877                }
878            } else {
879                byte chartype = fASCIICharMap[ch];
880                if (chartype != CHARTYPE_LETTER
881                        && chartype != CHARTYPE_UNDERSCORE) {
882                    return currentOffset;
883                }
884            }
885
886            //while (currentOffset++ < endOffset) {
887            while (++currentOffset < endOffset) {
888                ch = data.charAt(currentOffset);
889                if (ch >= 0x80) {
890                    if (!XMLChar.isName(ch)) {
891                        break;
892                    }
893                } else {
894                    byte chartype = fASCIICharMap[ch];
895                    if (chartype != CHARTYPE_LETTER
896                            && chartype != CHARTYPE_DIGIT
897                            && chartype != CHARTYPE_PERIOD
898                            && chartype != CHARTYPE_MINUS
899                            && chartype != CHARTYPE_UNDERSCORE) {
900                        break;
901                    }
902                }
903            }
904            return currentOffset;
905        }
906
907        /**
908         * Scans the SchemeData.
909         * [6]    SchemeData   ::=    EscapedData*
910         * [7]    EscapedData  ::=    NormalChar | '^(' | '^)' | '^^' | '(' SchemeData ')'
911         * [8]    NormalChar   ::=    UnicodeChar - [()^]
912         * [9]    UnicodeChar  ::=    [#x0-#x10FFFF]
913         *
914         */
915        private int scanData(String data, StringBuffer schemeData,
916                int endOffset, int currentOffset) {
917            while (true) {
918
919                if (currentOffset == endOffset) {
920                    break;
921                }
922
923                int ch = data.charAt(currentOffset);
924                byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
925                        : fASCIICharMap[ch];
926
927                if (chartype == CHARTYPE_OPEN_PAREN) {
928                    schemeData.append(ch);
929                    //schemeData.append(Tokens.XPTRTOKEN_OPEN_PAREN);
930                    currentOffset = scanData(data, schemeData, endOffset,
931                            ++currentOffset);
932                    if (currentOffset == endOffset) {
933                        return currentOffset;
934                    }
935
936                    ch = data.charAt(currentOffset);
937                    chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
938                            : fASCIICharMap[ch];
939
940                    if (chartype != CHARTYPE_CLOSE_PAREN) {
941                        return endOffset;
942                    }
943                    schemeData.append((char) ch);
944                    ++currentOffset;//
945
946                } else if (chartype == CHARTYPE_CLOSE_PAREN) {
947                    return currentOffset;
948
949                } else  if (chartype == CHARTYPE_CARRET) {
950                    ch = data.charAt(++currentOffset);
951                    chartype = (ch >= 0x80) ? CHARTYPE_NONASCII
952                            : fASCIICharMap[ch];
953
954                    if (chartype != CHARTYPE_CARRET
955                            && chartype != CHARTYPE_OPEN_PAREN
956                            && chartype != CHARTYPE_CLOSE_PAREN) {
957                        break;
958                    }
959                    schemeData.append((char) ch);
960                    ++currentOffset;
961
962                } else {
963                    schemeData.append((char) ch);
964                    ++currentOffset;//
965                }
966            }
967
968            return currentOffset;
969        }
970
971        //
972        // Protected methods
973        //
974
975        /**
976         * This method adds the specified token to the token list. By
977         * default, this method allows all tokens. However, subclasses
978         * of the XPathExprScanner can override this method in order
979         * to disallow certain tokens from being used in the scanned
980         * XPath expression. This is a convenient way of allowing only
981         * a subset of XPath.
982         */
983        protected void addToken(Tokens tokens, int token) throws XNIException {
984            tokens.addToken(token);
985        } // addToken(int)
986
987    } // class Scanner
988
989    // ************************************************************************
990    //  Overridden XMLDocumentHandler methods
991    // ************************************************************************
992    /**
993     * If the comment is a child of a matched element, then pass else return.
994     *
995     * @param text   The text in the comment.
996     * @param augs   Additional information that may include infoset augmentations
997     *
998     * @exception XNIException
999     *                   Thrown by application to signal an error.
1000     */
1001    public void comment(XMLString text, Augmentations augs) throws XNIException {
1002        if (!isChildFragmentResolved()) {
1003            return;
1004        }
1005        super.comment(text, augs);
1006    }
1007
1008    /**
1009     * A processing instruction. Processing instructions consist of a
1010     * target name and, optionally, text data. The data is only meaningful
1011     * to the application.
1012     * <p>
1013     * Typically, a processing instruction's data will contain a series
1014     * of pseudo-attributes. These pseudo-attributes follow the form of
1015     * element attributes but are <strong>not</strong> parsed or presented
1016     * to the application as anything other than text. The application is
1017     * responsible for parsing the data.
1018     *
1019     * @param target The target.
1020     * @param data   The data or null if none specified.
1021     * @param augs   Additional information that may include infoset augmentations
1022     *
1023     * @exception XNIException
1024     *                   Thrown by handler to signal an error.
1025     */
1026    public void processingInstruction(String target, XMLString data,
1027            Augmentations augs) throws XNIException {
1028        if (!isChildFragmentResolved()) {
1029            return;
1030        }
1031        super.processingInstruction(target, data, augs);
1032    }
1033
1034    /**
1035     * The start of an element.
1036     *
1037     * @param element    The name of the element.
1038     * @param attributes The element attributes.
1039     * @param augs       Additional information that may include infoset augmentations
1040     *
1041     * @exception XNIException
1042     *                   Thrown by handler to signal an error.
1043     */
1044    public void startElement(QName element, XMLAttributes attributes,
1045            Augmentations augs) throws XNIException {
1046        if (!resolveXPointer(element, attributes, augs,
1047                XPointerPart.EVENT_ELEMENT_START)) {
1048
1049            // xml:base and xml:lang processing
1050                if (fFixupBase) {
1051                processXMLBaseAttributes(attributes);
1052                }
1053            if (fFixupLang) {
1054                processXMLLangAttributes(attributes);
1055            }
1056
1057            // set the context invalid if the element till an element from the result infoset is included
1058            fNamespaceContext.setContextInvalid();
1059
1060            return;
1061        }
1062        super.startElement(element, attributes, augs);
1063    }
1064
1065    /**
1066     * An empty element.
1067     *
1068     * @param element    The name of the element.
1069     * @param attributes The element attributes.
1070     * @param augs       Additional information that may include infoset augmentations
1071     *
1072     * @exception XNIException
1073     *                   Thrown by handler to signal an error.
1074     */
1075    public void emptyElement(QName element, XMLAttributes attributes,
1076            Augmentations augs) throws XNIException {
1077        if (!resolveXPointer(element, attributes, augs,
1078                XPointerPart.EVENT_ELEMENT_EMPTY)) {
1079            // xml:base and xml:lang processing
1080                if (fFixupBase) {
1081                processXMLBaseAttributes(attributes);
1082                }
1083            if (fFixupLang) {
1084                processXMLLangAttributes(attributes);
1085            }
1086            // no need to restore restoreBaseURI() for xml:base and xml:lang processing
1087
1088            // set the context invalid if the element till an element from the result infoset is included
1089            fNamespaceContext.setContextInvalid();
1090            return;
1091        }
1092        super.emptyElement(element, attributes, augs);
1093    }
1094
1095    /**
1096     * Character content.
1097     *
1098     * @param text   The content.
1099     * @param augs   Additional information that may include infoset augmentations
1100     *
1101     * @exception XNIException
1102     *                   Thrown by handler to signal an error.
1103     */
1104    public void characters(XMLString text, Augmentations augs)
1105            throws XNIException {
1106        if (!isChildFragmentResolved()) {
1107            return;
1108        }
1109        super.characters(text, augs);
1110    }
1111
1112    /**
1113     * Ignorable whitespace. For this method to be called, the document
1114     * source must have some way of determining that the text containing
1115     * only whitespace characters should be considered ignorable. For
1116     * example, the validator can determine if a length of whitespace
1117     * characters in the document are ignorable based on the element
1118     * content model.
1119     *
1120     * @param text   The ignorable whitespace.
1121     * @param augs   Additional information that may include infoset augmentations
1122     *
1123     * @exception XNIException
1124     *                   Thrown by handler to signal an error.
1125     */
1126    public void ignorableWhitespace(XMLString text, Augmentations augs)
1127            throws XNIException {
1128        if (!isChildFragmentResolved()) {
1129            return;
1130        }
1131        super.ignorableWhitespace(text, augs);
1132    }
1133
1134    /**
1135     * The end of an element.
1136     *
1137     * @param element The name of the element.
1138     * @param augs    Additional information that may include infoset augmentations
1139     *
1140     * @exception XNIException
1141     *                   Thrown by handler to signal an error.
1142     */
1143    public void endElement(QName element, Augmentations augs)
1144            throws XNIException {
1145        if (!resolveXPointer(element, null, augs,
1146                XPointerPart.EVENT_ELEMENT_END)) {
1147
1148            // no need to restore restoreBaseURI() for xml:base and xml:lang processing
1149            return;
1150        }
1151        super.endElement(element, augs);
1152    }
1153
1154    /**
1155     * The start of a CDATA section.
1156     *
1157     * @param augs   Additional information that may include infoset augmentations
1158     *
1159     * @exception XNIException
1160     *                   Thrown by handler to signal an error.
1161     */
1162    public void startCDATA(Augmentations augs) throws XNIException {
1163        if (!isChildFragmentResolved()) {
1164            return;
1165        }
1166        super.startCDATA(augs);
1167    }
1168
1169    /**
1170     * The end of a CDATA section.
1171     *
1172     * @param augs   Additional information that may include infoset augmentations
1173     *
1174     * @exception XNIException
1175     *                   Thrown by handler to signal an error.
1176     */
1177    public void endCDATA(Augmentations augs) throws XNIException {
1178        if (!isChildFragmentResolved()) {
1179            return;
1180        }
1181        super.endCDATA(augs);
1182    }
1183
1184    // ************************************************************************
1185    // Overridden XMLComponent methods
1186    // ************************************************************************
1187    /**
1188     * <p>
1189     * Sets the value of a property. This method is called by the component
1190     * manager any time after reset when a property changes value.
1191     * </p>
1192     * <strong>Note:</strong> Components should silently ignore properties
1193     * that do not affect the operation of the component.
1194     *
1195     * @param propertyId The property identifier.
1196     * @param value      The value of the property.
1197     *
1198     * @throws XMLConfigurationException Thrown for configuration error.
1199     *                                  In general, components should
1200     *                                  only throw this exception if
1201     *                                  it is <strong>really</strong>
1202     *                                  a critical error.
1203     */
1204    public void setProperty(String propertyId, Object value)
1205            throws XMLConfigurationException {
1206
1207        // Error reporter
1208        if (propertyId == Constants.XERCES_PROPERTY_PREFIX
1209                + Constants.ERROR_REPORTER_PROPERTY) {
1210            if (value != null) {
1211                fXPointerErrorReporter = (XMLErrorReporter) value;
1212            } else {
1213                fXPointerErrorReporter = null;
1214            }
1215        }
1216
1217        // Error handler
1218        if (propertyId == Constants.XERCES_PROPERTY_PREFIX
1219                + Constants.ERROR_HANDLER_PROPERTY) {
1220            if (value != null) {
1221                fErrorHandler = (XMLErrorHandler) value;
1222            } else {
1223                fErrorHandler = null;
1224            }
1225        }
1226
1227        // xml:lang
1228        if (propertyId == Constants.XERCES_FEATURE_PREFIX
1229                + Constants.XINCLUDE_FIXUP_LANGUAGE_FEATURE) {
1230            if (value != null) {
1231                fFixupLang = ((Boolean)value).booleanValue();
1232            } else {
1233                fFixupLang = false;
1234            }
1235        }
1236
1237        // xml:base
1238        if (propertyId == Constants.XERCES_FEATURE_PREFIX
1239                + Constants.XINCLUDE_FIXUP_BASE_URIS_FEATURE) {
1240            if (value != null) {
1241                fFixupBase = ((Boolean)value).booleanValue();
1242            } else {
1243                fFixupBase = false;
1244            }
1245        }
1246
1247        //
1248        if (propertyId == Constants.XERCES_PROPERTY_PREFIX
1249                + Constants.NAMESPACE_CONTEXT_PROPERTY) {
1250            fNamespaceContext = (XIncludeNamespaceSupport) value;
1251        }
1252
1253        super.setProperty(propertyId, value);
1254    }
1255
1256}
1257