You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/09/28 03:55:35 UTC

svn commit: r819444 [27/27] - in /struts/struts2/trunk/plugins/embeddedjsp: ./ src/main/java/org/apache/struts2/el/ src/main/java/org/apache/struts2/el/lang/ src/main/java/org/apache/struts2/el/parser/ src/main/java/org/apache/struts2/el/util/ src/main...

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLEncodingDetector.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,1637 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.Locale;
+import java.util.jar.JarFile;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.compiler.ErrorDispatcher;
+import org.apache.struts2.jasper.compiler.JspUtil;
+
+public class XMLEncodingDetector {
+    
+    private InputStream stream;
+    private String encoding;
+    private boolean isEncodingSetInProlog;
+    private boolean isBomPresent;
+    private int skip;
+    private Boolean isBigEndian;
+    private Reader reader;
+    
+    // org.apache.xerces.impl.XMLEntityManager fields
+    public static final int DEFAULT_BUFFER_SIZE = 2048;
+    public static final int DEFAULT_XMLDECL_BUFFER_SIZE = 64;
+    private boolean fAllowJavaEncodings;
+    private SymbolTable fSymbolTable;
+    private XMLEncodingDetector fCurrentEntity;
+    private int fBufferSize = DEFAULT_BUFFER_SIZE;
+    
+    // org.apache.xerces.impl.XMLEntityManager.ScannedEntity fields
+    private int lineNumber = 1;
+    private int columnNumber = 1;
+    private boolean literal;
+    private char[] ch = new char[DEFAULT_BUFFER_SIZE];
+    private int position;
+    private int count;
+    private boolean mayReadChunks = false;
+    
+    // org.apache.xerces.impl.XMLScanner fields
+    private XMLString fString = new XMLString();    
+    private XMLStringBuffer fStringBuffer = new XMLStringBuffer();
+    private XMLStringBuffer fStringBuffer2 = new XMLStringBuffer();
+    private final static String fVersionSymbol = "version";
+    private final static String fEncodingSymbol = "encoding";
+    private final static String fStandaloneSymbol = "standalone";
+    
+    // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl fields
+    private int fMarkupDepth = 0;
+    private String[] fStrings = new String[3];
+
+    private ErrorDispatcher err;
+
+    /**
+     * Constructor
+     */
+    public XMLEncodingDetector() {
+        fSymbolTable = new SymbolTable();
+        fCurrentEntity = this;
+    }
+
+    /**
+     * Autodetects the encoding of the XML document supplied by the given
+     * input stream.
+     *
+     * Encoding autodetection is done according to the XML 1.0 specification,
+     * Appendix F.1: Detection Without External Encoding Information.
+     *
+     * @return Two-element array, where the first element (of type
+     * java.lang.String) contains the name of the (auto)detected encoding, and
+     * the second element (of type java.lang.Boolean) specifies whether the 
+     * encoding was specified using the 'encoding' attribute of an XML prolog
+     * (TRUE) or autodetected (FALSE).
+     */
+    public static Object[] getEncoding(String fname, JarFile jarFile,
+                                       JspCompilationContext ctxt,
+                                       ErrorDispatcher err)
+        throws IOException, JasperException
+    {
+        InputStream inStream = JspUtil.getInputStream(fname, jarFile, ctxt,
+                                                      err);
+        XMLEncodingDetector detector = new XMLEncodingDetector();
+        Object[] ret = detector.getEncoding(inStream, err);
+        inStream.close();
+
+        return ret;
+    }
+
+    private Object[] getEncoding(InputStream in, ErrorDispatcher err)
+        throws IOException, JasperException
+    {
+        this.stream = in;
+        this.err=err;
+        createInitialReader();
+        scanXMLDecl();
+	
+        return new Object[] { this.encoding,
+                              Boolean.valueOf(this.isEncodingSetInProlog),
+                              Boolean.valueOf(this.isBomPresent),
+                              Integer.valueOf(this.skip) };
+    }
+    
+    // stub method
+    void endEntity() {
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.startEntity()
+    private void createInitialReader() throws IOException, JasperException {
+
+	// wrap this stream in RewindableInputStream
+	stream = new RewindableInputStream(stream);
+
+	// perform auto-detect of encoding if necessary
+	if (encoding == null) {
+	    // read first four bytes and determine encoding
+	    final byte[] b4 = new byte[4];
+	    int count = 0;
+	    for (; count<4; count++ ) {
+		b4[count] = (byte)stream.read();
+	    }
+	    if (count == 4) {
+		Object [] encodingDesc = getEncodingName(b4, count);
+		encoding = (String)(encodingDesc[0]);
+		isBigEndian = (Boolean)(encodingDesc[1]);
+        
+        if (encodingDesc.length > 3) {
+            isBomPresent = (Boolean)(encodingDesc[2]);
+            skip = (Integer)(encodingDesc[3]);
+        } else {
+            isBomPresent = true;
+            skip = (Integer)(encodingDesc[2]);
+        }
+
+		stream.reset();
+		// Special case UTF-8 files with BOM created by Microsoft
+		// tools. It's more efficient to consume the BOM than make
+		// the reader perform extra checks. -Ac
+		if (count > 2 && encoding.equals("UTF-8")) {
+		    int b0 = b4[0] & 0xFF;
+		    int b1 = b4[1] & 0xFF;
+		    int b2 = b4[2] & 0xFF;
+		    if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+			// ignore first three bytes...
+			stream.skip(3);
+		    }
+		}
+		reader = createReader(stream, encoding, isBigEndian);
+	    } else {
+		reader = createReader(stream, encoding, isBigEndian);
+	    }
+	}
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.createReader
+    /**
+     * Creates a reader capable of reading the given input stream in
+     * the specified encoding.
+     *
+     * @param inputStream  The input stream.
+     * @param encoding     The encoding name that the input stream is
+     *                     encoded using. If the user has specified that
+     *                     Java encoding names are allowed, then the
+     *                     encoding name may be a Java encoding name;
+     *                     otherwise, it is an ianaEncoding name.
+     * @param isBigEndian   For encodings (like uCS-4), whose names cannot
+     *                      specify a byte order, this tells whether the order
+     *                      is bigEndian. null means unknown or not relevant.
+     *
+     * @return Returns a reader.
+     */
+    private Reader createReader(InputStream inputStream, String encoding,
+				Boolean isBigEndian)
+                throws IOException, JasperException {
+
+        // normalize encoding name
+        if (encoding == null) {
+            encoding = "UTF-8";
+        }
+
+        // try to use an optimized reader
+        String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
+        if (ENCODING.equals("UTF-8")) {
+            return new UTF8Reader(inputStream, fBufferSize);
+        }
+        if (ENCODING.equals("US-ASCII")) {
+            return new ASCIIReader(inputStream, fBufferSize);
+        }
+        if (ENCODING.equals("ISO-10646-UCS-4")) {
+            if (isBigEndian != null) {
+                boolean isBE = isBigEndian.booleanValue();
+                if (isBE) {
+                    return new UCSReader(inputStream, UCSReader.UCS4BE);
+                } else {
+                    return new UCSReader(inputStream, UCSReader.UCS4LE);
+                }
+            } else {
+                err.jspError("jsp.error.xml.encodingByteOrderUnsupported",
+			     encoding);
+            }
+        }
+        if (ENCODING.equals("ISO-10646-UCS-2")) {
+            if (isBigEndian != null) { // sould never happen with this encoding...
+                boolean isBE = isBigEndian.booleanValue();
+                if (isBE) {
+                    return new UCSReader(inputStream, UCSReader.UCS2BE);
+                } else {
+                    return new UCSReader(inputStream, UCSReader.UCS2LE);
+                }
+            } else {
+                err.jspError("jsp.error.xml.encodingByteOrderUnsupported",
+			     encoding);
+            }
+        }
+
+        // check for valid name
+        boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
+        boolean validJava = XMLChar.isValidJavaEncoding(encoding);
+        if (!validIANA || (fAllowJavaEncodings && !validJava)) {
+            err.jspError("jsp.error.xml.encodingDeclInvalid", encoding);
+            // NOTE: AndyH suggested that, on failure, we use ISO Latin 1
+            //       because every byte is a valid ISO Latin 1 character.
+            //       It may not translate correctly but if we failed on
+            //       the encoding anyway, then we're expecting the content
+            //       of the document to be bad. This will just prevent an
+            //       invalid UTF-8 sequence to be detected. This is only
+            //       important when continue-after-fatal-error is turned
+            //       on. -Ac
+            encoding = "ISO-8859-1";
+        }
+
+        // try to use a Java reader
+        String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
+        if (javaEncoding == null) {
+            if (fAllowJavaEncodings) {
+		javaEncoding = encoding;
+            } else {
+                err.jspError("jsp.error.xml.encodingDeclInvalid", encoding);
+                // see comment above.
+                javaEncoding = "ISO8859_1";
+            }
+        }
+        return new InputStreamReader(inputStream, javaEncoding);
+
+    } // createReader(InputStream,String, Boolean): Reader
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.getEncodingName
+    /**
+     * Returns the IANA encoding name that is auto-detected from
+     * the bytes specified, with the endian-ness of that encoding where
+     * appropriate.
+     *
+     * @param b4    The first four bytes of the input.
+     * @param count The number of bytes actually read.
+     * @return a 2-element array:  the first element, an IANA-encoding string,
+     *  the second element a Boolean which is true iff the document is big
+     *  endian, false if it's little-endian, and null if the distinction isn't
+     *  relevant.
+     */
+    private Object[] getEncodingName(byte[] b4, int count) {
+
+        if (count < 2) {
+            return new Object[]{"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+        }
+
+        // UTF-16, with BOM
+        int b0 = b4[0] & 0xFF;
+        int b1 = b4[1] & 0xFF;
+        if (b0 == 0xFE && b1 == 0xFF) {
+            // UTF-16, big-endian
+            return new Object [] {"UTF-16BE", Boolean.TRUE, Integer.valueOf(2)};
+        }
+        if (b0 == 0xFF && b1 == 0xFE) {
+            // UTF-16, little-endian
+            return new Object [] {"UTF-16LE", Boolean.FALSE, Integer.valueOf(2)};
+        }
+
+        // default to UTF-8 if we don't have enough bytes to make a
+        // good determination of the encoding
+        if (count < 3) {
+            return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+        }
+
+        // UTF-8 with a BOM
+        int b2 = b4[2] & 0xFF;
+        if (b0 == 0xEF && b1 == 0xBB && b2 == 0xBF) {
+            return new Object [] {"UTF-8", null, Integer.valueOf(3)};
+        }
+
+        // default to UTF-8 if we don't have enough bytes to make a
+        // good determination of the encoding
+        if (count < 4) {
+            return new Object [] {"UTF-8", null, Integer.valueOf(0)};
+        }
+
+        // other encodings
+        int b3 = b4[3] & 0xFF;
+        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x00 && b3 == 0x3C) {
+            // UCS-4, big endian (1234)
+            return new Object [] {"ISO-10646-UCS-4", new Boolean(true), Integer.valueOf(4)};
+        }
+        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x00 && b3 == 0x00) {
+            // UCS-4, little endian (4321)
+            return new Object [] {"ISO-10646-UCS-4", new Boolean(false), Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x00 && b2 == 0x3C && b3 == 0x00) {
+            // UCS-4, unusual octet order (2143)
+            // REVISIT: What should this be?
+            return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x00) {
+            // UCS-4, unusual octect order (3412)
+            // REVISIT: What should this be?
+            return new Object [] {"ISO-10646-UCS-4", null, Integer.valueOf(4)};
+        }
+        if (b0 == 0x00 && b1 == 0x3C && b2 == 0x00 && b3 == 0x3F) {
+            // UTF-16, big-endian, no BOM
+            // (or could turn out to be UCS-2...
+            // REVISIT: What should this be?
+            return new Object [] {"UTF-16BE", new Boolean(true), Integer.valueOf(4)};
+        }
+        if (b0 == 0x3C && b1 == 0x00 && b2 == 0x3F && b3 == 0x00) {
+            // UTF-16, little-endian, no BOM
+            // (or could turn out to be UCS-2...
+            return new Object [] {"UTF-16LE", new Boolean(false), Integer.valueOf(4)};
+        }
+        if (b0 == 0x4C && b1 == 0x6F && b2 == 0xA7 && b3 == 0x94) {
+            // EBCDIC
+            // a la xerces1, return CP037 instead of EBCDIC here
+            return new Object [] {"CP037", null, Integer.valueOf(4)};
+        }
+
+        // default encoding
+        return new Object [] {"UTF-8", null, Boolean.FALSE, Integer.valueOf(0)};
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.isExternal
+    /** Returns true if the current entity being scanned is external. */
+    public boolean isExternal() {
+	return true;
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.peekChar
+    /**
+     * Returns the next character on the input.
+     * <p>
+     * <strong>Note:</strong> The character is <em>not</em> consumed.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int peekChar() throws IOException {
+	
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+	
+	// peek at character
+	int c = fCurrentEntity.ch[fCurrentEntity.position];
+
+	// return peeked character
+	if (fCurrentEntity.isExternal()) {
+	    return c != '\r' ? c : '\n';
+	}
+	else {
+	    return c;
+	}
+	
+    } // peekChar():int
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanChar
+    /**
+     * Returns the next character on the input.
+     * <p>
+     * <strong>Note:</strong> The character is consumed.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int scanChar() throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// scan character
+	int c = fCurrentEntity.ch[fCurrentEntity.position++];
+	boolean external = false;
+	if (c == '\n' ||
+	    (c == '\r' && (external = fCurrentEntity.isExternal()))) {
+	    fCurrentEntity.lineNumber++;
+	    fCurrentEntity.columnNumber = 1;
+	    if (fCurrentEntity.position == fCurrentEntity.count) {
+		fCurrentEntity.ch[0] = (char)c;
+		load(1, false);
+	    }
+	    if (c == '\r' && external) {
+		if (fCurrentEntity.ch[fCurrentEntity.position++] != '\n') {
+		    fCurrentEntity.position--;
+		}
+		c = '\n';
+	    }
+	}
+
+	// return character that was scanned
+	fCurrentEntity.columnNumber++;
+	return c;
+	
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanName
+    /**
+     * Returns a string matching the Name production appearing immediately
+     * on the input as a symbol, or null if no Name string is present.
+     * <p>
+     * <strong>Note:</strong> The Name characters are consumed.
+     * <p>
+     * <strong>Note:</strong> The string returned must be a symbol. The
+     * SymbolTable can be used for this purpose.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     *
+     * @see SymbolTable
+     * @see XMLChar#isName
+     * @see XMLChar#isNameStart
+     */
+    public String scanName() throws IOException {
+	
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+	
+	// scan name
+	int offset = fCurrentEntity.position;
+	if (XMLChar.isNameStart(fCurrentEntity.ch[offset])) {
+	    if (++fCurrentEntity.position == fCurrentEntity.count) {
+		fCurrentEntity.ch[0] = fCurrentEntity.ch[offset];
+		offset = 0;
+		if (load(1, false)) {
+		    fCurrentEntity.columnNumber++;
+		    String symbol = fSymbolTable.addSymbol(fCurrentEntity.ch,
+							   0, 1);
+		    return symbol;
+		}
+	    }
+	    while (XMLChar.isName(fCurrentEntity.ch[fCurrentEntity.position])) {
+		if (++fCurrentEntity.position == fCurrentEntity.count) {
+		    int length = fCurrentEntity.position - offset;
+		    if (length == fBufferSize) {
+			// bad luck we have to resize our buffer
+			char[] tmp = new char[fBufferSize * 2];
+			System.arraycopy(fCurrentEntity.ch, offset,
+					 tmp, 0, length);
+			fCurrentEntity.ch = tmp;
+			fBufferSize *= 2;
+		    } else {
+			System.arraycopy(fCurrentEntity.ch, offset,
+					 fCurrentEntity.ch, 0, length);
+		    }
+		    offset = 0;
+		    if (load(length, false)) {
+			break;
+		    }
+		}
+	    }
+	}
+	int length = fCurrentEntity.position - offset;
+	fCurrentEntity.columnNumber += length;
+
+	// return name
+	String symbol = null;
+	if (length > 0) {
+	    symbol = fSymbolTable.addSymbol(fCurrentEntity.ch, offset, length);
+	}
+	return symbol;
+	
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.scanLiteral
+    /**
+     * Scans a range of attribute value data, setting the fields of the
+     * XMLString structure, appropriately.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed.
+     * <p>
+     * <strong>Note:</strong> This method does not guarantee to return
+     * the longest run of attribute value data. This method may return
+     * before the quote character due to reaching the end of the input
+     * buffer or any other reason.
+     * <p>
+     * <strong>Note:</strong> The fields contained in the XMLString
+     * structure are not guaranteed to remain valid upon subsequent calls
+     * to the entity scanner. Therefore, the caller is responsible for
+     * immediately using the returned character data or making a copy of
+     * the character data.
+     *
+     * @param quote   The quote character that signifies the end of the
+     *                attribute value data.
+     * @param content The content structure to fill.
+     *
+     * @return Returns the next character on the input, if known. This
+     *         value may be -1 but this does <em>note</em> designate
+     *         end of file.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public int scanLiteral(int quote, XMLString content)
+	throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	} else if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+	    fCurrentEntity.ch[0] = fCurrentEntity.ch[fCurrentEntity.count - 1];
+	    load(1, false);
+	    fCurrentEntity.position = 0;
+	}
+
+	// normalize newlines
+	int offset = fCurrentEntity.position;
+	int c = fCurrentEntity.ch[offset];
+	int newlines = 0;
+	boolean external = fCurrentEntity.isExternal();
+	if (c == '\n' || (c == '\r' && external)) {
+	    do {
+		c = fCurrentEntity.ch[fCurrentEntity.position++];
+		if (c == '\r' && external) {
+		    newlines++;
+		    fCurrentEntity.lineNumber++;
+		    fCurrentEntity.columnNumber = 1;
+		    if (fCurrentEntity.position == fCurrentEntity.count) {
+			offset = 0;
+			fCurrentEntity.position = newlines;
+			if (load(newlines, false)) {
+			    break;
+			}
+		    }
+		    if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+			fCurrentEntity.position++;
+			offset++;
+		    }
+		    /*** NEWLINE NORMALIZATION ***/
+		    else {
+			newlines++;
+		    }
+		    /***/
+		}
+		else if (c == '\n') {
+		    newlines++;
+		    fCurrentEntity.lineNumber++;
+		    fCurrentEntity.columnNumber = 1;
+		    if (fCurrentEntity.position == fCurrentEntity.count) {
+			offset = 0;
+			fCurrentEntity.position = newlines;
+			if (load(newlines, false)) {
+			    break;
+			}
+		    }
+		    /*** NEWLINE NORMALIZATION ***
+			 if (fCurrentEntity.ch[fCurrentEntity.position] == '\r'
+			 && external) {
+			 fCurrentEntity.position++;
+			 offset++;
+			 }
+			 /***/
+		}
+		else {
+		    fCurrentEntity.position--;
+		    break;
+		}
+	    } while (fCurrentEntity.position < fCurrentEntity.count - 1);
+	    for (int i = offset; i < fCurrentEntity.position; i++) {
+		fCurrentEntity.ch[i] = '\n';
+	    }
+	    int length = fCurrentEntity.position - offset;
+	    if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+		content.setValues(fCurrentEntity.ch, offset, length);
+		return -1;
+	    }
+	}
+
+	// scan literal value
+	while (fCurrentEntity.position < fCurrentEntity.count) {
+	    c = fCurrentEntity.ch[fCurrentEntity.position++];
+	    if ((c == quote &&
+		 (!fCurrentEntity.literal || external))
+		|| c == '%' || !XMLChar.isContent(c)) {
+		fCurrentEntity.position--;
+		break;
+	    }
+	}
+	int length = fCurrentEntity.position - offset;
+	fCurrentEntity.columnNumber += length - newlines;
+	content.setValues(fCurrentEntity.ch, offset, length);
+
+	// return next character
+	if (fCurrentEntity.position != fCurrentEntity.count) {
+	    c = fCurrentEntity.ch[fCurrentEntity.position];
+	    // NOTE: We don't want to accidentally signal the
+	    //       end of the literal if we're expanding an
+	    //       entity appearing in the literal. -Ac
+	    if (c == quote && fCurrentEntity.literal) {
+		c = -1;
+	    }
+	}
+	else {
+	    c = -1;
+	}
+	return c;
+
+    }
+
+    /**
+     * Scans a range of character data up to the specified delimiter,
+     * setting the fields of the XMLString structure, appropriately.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed.
+     * <p>
+     * <strong>Note:</strong> This assumes that the internal buffer is
+     * at least the same size, or bigger, than the length of the delimiter
+     * and that the delimiter contains at least one character.
+     * <p>
+     * <strong>Note:</strong> This method does not guarantee to return
+     * the longest run of character data. This method may return before
+     * the delimiter due to reaching the end of the input buffer or any
+     * other reason.
+     * <p>
+     * <strong>Note:</strong> The fields contained in the XMLString
+     * structure are not guaranteed to remain valid upon subsequent calls
+     * to the entity scanner. Therefore, the caller is responsible for
+     * immediately using the returned character data or making a copy of
+     * the character data.
+     *
+     * @param delimiter The string that signifies the end of the character
+     *                  data to be scanned.
+     * @param buffer    The data structure to fill.
+     *
+     * @return Returns true if there is more data to scan, false otherwise.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean scanData(String delimiter, XMLStringBuffer buffer)
+	throws IOException {
+
+	boolean done = false;
+	int delimLen = delimiter.length();
+	char charAt0 = delimiter.charAt(0);
+	boolean external = fCurrentEntity.isExternal();
+	do {
+    
+	    // load more characters, if needed
+    
+	    if (fCurrentEntity.position == fCurrentEntity.count) {
+		load(0, true);
+	    }
+	    else if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) {
+		System.arraycopy(fCurrentEntity.ch, fCurrentEntity.position,
+				 fCurrentEntity.ch, 0, fCurrentEntity.count - fCurrentEntity.position);
+		load(fCurrentEntity.count - fCurrentEntity.position, false);
+		fCurrentEntity.position = 0;
+	    } 
+	    if (fCurrentEntity.position >= fCurrentEntity.count - delimLen) {
+		// something must be wrong with the input: e.g., file ends an
+		// unterminated comment
+		int length = fCurrentEntity.count - fCurrentEntity.position;
+		buffer.append (fCurrentEntity.ch, fCurrentEntity.position,
+			       length); 
+		fCurrentEntity.columnNumber += fCurrentEntity.count;
+		fCurrentEntity.position = fCurrentEntity.count;
+		load(0,true);
+		return false;
+	    }
+    
+	    // normalize newlines
+	    int offset = fCurrentEntity.position;
+	    int c = fCurrentEntity.ch[offset];
+	    int newlines = 0;
+	    if (c == '\n' || (c == '\r' && external)) {
+		do {
+		    c = fCurrentEntity.ch[fCurrentEntity.position++];
+		    if (c == '\r' && external) {
+			newlines++;
+			fCurrentEntity.lineNumber++;
+			fCurrentEntity.columnNumber = 1;
+			if (fCurrentEntity.position == fCurrentEntity.count) {
+			    offset = 0;
+			    fCurrentEntity.position = newlines;
+			    if (load(newlines, false)) {
+				break;
+			    }
+			}
+			if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+			    fCurrentEntity.position++;
+			    offset++;
+			}
+			/*** NEWLINE NORMALIZATION ***/
+			else {
+			    newlines++;
+			}
+		    }
+		    else if (c == '\n') {
+			newlines++;
+			fCurrentEntity.lineNumber++;
+			fCurrentEntity.columnNumber = 1;
+			if (fCurrentEntity.position == fCurrentEntity.count) {
+			    offset = 0;
+			    fCurrentEntity.position = newlines;
+			    fCurrentEntity.count = newlines;
+			    if (load(newlines, false)) {
+				break;
+			    }
+			}
+		    }
+		    else {
+			fCurrentEntity.position--;
+			break;
+		    }
+		} while (fCurrentEntity.position < fCurrentEntity.count - 1);
+		for (int i = offset; i < fCurrentEntity.position; i++) {
+		    fCurrentEntity.ch[i] = '\n';
+		}
+		int length = fCurrentEntity.position - offset;
+		if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+		    buffer.append(fCurrentEntity.ch, offset, length);
+		    return true;
+		}
+	    }
+    
+	    // iterate over buffer looking for delimiter
+	OUTER: while (fCurrentEntity.position < fCurrentEntity.count) {
+	    c = fCurrentEntity.ch[fCurrentEntity.position++];
+	    if (c == charAt0) {
+		// looks like we just hit the delimiter
+		int delimOffset = fCurrentEntity.position - 1;
+		for (int i = 1; i < delimLen; i++) {
+		    if (fCurrentEntity.position == fCurrentEntity.count) {
+			fCurrentEntity.position -= i;
+			break OUTER;
+		    }
+		    c = fCurrentEntity.ch[fCurrentEntity.position++];
+		    if (delimiter.charAt(i) != c) {
+			fCurrentEntity.position--;
+			break;
+		    }
+		}
+		if (fCurrentEntity.position == delimOffset + delimLen) {
+		    done = true;
+		    break;
+		}
+	    }
+	    else if (c == '\n' || (external && c == '\r')) {
+		fCurrentEntity.position--;
+		break;
+	    }
+	    else if (XMLChar.isInvalid(c)) {
+		fCurrentEntity.position--;
+		int length = fCurrentEntity.position - offset;
+		fCurrentEntity.columnNumber += length - newlines;
+		buffer.append(fCurrentEntity.ch, offset, length); 
+		return true;
+	    }
+	}
+	    int length = fCurrentEntity.position - offset;
+	    fCurrentEntity.columnNumber += length - newlines;
+	    if (done) {
+		length -= delimLen;
+	    }
+	    buffer.append (fCurrentEntity.ch, offset, length);
+    
+	    // return true if string was skipped
+	} while (!done);
+	return !done;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipChar
+    /**
+     * Skips a character appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The character is consumed only if it matches
+     * the specified character.
+     *
+     * @param c The character to skip.
+     *
+     * @return Returns true if the character was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean skipChar(int c) throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// skip character
+	int cc = fCurrentEntity.ch[fCurrentEntity.position];
+	if (cc == c) {
+	    fCurrentEntity.position++;
+	    if (c == '\n') {
+		fCurrentEntity.lineNumber++;
+		fCurrentEntity.columnNumber = 1;
+	    }
+	    else {
+		fCurrentEntity.columnNumber++;
+	    }
+	    return true;
+	} else if (c == '\n' && cc == '\r' && fCurrentEntity.isExternal()) {
+	    // handle newlines
+	    if (fCurrentEntity.position == fCurrentEntity.count) {
+		fCurrentEntity.ch[0] = (char)cc;
+		load(1, false);
+	    }
+	    fCurrentEntity.position++;
+	    if (fCurrentEntity.ch[fCurrentEntity.position] == '\n') {
+		fCurrentEntity.position++;
+	    }
+	    fCurrentEntity.lineNumber++;
+	    fCurrentEntity.columnNumber = 1;
+	    return true;
+	}
+
+	// character was not skipped
+	return false;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.skipSpaces
+    /**
+     * Skips space characters appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed only if they are
+     * space characters.
+     *
+     * @return Returns true if at least one space character was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     *
+     * @see XMLChar#isSpace
+     */
+    public boolean skipSpaces() throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// skip spaces
+	int c = fCurrentEntity.ch[fCurrentEntity.position];
+	if (XMLChar.isSpace(c)) {
+	    boolean external = fCurrentEntity.isExternal();
+	    do {
+		boolean entityChanged = false;
+		// handle newlines
+		if (c == '\n' || (external && c == '\r')) {
+		    fCurrentEntity.lineNumber++;
+		    fCurrentEntity.columnNumber = 1;
+		    if (fCurrentEntity.position == fCurrentEntity.count - 1) {
+			fCurrentEntity.ch[0] = (char)c;
+			entityChanged = load(1, true);
+			if (!entityChanged)
+                                // the load change the position to be 1,
+                                // need to restore it when entity not changed
+			    fCurrentEntity.position = 0;
+		    }
+		    if (c == '\r' && external) {
+			// REVISIT: Does this need to be updated to fix the
+			//          #x0D ^#x0A newline normalization problem? -Ac
+			if (fCurrentEntity.ch[++fCurrentEntity.position] != '\n') {
+			    fCurrentEntity.position--;
+			}
+		    }
+		    /*** NEWLINE NORMALIZATION ***
+			 else {
+			 if (fCurrentEntity.ch[fCurrentEntity.position + 1] == '\r'
+			 && external) {
+			 fCurrentEntity.position++;
+			 }
+			 }
+			 /***/
+		}
+		else {
+		    fCurrentEntity.columnNumber++;
+		}
+		// load more characters, if needed
+		if (!entityChanged)
+		    fCurrentEntity.position++;
+		if (fCurrentEntity.position == fCurrentEntity.count) {
+		    load(0, true);
+		}
+	    } while (XMLChar.isSpace(c = fCurrentEntity.ch[fCurrentEntity.position]));
+	    return true;
+	}
+
+	// no spaces were found
+	return false;
+
+    }
+
+    /**
+     * Skips the specified string appearing immediately on the input.
+     * <p>
+     * <strong>Note:</strong> The characters are consumed only if they are
+     * space characters.
+     *
+     * @param s The string to skip.
+     *
+     * @return Returns true if the string was skipped.
+     *
+     * @throws IOException  Thrown if i/o error occurs.
+     * @throws EOFException Thrown on end of file.
+     */
+    public boolean skipString(String s) throws IOException {
+
+	// load more characters, if needed
+	if (fCurrentEntity.position == fCurrentEntity.count) {
+	    load(0, true);
+	}
+
+	// skip string
+	final int length = s.length();
+	for (int i = 0; i < length; i++) {
+	    char c = fCurrentEntity.ch[fCurrentEntity.position++];
+	    if (c != s.charAt(i)) {
+		fCurrentEntity.position -= i + 1;
+		return false;
+	    }
+	    if (i < length - 1 && fCurrentEntity.position == fCurrentEntity.count) {
+		System.arraycopy(fCurrentEntity.ch, fCurrentEntity.count - i - 1, fCurrentEntity.ch, 0, i + 1);
+		// REVISIT: Can a string to be skipped cross an
+		//          entity boundary? -Ac
+		if (load(i + 1, false)) {
+		    fCurrentEntity.position -= i + 1;
+		    return false;
+		}
+	    }
+	}
+	fCurrentEntity.columnNumber += length;
+	return true;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.EntityScanner.load
+    /**
+     * Loads a chunk of text.
+     *
+     * @param offset       The offset into the character buffer to
+     *                     read the next batch of characters.
+     * @param changeEntity True if the load should change entities
+     *                     at the end of the entity, otherwise leave
+     *                     the current entity in place and the entity
+     *                     boundary will be signaled by the return
+     *                     value.
+     *
+     * @returns Returns true if the entity changed as a result of this
+     *          load operation.
+     */
+    final boolean load(int offset, boolean changeEntity)
+	throws IOException {
+
+	// read characters
+	int length = fCurrentEntity.mayReadChunks?
+	    (fCurrentEntity.ch.length - offset):
+	    (DEFAULT_XMLDECL_BUFFER_SIZE);
+	int count = fCurrentEntity.reader.read(fCurrentEntity.ch, offset,
+					       length);
+
+	// reset count and position
+	boolean entityChanged = false;
+	if (count != -1) {
+	    if (count != 0) {
+		fCurrentEntity.count = count + offset;
+		fCurrentEntity.position = offset;
+	    }
+	}
+
+	// end of this entity
+	else {
+	    fCurrentEntity.count = offset;
+	    fCurrentEntity.position = offset;
+	    entityChanged = true;
+	    if (changeEntity) {
+		endEntity();
+		if (fCurrentEntity == null) {
+		    throw new EOFException();
+		}
+		// handle the trailing edges
+		if (fCurrentEntity.position == fCurrentEntity.count) {
+		    load(0, false);
+		}
+	    }
+	}
+
+	return entityChanged;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLEntityManager.RewindableInputStream
+    /**
+     * This class wraps the byte inputstreams we're presented with.
+     * We need it because java.io.InputStreams don't provide
+     * functionality to reread processed bytes, and they have a habit
+     * of reading more than one character when you call their read()
+     * methods.  This means that, once we discover the true (declared)
+     * encoding of a document, we can neither backtrack to read the
+     * whole doc again nor start reading where we are with a new
+     * reader.
+     *
+     * This class allows rewinding an inputStream by allowing a mark
+     * to be set, and the stream reset to that position.  <strong>The
+     * class assumes that it needs to read one character per
+     * invocation when it's read() method is inovked, but uses the
+     * underlying InputStream's read(char[], offset length) method--it
+     * won't buffer data read this way!</strong>
+     *
+     * @author Neil Graham, IBM
+     * @author Glenn Marcy, IBM
+     */
+    private final class RewindableInputStream extends InputStream {
+
+        private InputStream fInputStream;
+        private byte[] fData;
+        private int fStartOffset;
+        private int fEndOffset;
+        private int fOffset;
+        private int fLength;
+        private int fMark;
+
+        public RewindableInputStream(InputStream is) {
+            fData = new byte[DEFAULT_XMLDECL_BUFFER_SIZE];
+            fInputStream = is;
+            fStartOffset = 0;
+            fEndOffset = -1;
+            fOffset = 0;
+            fLength = 0;
+            fMark = 0;
+        }
+
+        public void setStartOffset(int offset) {
+            fStartOffset = offset;
+        }
+
+        public void rewind() {
+            fOffset = fStartOffset;
+        }
+
+        public int read() throws IOException {
+            int b = 0;
+            if (fOffset < fLength) {
+                return fData[fOffset++] & 0xff;
+            }
+            if (fOffset == fEndOffset) {
+                return -1;
+            }
+            if (fOffset == fData.length) {
+                byte[] newData = new byte[fOffset << 1];
+                System.arraycopy(fData, 0, newData, 0, fOffset);
+                fData = newData;
+            }
+            b = fInputStream.read();
+            if (b == -1) {
+                fEndOffset = fOffset;
+                return -1;
+            }
+            fData[fLength++] = (byte)b;
+            fOffset++;
+            return b & 0xff;
+        }
+
+        public int read(byte[] b, int off, int len) throws IOException {
+            int bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return -1;
+                }
+                // better get some more for the voracious reader...
+                if (fCurrentEntity.mayReadChunks) {
+                    return fInputStream.read(b, off, len);
+                }
+                int returnedVal = read();
+                if (returnedVal == -1) {
+                    fEndOffset = fOffset;
+                    return -1;
+                }
+                b[off] = (byte)returnedVal;
+                return 1;
+            }
+            if (len < bytesLeft) {
+                if (len <= 0) {
+                    return 0;
+                }
+            }
+            else {
+                len = bytesLeft;
+            }
+            if (b != null) {
+                System.arraycopy(fData, fOffset, b, off, len);
+            }
+            fOffset += len;
+            return len;
+        }
+
+        public long skip(long n)
+            throws IOException
+        {
+            int bytesLeft;
+            if (n <= 0) {
+                return 0;
+            }
+            bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return 0;
+                }
+                return fInputStream.skip(n);
+            }
+            if (n <= bytesLeft) {
+                fOffset += n;
+                return n;
+            }
+            fOffset += bytesLeft;
+            if (fOffset == fEndOffset) {
+                return bytesLeft;
+            }
+            n -= bytesLeft;
+	    /*
+	     * In a manner of speaking, when this class isn't permitting more
+	     * than one byte at a time to be read, it is "blocking".  The
+	     * available() method should indicate how much can be read without
+	     * blocking, so while we're in this mode, it should only indicate
+	     * that bytes in its buffer are available; otherwise, the result of
+	     * available() on the underlying InputStream is appropriate.
+	     */
+            return fInputStream.skip(n) + bytesLeft;
+        }
+
+        public int available() throws IOException {
+            int bytesLeft = fLength - fOffset;
+            if (bytesLeft == 0) {
+                if (fOffset == fEndOffset) {
+                    return -1;
+                }
+                return fCurrentEntity.mayReadChunks ? fInputStream.available()
+		    : 0;
+            }
+            return bytesLeft;
+        }
+
+        public void mark(int howMuch) {
+            fMark = fOffset;
+        }
+
+        public void reset() {
+            fOffset = fMark;
+        }
+
+        public boolean markSupported() {
+            return true;
+        }
+
+        public void close() throws IOException {
+            if (fInputStream != null) {
+                fInputStream.close();
+                fInputStream = null;
+            }
+        }
+    } // end of RewindableInputStream class
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLDocumentScannerImpl.dispatch
+    private void scanXMLDecl() throws IOException, JasperException {
+
+	if (skipString("<?xml")) {
+	    fMarkupDepth++;
+	    // NOTE: special case where document starts with a PI
+	    //       whose name starts with "xml" (e.g. "xmlfoo")
+	    if (XMLChar.isName(peekChar())) {
+		fStringBuffer.clear();
+		fStringBuffer.append("xml");
+		while (XMLChar.isName(peekChar())) {
+		    fStringBuffer.append((char)scanChar());
+		}
+		String target = fSymbolTable.addSymbol(fStringBuffer.ch,
+						       fStringBuffer.offset,
+						       fStringBuffer.length);
+		scanPIData(target, fString);
+	    }
+
+	    // standard XML declaration
+	    else {
+		scanXMLDeclOrTextDecl(false);
+	    }
+	}
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanXMLDeclOrTextDecl
+    /**
+     * Scans an XML or text declaration.
+     * <p>
+     * <pre>
+     * [23] XMLDecl ::= '&lt;?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '&lt;?xml' VersionInfo? EncodingDecl S? '?>'
+     * </pre>
+     *
+     * @param scanningTextDecl True if a text declaration is to
+     *                         be scanned instead of an XML
+     *                         declaration.
+     */
+    private void scanXMLDeclOrTextDecl(boolean scanningTextDecl) 
+        throws IOException, JasperException {
+
+        // scan decl
+        scanXMLDeclOrTextDecl(scanningTextDecl, fStrings);
+        fMarkupDepth--;
+
+        // pseudo-attribute values
+        String encodingPseudoAttr = fStrings[1];
+
+        // set encoding on reader
+        if (encodingPseudoAttr != null) {
+            isEncodingSetInProlog = true;
+	    encoding = encodingPseudoAttr;
+        }
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanXMLDeclOrTextDecl
+    /**
+     * Scans an XML or text declaration.
+     * <p>
+     * <pre>
+     * [23] XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
+     * [24] VersionInfo ::= S 'version' Eq (' VersionNum ' | " VersionNum ")
+     * [80] EncodingDecl ::= S 'encoding' Eq ('"' EncName '"' |  "'" EncName "'" )
+     * [81] EncName ::= [A-Za-z] ([A-Za-z0-9._] | '-')*
+     * [32] SDDecl ::= S 'standalone' Eq (("'" ('yes' | 'no') "'")
+     *                 | ('"' ('yes' | 'no') '"'))
+     *
+     * [77] TextDecl ::= '<?xml' VersionInfo? EncodingDecl S? '?>'
+     * </pre>
+     *
+     * @param scanningTextDecl True if a text declaration is to
+     *                         be scanned instead of an XML
+     *                         declaration.
+     * @param pseudoAttributeValues An array of size 3 to return the version,
+     *                         encoding and standalone pseudo attribute values
+     *                         (in that order).
+     *
+     * <strong>Note:</strong> This method uses fString, anything in it
+     * at the time of calling is lost.
+     */
+    private void scanXMLDeclOrTextDecl(boolean scanningTextDecl,
+				       String[] pseudoAttributeValues) 
+                throws IOException, JasperException {
+
+        // pseudo-attribute values
+        String version = null;
+        String encoding = null;
+        String standalone = null;
+
+        // scan pseudo-attributes
+        final int STATE_VERSION = 0;
+        final int STATE_ENCODING = 1;
+        final int STATE_STANDALONE = 2;
+        final int STATE_DONE = 3;
+        int state = STATE_VERSION;
+
+        boolean dataFoundForTarget = false;
+        boolean sawSpace = skipSpaces();
+        while (peekChar() != '?') {
+            dataFoundForTarget = true;
+            String name = scanPseudoAttribute(scanningTextDecl, fString);
+            switch (state) {
+                case STATE_VERSION: {
+                    if (name == fVersionSymbol) {
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                       ? "jsp.error.xml.spaceRequiredBeforeVersionInTextDecl"
+                                       : "jsp.error.xml.spaceRequiredBeforeVersionInXMLDecl",
+                                             null);
+                        }
+                        version = fString.toString();
+                        state = STATE_ENCODING;
+                        if (!version.equals("1.0")) {
+                            // REVISIT: XML REC says we should throw an error
+			    // in such cases.
+                            // some may object the throwing of fatalError.
+                            err.jspError("jsp.error.xml.versionNotSupported",
+					 version);
+                        }
+                    } else if (name == fEncodingSymbol) {
+                        if (!scanningTextDecl) {
+                            err.jspError("jsp.error.xml.versionInfoRequired");
+                        }
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                      ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl"
+                                      : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl",
+                                             null);
+                        }
+                        encoding = fString.toString();
+                        state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE;
+                    } else {
+                        if (scanningTextDecl) {
+                            err.jspError("jsp.error.xml.encodingDeclRequired");
+                        }
+                        else {
+                            err.jspError("jsp.error.xml.versionInfoRequired");
+                        }
+                    }
+                    break;
+                }
+                case STATE_ENCODING: {
+                    if (name == fEncodingSymbol) {
+                        if (!sawSpace) {
+                            reportFatalError(scanningTextDecl
+                                      ? "jsp.error.xml.spaceRequiredBeforeEncodingInTextDecl"
+                                      : "jsp.error.xml.spaceRequiredBeforeEncodingInXMLDecl",
+                                             null);
+                        }
+                        encoding = fString.toString();
+                        state = scanningTextDecl ? STATE_DONE : STATE_STANDALONE;
+                        // TODO: check encoding name; set encoding on
+                        //       entity scanner
+                    } else if (!scanningTextDecl && name == fStandaloneSymbol) {
+                        if (!sawSpace) {
+                            err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone");
+                        }
+                        standalone = fString.toString();
+                        state = STATE_DONE;
+                        if (!standalone.equals("yes") && !standalone.equals("no")) {
+                            err.jspError("jsp.error.xml.sdDeclInvalid");
+                        }
+                    } else {
+                        err.jspError("jsp.error.xml.encodingDeclRequired");
+                    }
+                    break;
+                }
+                case STATE_STANDALONE: {
+                    if (name == fStandaloneSymbol) {
+                        if (!sawSpace) {
+                            err.jspError("jsp.error.xml.spaceRequiredBeforeStandalone");
+                        }
+                        standalone = fString.toString();
+                        state = STATE_DONE;
+                        if (!standalone.equals("yes") && !standalone.equals("no")) {
+                            err.jspError("jsp.error.xml.sdDeclInvalid");
+                        }
+                    } else {
+			err.jspError("jsp.error.xml.encodingDeclRequired");
+                    }
+                    break;
+                }
+                default: {
+                    err.jspError("jsp.error.xml.noMorePseudoAttributes");
+                }
+            }
+            sawSpace = skipSpaces();
+        }
+        // REVISIT: should we remove this error reporting?
+        if (scanningTextDecl && state != STATE_DONE) {
+            err.jspError("jsp.error.xml.morePseudoAttributes");
+        }
+        
+        // If there is no data in the xml or text decl then we fail to report
+	// error for version or encoding info above.
+        if (scanningTextDecl) {
+            if (!dataFoundForTarget && encoding == null) {
+                err.jspError("jsp.error.xml.encodingDeclRequired");
+            }
+        } else {
+            if (!dataFoundForTarget && version == null) {
+                err.jspError("jsp.error.xml.versionInfoRequired");
+            }
+        }
+
+        // end
+        if (!skipChar('?')) {
+            err.jspError("jsp.error.xml.xmlDeclUnterminated");
+        }
+        if (!skipChar('>')) {
+            err.jspError("jsp.error.xml.xmlDeclUnterminated");
+
+        }
+        
+        // fill in return array
+        pseudoAttributeValues[0] = version;
+        pseudoAttributeValues[1] = encoding;
+        pseudoAttributeValues[2] = standalone;
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanPseudoAttribute
+    /**
+     * Scans a pseudo attribute.
+     *
+     * @param scanningTextDecl True if scanning this pseudo-attribute for a
+     *                         TextDecl; false if scanning XMLDecl. This 
+     *                         flag is needed to report the correct type of
+     *                         error.
+     * @param value            The string to fill in with the attribute 
+     *                         value.
+     *
+     * @return The name of the attribute
+     *
+     * <strong>Note:</strong> This method uses fStringBuffer2, anything in it
+     * at the time of calling is lost.
+     */
+    public String scanPseudoAttribute(boolean scanningTextDecl, 
+                                      XMLString value) 
+                throws IOException, JasperException {
+
+        String name = scanName();
+        if (name == null) {
+            err.jspError("jsp.error.xml.pseudoAttrNameExpected");
+        }
+        skipSpaces();
+        if (!skipChar('=')) {
+            reportFatalError(scanningTextDecl ?
+			     "jsp.error.xml.eqRequiredInTextDecl"
+                             : "jsp.error.xml.eqRequiredInXMLDecl",
+			     name);
+        }
+        skipSpaces();
+        int quote = peekChar();
+        if (quote != '\'' && quote != '"') {
+            reportFatalError(scanningTextDecl ?
+			     "jsp.error.xml.quoteRequiredInTextDecl"
+                             : "jsp.error.xml.quoteRequiredInXMLDecl" ,
+			     name);
+        }
+        scanChar();
+        int c = scanLiteral(quote, value);
+        if (c != quote) {
+            fStringBuffer2.clear();
+            do {
+                fStringBuffer2.append(value);
+                if (c != -1) {
+                    if (c == '&' || c == '%' || c == '<' || c == ']') {
+                        fStringBuffer2.append((char)scanChar());
+                    }
+                    else if (XMLChar.isHighSurrogate(c)) {
+                        scanSurrogates(fStringBuffer2);
+                    }
+                    else if (XMLChar.isInvalid(c)) {
+                        String key = scanningTextDecl
+                            ? "jsp.error.xml.invalidCharInTextDecl"
+			    : "jsp.error.xml.invalidCharInXMLDecl";
+                        reportFatalError(key, Integer.toString(c, 16));
+                        scanChar();
+                    }
+                }
+                c = scanLiteral(quote, value);
+            } while (c != quote);
+            fStringBuffer2.append(value);
+            value.setValues(fStringBuffer2);
+        }
+        if (!skipChar(quote)) {
+            reportFatalError(scanningTextDecl ?
+			     "jsp.error.xml.closeQuoteMissingInTextDecl"
+                             : "jsp.error.xml.closeQuoteMissingInXMLDecl",
+			     name);
+        }
+
+        // return
+        return name;
+
+    }
+    
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanPIData
+    /**
+     * Scans a processing data. This is needed to handle the situation
+     * where a document starts with a processing instruction whose 
+     * target name <em>starts with</em> "xml". (e.g. xmlfoo)
+     *
+     * <strong>Note:</strong> This method uses fStringBuffer, anything in it
+     * at the time of calling is lost.
+     *
+     * @param target The PI target
+     * @param data The string to fill in with the data
+     */
+    private void scanPIData(String target, XMLString data) 
+        throws IOException, JasperException {
+
+        // check target
+        if (target.length() == 3) {
+            char c0 = Character.toLowerCase(target.charAt(0));
+            char c1 = Character.toLowerCase(target.charAt(1));
+            char c2 = Character.toLowerCase(target.charAt(2));
+            if (c0 == 'x' && c1 == 'm' && c2 == 'l') {
+                err.jspError("jsp.error.xml.reservedPITarget");
+            }
+        }
+
+        // spaces
+        if (!skipSpaces()) {
+            if (skipString("?>")) {
+                // we found the end, there is no data
+                data.clear();
+                return;
+            }
+            else {
+                // if there is data there should be some space
+                err.jspError("jsp.error.xml.spaceRequiredInPI");
+            }
+        }
+
+        fStringBuffer.clear();
+        // data
+        if (scanData("?>", fStringBuffer)) {
+            do {
+                int c = peekChar();
+                if (c != -1) {
+                    if (XMLChar.isHighSurrogate(c)) {
+                        scanSurrogates(fStringBuffer);
+                    } else if (XMLChar.isInvalid(c)) {
+                        err.jspError("jsp.error.xml.invalidCharInPI",
+				     Integer.toHexString(c));
+                        scanChar();
+                    }
+                }
+            } while (scanData("?>", fStringBuffer));
+        }
+        data.setValues(fStringBuffer);
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.scanSurrogates
+    /**
+     * Scans surrogates and append them to the specified buffer.
+     * <p>
+     * <strong>Note:</strong> This assumes the current char has already been
+     * identified as a high surrogate.
+     *
+     * @param buf The StringBuffer to append the read surrogates to.
+     * @returns True if it succeeded.
+     */
+    private boolean scanSurrogates(XMLStringBuffer buf)
+        throws IOException, JasperException {
+
+        int high = scanChar();
+        int low = peekChar();
+        if (!XMLChar.isLowSurrogate(low)) {
+            err.jspError("jsp.error.xml.invalidCharInContent",
+			 Integer.toString(high, 16));
+            return false;
+        }
+        scanChar();
+
+        // convert surrogates to supplemental character
+        int c = XMLChar.supplemental((char)high, (char)low);
+
+        // supplemental character must be a valid XML character
+        if (!XMLChar.isValid(c)) {
+            err.jspError("jsp.error.xml.invalidCharInContent",
+			 Integer.toString(c, 16)); 
+            return false;
+        }
+
+        // fill in the buffer
+        buf.append((char)high);
+        buf.append((char)low);
+
+        return true;
+
+    }
+
+    // Adapted from:
+    // org.apache.xerces.impl.XMLScanner.reportFatalError
+    /**
+     * Convenience function used in all XML scanners.
+     */
+    private void reportFatalError(String msgId, String arg)
+                throws JasperException {
+        err.jspError(msgId, arg);
+    }
+
+}
+
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLString.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,197 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+/**
+ * This class is used as a structure to pass text contained in the underlying
+ * character buffer of the scanner. The offset and length fields allow the
+ * buffer to be re-used without creating new character arrays.
+ * <p>
+ * <strong>Note:</strong> Methods that are passed an XMLString structure
+ * should consider the contents read-only and not make any modifications
+ * to the contents of the buffer. The method receiving this structure
+ * should also not modify the offset and length if this structure (or
+ * the values of this structure) are passed to another method.
+ * <p>
+ * <strong>Note:</strong> Methods that are passed an XMLString structure
+ * are required to copy the information out of the buffer if it is to be
+ * saved for use beyond the scope of the method. The contents of the 
+ * structure are volatile and the contents of the character buffer cannot
+ * be assured once the method that is passed this structure returns.
+ * Therefore, methods passed this structure should not save any reference
+ * to the structure or the character array contained in the structure.
+ *
+ * @author Eric Ye, IBM
+ * @author Andy Clark, IBM
+ *
+ * @version $Id: XMLString.java 467222 2006-10-24 03:17:11Z markt $
+ */
+public class XMLString {
+
+    //
+    // Data
+    //
+
+    /** The character array. */
+    public char[] ch;
+
+    /** The offset into the character array. */
+    public int offset;
+
+    /** The length of characters from the offset. */
+    public int length;
+
+    //
+    // Constructors
+    //
+
+    /** Default constructor. */
+    public XMLString() {
+    } // <init>()
+
+    /**
+     * Constructs an XMLString structure preset with the specified
+     * values.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public XMLString(char[] ch, int offset, int length) {
+        setValues(ch, offset, length);
+    } // <init>(char[],int,int)
+
+    /**
+     * Constructs an XMLString structure with copies of the values in
+     * the given structure.
+     * <p>
+     * <strong>Note:</strong> This does not copy the character array;
+     * only the reference to the array is copied.
+     *
+     * @param string The XMLString to copy.
+     */
+    public XMLString(XMLString string) {
+        setValues(string);
+    } // <init>(XMLString)
+
+    //
+    // Public methods
+    //
+
+    /**
+     * Initializes the contents of the XMLString structure with the
+     * specified values.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public void setValues(char[] ch, int offset, int length) {
+        this.ch = ch;
+        this.offset = offset;
+        this.length = length;
+    } // setValues(char[],int,int)
+
+    /**
+     * Initializes the contents of the XMLString structure with copies
+     * of the given string structure.
+     * <p>
+     * <strong>Note:</strong> This does not copy the character array;
+     * only the reference to the array is copied.
+     * 
+     * @param s
+     */
+    public void setValues(XMLString s) {
+        setValues(s.ch, s.offset, s.length);
+    } // setValues(XMLString)
+
+    /** Resets all of the values to their defaults. */
+    public void clear() {
+        this.ch = null;
+        this.offset = 0;
+        this.length = -1;
+    } // clear()
+
+    /**
+     * Returns true if the contents of this XMLString structure and
+     * the specified array are equal.
+     * 
+     * @param ch     The character array.
+     * @param offset The offset into the character array.
+     * @param length The length of characters from the offset.
+     */
+    public boolean equals(char[] ch, int offset, int length) {
+        if (ch == null) {
+            return false;
+        }
+        if (this.length != length) {
+            return false;
+        }
+
+        for (int i=0; i<length; i++) {
+            if (this.ch[this.offset+i] != ch[offset+i] ) {
+                return false;
+            }
+        }
+        return true;
+    } // equals(char[],int,int):boolean
+
+    /**
+     * Returns true if the contents of this XMLString structure and
+     * the specified string are equal.
+     * 
+     * @param s The string to compare.
+     */
+    public boolean equals(String s) {
+        if (s == null) {
+            return false;
+        }
+        if ( length != s.length() ) {
+            return false;
+        }
+
+        // is this faster than call s.toCharArray first and compare the 
+        // two arrays directly, which will possibly involve creating a
+        // new char array object.
+        for (int i=0; i<length; i++) {
+            if (ch[offset+i] != s.charAt(i)) {
+                return false;
+            }
+        }
+
+        return true;
+    } // equals(String):boolean
+
+    //
+    // Object methods
+    //
+
+    /** Returns a string representation of this object. */
+    public String toString() {
+        return length > 0 ? new String(ch, offset, length) : "";
+    } // toString():String
+
+} // class XMLString

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/xmlparser/XMLStringBuffer.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,176 @@
+/*
+ * 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.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.apache.org.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ */
+
+package org.apache.struts2.jasper.xmlparser;
+
+/**
+ * XMLString is a structure used to pass character arrays. However,
+ * XMLStringBuffer is a buffer in which characters can be appended
+ * and extends XMLString so that it can be passed to methods
+ * expecting an XMLString object. This is a safe operation because
+ * it is assumed that any callee will <strong>not</strong> modify
+ * the contents of the XMLString structure.
+ * <p> 
+ * The contents of the string are managed by the string buffer. As
+ * characters are appended, the string buffer will grow as needed.
+ * <p>
+ * <strong>Note:</strong> Never set the <code>ch</code>, 
+ * <code>offset</code>, and <code>length</code> fields directly.
+ * These fields are managed by the string buffer. In order to reset
+ * the buffer, call <code>clear()</code>.
+ * 
+ * @author Andy Clark, IBM
+ * @author Eric Ye, IBM
+ *
+ * @version $Id: XMLStringBuffer.java 467222 2006-10-24 03:17:11Z markt $
+ */
+public class XMLStringBuffer
+    extends XMLString {
+
+    //
+    // Constants
+    //
+
+    /** Default buffer size (32). */
+    public static final int DEFAULT_SIZE = 32;
+
+    //
+    // Constructors
+    //
+
+    /**
+     * 
+     */
+    public XMLStringBuffer() {
+        this(DEFAULT_SIZE);
+    } // <init>()
+
+    /**
+     * 
+     * 
+     * @param size 
+     */
+    public XMLStringBuffer(int size) {
+        ch = new char[size];
+    } // <init>(int)
+
+    /** Constructs a string buffer from a char. */
+    public XMLStringBuffer(char c) {
+        this(1);
+        append(c);
+    } // <init>(char)
+
+    /** Constructs a string buffer from a String. */
+    public XMLStringBuffer(String s) {
+        this(s.length());
+        append(s);
+    } // <init>(String)
+
+    /** Constructs a string buffer from the specified character array. */
+    public XMLStringBuffer(char[] ch, int offset, int length) {
+        this(length);
+        append(ch, offset, length);
+    } // <init>(char[],int,int)
+
+    /** Constructs a string buffer from the specified XMLString. */
+    public XMLStringBuffer(XMLString s) {
+        this(s.length);
+        append(s);
+    } // <init>(XMLString)
+
+    //
+    // Public methods
+    //
+
+    /** Clears the string buffer. */
+    public void clear() {
+        offset = 0;
+        length = 0;
+    }
+
+    /**
+     * append
+     * 
+     * @param c 
+     */
+    public void append(char c) {
+        if (this.length + 1 > this.ch.length) {
+                    int newLength = this.ch.length*2;
+                    if (newLength < this.ch.length + DEFAULT_SIZE)
+                        newLength = this.ch.length + DEFAULT_SIZE;
+                    char[] newch = new char[newLength];
+                    System.arraycopy(this.ch, 0, newch, 0, this.length);
+                    this.ch = newch;
+        }
+        this.ch[this.length] = c;
+        this.length++;
+    } // append(char)
+
+    /**
+     * append
+     * 
+     * @param s 
+     */
+    public void append(String s) {
+        int length = s.length();
+        if (this.length + length > this.ch.length) {
+            int newLength = this.ch.length*2;
+            if (newLength < this.length + length + DEFAULT_SIZE)
+                newLength = this.ch.length + length + DEFAULT_SIZE;
+            char[] newch = new char[newLength];            
+            System.arraycopy(this.ch, 0, newch, 0, this.length);
+            this.ch = newch;
+        }
+        s.getChars(0, length, this.ch, this.length);
+        this.length += length;
+    } // append(String)
+
+    /**
+     * append
+     * 
+     * @param ch 
+     * @param offset 
+     * @param length 
+     */
+    public void append(char[] ch, int offset, int length) {
+        if (this.length + length > this.ch.length) {
+            char[] newch = new char[this.ch.length + length + DEFAULT_SIZE];
+            System.arraycopy(this.ch, 0, newch, 0, this.length);
+            this.ch = newch;
+        }
+        System.arraycopy(ch, offset, this.ch, this.length, length);
+        this.length += length;
+    } // append(char[],int,int)
+
+    /**
+     * append
+     * 
+     * @param s 
+     */
+    public void append(XMLString s) {
+        append(s.ch, s.offset, s.length);
+    } // append(XMLString)
+
+} // class XMLStringBuffer