/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.xerces.xpointer; import java.util.HashMap; import org.apache.xerces.impl.XMLErrorReporter; import org.apache.xerces.util.SymbolTable; import org.apache.xerces.util.XMLChar; import org.apache.xerces.xni.Augmentations; import org.apache.xerces.xni.QName; import org.apache.xerces.xni.XMLAttributes; import org.apache.xerces.xni.XNIException; import org.apache.xerces.xni.parser.XMLErrorHandler; /** * <p> * Implements the XPointerPart interface for element() scheme specific processing. * </p> * * @xerces.internal * * @version $Id: ElementSchemePointer.java 603808 2007-12-13 03:44:48Z mrglavas $ */ final class ElementSchemePointer implements XPointerPart { // Fields // The Scheme Name i.e element private String fSchemeName; // The scheme Data private String fSchemeData; // The scheme Data & child sequence private String fShortHandPointerName; // Should we attempt to resolve the ChildSequence from the // current element position. If a ShortHand Pointer is present // attempt to resolve relative to the short hand pointer. private boolean fIsResolveElement = false; // Has the element been found private boolean fIsElementFound = false; // Was only an empty element found private boolean fWasOnlyEmptyElementFound = false; // If a shorthand pointer is present and resolved boolean fIsShortHand = false; // The depth at which the element was found int fFoundDepth = 0; // The XPointer element child sequence private int fChildSequence[]; // The current child position private int fCurrentChildPosition = 1; // The current child depth private int fCurrentChildDepth = 0; // The current element's child sequence private int fCurrentChildSequence[];; // Stores if the Fragment was resolved by the pointer private boolean fIsFragmentResolved = false; // Stores if the Fragment was resolved by the pointer private ShortHandPointer fShortHandPointer; // The XPointer Error reporter protected XMLErrorReporter fErrorReporter; // The XPointer Error Handler protected XMLErrorHandler fErrorHandler; // private SymbolTable fSymbolTable; // ************************************************************************ // Constructors // ************************************************************************ public ElementSchemePointer() { } public ElementSchemePointer(SymbolTable symbolTable) { fSymbolTable = symbolTable; } public ElementSchemePointer(SymbolTable symbolTable, XMLErrorReporter errorReporter) { fSymbolTable = symbolTable; fErrorReporter = errorReporter; } // ************************************************************************ // XPointerPart implementation // ************************************************************************ /** * Parses the XPointer expression and tokenizes it into Strings * delimited by whitespace. * * @see org.apache.xerces.xpointer.XPointerProcessor#parseXPointer(java.lang.String) */ public void parseXPointer(String xpointer) throws XNIException { // init(); // tokens final Tokens tokens = new Tokens(fSymbolTable); // scanner Scanner scanner = new Scanner(fSymbolTable) { protected void addToken(Tokens tokens, int token) throws XNIException { if (token == Tokens.XPTRTOKEN_ELEM_CHILD || token == Tokens.XPTRTOKEN_ELEM_NCNAME) { super.addToken(tokens, token); return; } reportError("InvalidElementSchemeToken", new Object[] { tokens .getTokenString(token) }); } }; // scan the element() XPointer expression int length = xpointer.length(); boolean success = scanner.scanExpr(fSymbolTable, tokens, xpointer, 0, length); if (!success) { reportError("InvalidElementSchemeXPointer", new Object[] { xpointer }); } // Initialize a temp arrays to the size of token count which should // be atleast twice the size of child sequence, to hold the ChildSequence. int tmpChildSequence[] = new int[tokens.getTokenCount() / 2 + 1]; // the element depth int i = 0; // Traverse the scanned tokens while (tokens.hasMore()) { int token = tokens.nextToken(); switch (token) { case Tokens.XPTRTOKEN_ELEM_NCNAME: { // Note: Only a single ShortHand pointer can be present // The shortHand name token = tokens.nextToken(); fShortHandPointerName = tokens.getTokenString(token); // Create a new ShortHandPointer fShortHandPointer = new ShortHandPointer(fSymbolTable); fShortHandPointer.setSchemeName(fShortHandPointerName); break; } case Tokens.XPTRTOKEN_ELEM_CHILD: { tmpChildSequence[i] = tokens.nextToken(); i++; break; } default: reportError("InvalidElementSchemeXPointer", new Object[] { xpointer }); } } // Initialize the arrays to the number of elements in the ChildSequence. fChildSequence = new int[i]; fCurrentChildSequence = new int[i]; System.arraycopy(tmpChildSequence, 0, fChildSequence, 0, i); } /** * Returns the scheme name i.e element * @see org.apache.xerces.xpointer.XPointerPart#getSchemeName() */ public String getSchemeName() { return fSchemeName; } /** * Returns the scheme data * * @see org.apache.xerces.xpointer.XPointerPart#getSchemeData() */ public String getSchemeData() { return fSchemeData; } /** * Sets the scheme name * * @see org.apache.xerces.xpointer.XPointerPart#setSchemeName(java.lang.String) */ public void setSchemeName(String schemeName) { fSchemeName = schemeName; } /** * Sets the scheme data * * @see org.apache.xerces.xpointer.XPointerPart#setSchemeData(java.lang.String) */ public void setSchemeData(String schemeData) { fSchemeData = schemeData; } /** * Responsible for resolving the element() scheme XPointer. If a ShortHand * Pointer is present and it is successfully resolved and if a child * sequence is present, the child sequence is resolved relative to it. * * @see org.apache.xerces.xpointer.XPointerProcessor#resolveXPointer(org.apache.xerces.xni.QName, org.apache.xerces.xni.XMLAttributes, org.apache.xerces.xni.Augmentations, int event) */ public boolean resolveXPointer(QName element, XMLAttributes attributes, Augmentations augs, int event) throws XNIException { boolean isShortHandPointerResolved = false; // if a ChildSequence exisits, resolve child elements // if an element name exists if (fShortHandPointerName != null) { // resolve ShortHand Pointer isShortHandPointerResolved = fShortHandPointer.resolveXPointer( element, attributes, augs, event); if (isShortHandPointerResolved) { fIsResolveElement = true; fIsShortHand = true; } else { fIsResolveElement = false; } } else { fIsResolveElement = true; } // Added here to skip the ShortHand pointer corresponding to // an element if one exisits and start searching from its child if (fChildSequence.length > 0) { fIsFragmentResolved = matchChildSequence(element, event); } else if (isShortHandPointerResolved && fChildSequence.length <= 0) { // if only a resolved shorthand pointer exists fIsFragmentResolved = isShortHandPointerResolved; } else { fIsFragmentResolved = false; } return fIsFragmentResolved; } /** * Matches the current element position in the document tree with the * element position specified in the element XPointer scheme. * * @param event * @return boolean - true if the current element position in the document * tree matches theelement position specified in the element XPointer * scheme. */ protected boolean matchChildSequence(QName element, int event) throws XNIException { // need to resize fCurrentChildSequence if (fCurrentChildDepth >= fCurrentChildSequence.length) { int tmpCurrentChildSequence[] = new int[fCurrentChildSequence.length]; System.arraycopy(fCurrentChildSequence, 0, tmpCurrentChildSequence, 0, fCurrentChildSequence.length); // Increase the size by a factor of 2 (?) fCurrentChildSequence = new int[fCurrentChildDepth * 2]; System.arraycopy(tmpCurrentChildSequence, 0, fCurrentChildSequence, 0, tmpCurrentChildSequence.length); } // if (fIsResolveElement) { // start if (event == XPointerPart.EVENT_ELEMENT_START) { fCurrentChildSequence[fCurrentChildDepth] = fCurrentChildPosition; fCurrentChildDepth++; // reset the current child position fCurrentChildPosition = 1; //if (!fSchemeNameFound) { if ((fCurrentChildDepth <= fFoundDepth) || (fFoundDepth == 0)) { if (checkMatch()) { fIsElementFound = true; fFoundDepth = fCurrentChildDepth; } else { fIsElementFound = false; fFoundDepth = 0; } } } else if (event == XPointerPart.EVENT_ELEMENT_END) { if (fCurrentChildDepth == fFoundDepth) { fIsElementFound = true; } else if (((fCurrentChildDepth < fFoundDepth) && (fFoundDepth != 0)) || ((fCurrentChildDepth > fFoundDepth) // or empty element found && (fFoundDepth == 0))) { fIsElementFound = false; } // reset array position of last child fCurrentChildSequence[fCurrentChildDepth] = 0; fCurrentChildDepth--; fCurrentChildPosition = fCurrentChildSequence[fCurrentChildDepth] + 1; } else if (event == XPointerPart.EVENT_ELEMENT_EMPTY) { fCurrentChildSequence[fCurrentChildDepth] = fCurrentChildPosition; fCurrentChildPosition++; // Donot check for empty elements if the empty element is // a child of a found parent element if (checkMatch()) { if (!fIsElementFound) { fWasOnlyEmptyElementFound = true; } else { fWasOnlyEmptyElementFound = false; } fIsElementFound = true; } else { fIsElementFound = false; fWasOnlyEmptyElementFound = false; } } } return fIsElementFound; } /** * Matches the current position of the element being visited by checking * its position and previous elements against the element XPointer expression. * If a match is found it return true else false. * * @return boolean */ protected boolean checkMatch() { // If the number of elements in the ChildSequence is greater than the // current child depth, there is not point in checking further if (!fIsShortHand) { // If a shorthand pointer is not present traverse the children // and compare if (fChildSequence.length <= fCurrentChildDepth + 1) { for (int i = 0; i < fChildSequence.length; i++) { if (fChildSequence[i] != fCurrentChildSequence[i]) { return false; } } } else { return false; } } else { // If a shorthand pointer is present traverse the children // ignoring the first element of the CurrenChildSequence which // contains the ShortHand pointer element and compare if (fChildSequence.length <= fCurrentChildDepth + 1) { for (int i = 0; i < fChildSequence.length; i++) { // ensure fCurrentChildSequence is large enough if (fCurrentChildSequence.length < i + 2) { return false; } // ignore the first element of fCurrentChildSequence if (fChildSequence[i] != fCurrentChildSequence[i + 1]) { return false; } } } else { return false; } } return true; } /** * Returns true if the node matches or is a child of a matching element() * scheme XPointer. * * @see org.apache.xerces.xpointer.XPointerProcessor#isFragmentResolved() */ public boolean isFragmentResolved() throws XNIException { // Return true if the Fragment was resolved and the current Node depth // is greater than or equal to the depth at which the element was found return fIsFragmentResolved ; } /** * Returns true if the XPointer expression resolves to a non-element child * of the current resource fragment. * * @see org.apache.xerces.xpointer.XPointerPart#isChildFragmentResolved() * */ public boolean isChildFragmentResolved() { // if only a shorthand pointer was present if (fIsShortHand && fShortHandPointer != null && fChildSequence.length <= 0) { return fShortHandPointer.isChildFragmentResolved(); } else { return fWasOnlyEmptyElementFound ? !fWasOnlyEmptyElementFound : (fIsFragmentResolved && (fCurrentChildDepth >= fFoundDepth)); } } /** * Reports an XPointer error */ protected void reportError(String key, Object[] arguments) throws XNIException { /*fErrorReporter.reportError(XPointerMessageFormatter.XPOINTER_DOMAIN, key, arguments, XMLErrorReporter.SEVERITY_ERROR); */ throw new XNIException((fErrorReporter .getMessageFormatter(XPointerMessageFormatter.XPOINTER_DOMAIN)) .formatMessage(fErrorReporter.getLocale(), key, arguments)); } /** * Initializes error handling objects */ protected void initErrorReporter() { if (fErrorReporter == null) { fErrorReporter = new XMLErrorReporter(); } if (fErrorHandler == null) { fErrorHandler = new XPointerErrorHandler(); } fErrorReporter.putMessageFormatter( XPointerMessageFormatter.XPOINTER_DOMAIN, new XPointerMessageFormatter()); } /** * Initializes the element scheme processor */ protected void init() { fSchemeName = null; fSchemeData = null; fShortHandPointerName = null; fIsResolveElement = false; fIsElementFound = false; fWasOnlyEmptyElementFound = false; fFoundDepth = 0; fCurrentChildPosition = 1; fCurrentChildDepth = 0; fIsFragmentResolved = false; fShortHandPointer = null; initErrorReporter(); } // ************************************************************************ // element() Scheme expression scanner // ************************************************************************ /** * List of XPointer Framework tokens. * * @xerces.internal * * @author Neil Delima, IBM * @version $Id: ElementSchemePointer.java 603808 2007-12-13 03:44:48Z mrglavas $ * */ private final class Tokens { /** * XPointer element() scheme * [1] ElementSchemeData ::= (NCName ChildSequence?) | ChildSequence * [2] ChildSequence ::= ('/' [1-9] [0-9]*)+ */ private static final int XPTRTOKEN_ELEM_NCNAME = 0; private static final int XPTRTOKEN_ELEM_CHILD = 1; // Token names private final String[] fgTokenNames = { "XPTRTOKEN_ELEM_NCNAME", "XPTRTOKEN_ELEM_CHILD" }; // Token count private static final int INITIAL_TOKEN_COUNT = 1 << 8; private int[] fTokens = new int[INITIAL_TOKEN_COUNT]; private int fTokenCount = 0; // Current token position private int fCurrentTokenIndex; private SymbolTable fSymbolTable; private HashMap fTokenNames = new HashMap(); /** * Constructor * * @param symbolTable SymbolTable */ private Tokens(SymbolTable symbolTable) { fSymbolTable = symbolTable; fTokenNames.put(new Integer(XPTRTOKEN_ELEM_NCNAME), "XPTRTOKEN_ELEM_NCNAME"); fTokenNames.put(new Integer(XPTRTOKEN_ELEM_CHILD), "XPTRTOKEN_ELEM_CHILD"); } /* * Returns the token String * @param token The index of the token * @return String The token string */ private String getTokenString(int token) { return (String) fTokenNames.get(new Integer(token)); } /** * Returns the token String * @param token The index of the token * @return String The token string */ private Integer getToken(int token) { return (Integer) fTokenNames.get(new Integer(token)); } /** * Add the specified string as a token * * @param token The token string */ private void addToken(String tokenStr) { Integer tokenInt = (Integer) fTokenNames.get(tokenStr); if (tokenInt == null) { tokenInt = new Integer(fTokenNames.size()); fTokenNames.put(tokenInt, tokenStr); } addToken(tokenInt.intValue()); } /** * Add the specified int token * * @param token The int specifying the token */ private void addToken(int token) { try { fTokens[fTokenCount] = token; } catch (ArrayIndexOutOfBoundsException ex) { int[] oldList = fTokens; fTokens = new int[fTokenCount << 1]; System.arraycopy(oldList, 0, fTokens, 0, fTokenCount); fTokens[fTokenCount] = token; } fTokenCount++; } /** * Resets the current position to the head of the token list. */ private void rewind() { fCurrentTokenIndex = 0; } /** * Returns true if the {@link #getNextToken()} method * returns a valid token. */ private boolean hasMore() { return fCurrentTokenIndex < fTokenCount; } /** * Obtains the token at the current position, then advance * the current position by one. * * If there's no such next token, this method throws * <tt>new XNIException("InvalidXPointerExpression");</tt>. */ private int nextToken() throws XNIException { if (fCurrentTokenIndex == fTokenCount) reportError("XPointerElementSchemeProcessingError", null); return fTokens[fCurrentTokenIndex++]; } /** * Obtains the token at the current position, without advancing * the current position. * * If there's no such next token, this method throws * <tt>new XNIException("InvalidXPointerExpression");</tt>. */ private int peekToken() throws XNIException { if (fCurrentTokenIndex == fTokenCount) reportError("XPointerElementSchemeProcessingError", null); return fTokens[fCurrentTokenIndex]; } /** * Obtains the token at the current position as a String. * * If there's no current token or if the current token * is not a string token, this method throws * If there's no such next token, this method throws * <tt>new XNIException("InvalidXPointerExpression");</tt>. */ private String nextTokenAsString() throws XNIException { String s = getTokenString(nextToken()); if (s == null) reportError("XPointerElementSchemeProcessingError", null); return s; } /** * Returns the number of tokens. * */ private int getTokenCount() { return fTokenCount; } } /** * * The XPointer expression scanner. Scans the XPointer framework expression. * * @xerces.internal * * @version $Id: ElementSchemePointer.java 603808 2007-12-13 03:44:48Z mrglavas $ */ private class Scanner { /** * 7-bit ASCII subset * * 0 1 2 3 4 5 6 7 8 9 A B C D E F * 0, 0, 0, 0, 0, 0, 0, 0, 0, HT, LF, 0, 0, CR, 0, 0, // 0 * 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 * SP, !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, // 2 * 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?, // 3 * @, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, // 4 * P, Q, R, S, T, U, V, W, X, Y, Z, [, \, ], ^, _, // 5 * `, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, // 6 * p, q, r, s, t, u, v, w, x, y, z, {, |, }, ~, DEL // 7 */ private static final byte CHARTYPE_INVALID = 0, // invalid XML characters, control characters and 7F CHARTYPE_OTHER = 1, // A valid XML character (possibly invalid NCNameChar) that does not fall in one of the other categories CHARTYPE_MINUS = 2, // '-' (0x2D) CHARTYPE_PERIOD = 3, // '.' (0x2E) CHARTYPE_SLASH = 4, // '/' (0x2F) CHARTYPE_DIGIT = 5, // '0'-'9' (0x30 to 0x39) CHARTYPE_LETTER = 6, // 'A'-'Z' or 'a'-'z' (0x41 to 0x5A and 0x61 to 0x7A) CHARTYPE_UNDERSCORE = 7, // '_' (0x5F) CHARTYPE_NONASCII = 8; // Non-ASCII Unicode codepoint (>= 0x80) private final byte[] fASCIICharMap = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 7, 1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1, 1, 1, 1, 1 }; /** * Symbol literals */ // // Data // /** Symbol table. */ private SymbolTable fSymbolTable; // // Constructors // /** * Constructs an XPath expression scanner. * * @param symbolTable SymbolTable */ private Scanner(SymbolTable symbolTable) { // save pool and tokens fSymbolTable = symbolTable; } // <init>(SymbolTable) /** * Scans the XPointer Expression * */ private boolean scanExpr(SymbolTable symbolTable, Tokens tokens, String data, int currentOffset, int endOffset) throws XNIException { int ch; int nameOffset; String nameHandle = null; while (true) { if (currentOffset == endOffset) { break; } ch = data.charAt(currentOffset); byte chartype = (ch >= 0x80) ? CHARTYPE_NONASCII : fASCIICharMap[ch]; // // [1] ElementSchemeData ::= (NCName ChildSequence?) | ChildSequence // [2] ChildSequence ::= ('/' [1-9] [0-9]*)+ // switch (chartype) { case CHARTYPE_SLASH: // if last character is '/', break and report an error if (++currentOffset == endOffset) { return false; } addToken(tokens, Tokens.XPTRTOKEN_ELEM_CHILD); ch = data.charAt(currentOffset); // ChildSequence ::= ('/' [1-9] [0-9]*)+ int child = 0; while (ch >= '0' && ch <= '9') { child = (child * 10) + (ch - '0'); if (++currentOffset == endOffset) { break; } ch = data.charAt(currentOffset); } // An invalid child sequence character if (child == 0) { reportError("InvalidChildSequenceCharacter", new Object[] { new Character((char) ch) }); return false; } tokens.addToken(child); break; case CHARTYPE_DIGIT: case CHARTYPE_LETTER: case CHARTYPE_MINUS: case CHARTYPE_NONASCII: case CHARTYPE_OTHER: case CHARTYPE_PERIOD: case CHARTYPE_UNDERSCORE: // Scan the ShortHand Pointer NCName nameOffset = currentOffset; currentOffset = scanNCName(data, endOffset, currentOffset); if (currentOffset == nameOffset) { //return false; reportError("InvalidNCNameInElementSchemeData", new Object[] { data }); return false; } if (currentOffset < endOffset) { ch = data.charAt(currentOffset); } else { ch = -1; } nameHandle = symbolTable.addSymbol(data.substring( nameOffset, currentOffset)); addToken(tokens, Tokens.XPTRTOKEN_ELEM_NCNAME); tokens.addToken(nameHandle); break; } } return true; } /** * Scans a NCName. * From Namespaces in XML * [5] NCName ::= (Letter | '_') (NCNameChar)* * [6] NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender * * @param data A String containing the XPointer expression * @param endOffset The int XPointer expression length * @param currentOffset An int representing the current position of the XPointer expression pointer */ private int scanNCName(String data, int endOffset, int currentOffset) { int ch = data.charAt(currentOffset); if (ch >= 0x80) { if (!XMLChar.isNameStart(ch)) { return currentOffset; } } else { byte chartype = fASCIICharMap[ch]; if (chartype != CHARTYPE_LETTER && chartype != CHARTYPE_UNDERSCORE) { return currentOffset; } } while (++currentOffset < endOffset) { ch = data.charAt(currentOffset); if (ch >= 0x80) { if (!XMLChar.isName(ch)) { break; } } else { byte chartype = fASCIICharMap[ch]; if (chartype != CHARTYPE_LETTER && chartype != CHARTYPE_DIGIT && chartype != CHARTYPE_PERIOD && chartype != CHARTYPE_MINUS && chartype != CHARTYPE_UNDERSCORE) { break; } } } return currentOffset; } // // Protected methods // /** * This method adds the specified token to the token list. By * default, this method allows all tokens. However, subclasses * of the XPathExprScanner can override this method in order * to disallow certain tokens from being used in the scanned * XPath expression. This is a convenient way of allowing only * a subset of XPath. */ protected void addToken(Tokens tokens, int token) throws XNIException { tokens.addToken(token); } // addToken(int) } // class Scanner }