/* Woodstox XML processor
 *
 * Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
 *
 * Licensed under the License specified in file LICENSE, included with
 * the source code.
 * You may not use this file except in compliance with the License.
 *
 * 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 com.ctc.wstx.io;

import java.io.*;
import java.text.MessageFormat;

import javax.xml.stream.Location;
import javax.xml.stream.XMLReporter;
import javax.xml.stream.XMLStreamException;

import org.codehaus.stax2.validation.XMLValidationProblem;

import com.ctc.wstx.api.ReaderConfig;
import com.ctc.wstx.cfg.ErrorConsts;
import com.ctc.wstx.cfg.ParsingErrorMsgs;
import com.ctc.wstx.exc.*;
import com.ctc.wstx.util.StringUtil;

Input bootstrap class used when input comes from a Reader; in this case, encoding is already known, and thus encoding from XML declaration (if any) is only double-checked, not really used.

Note: since the actual Reader to use after bootstrapping is pre-constructed, the local input buffer can (and should) be quite small.

/** * Input bootstrap class used when input comes from a Reader; in this case, * encoding is already known, and thus encoding from XML declaration (if * any) is only double-checked, not really used. *<p> * Note: since the actual Reader to use after bootstrapping is pre-constructed, * the local input buffer can (and should) be quite small. */
public final class ReaderBootstrapper extends InputBootstrapper { final static char CHAR_BOM_MARKER = (char) 0xFEFF; /* //////////////////////////////////////// // Configuration //////////////////////////////////////// */
Underlying Reader to use for reading content.
/** * Underlying Reader to use for reading content. */
final Reader mIn;
Encoding identifier processing application passed in; if not null, will be compared to actual xml declaration based encoding (if declaration found)
/** * Encoding identifier processing application passed in; if not null, * will be compared to actual xml declaration based encoding (if * declaration found) */
final String mInputEncoding; /* /////////////////////////////////////////////////////////////// // Input buffering /////////////////////////////////////////////////////////////// */ private char[] mCharBuffer; private int mInputPtr; private int mInputEnd; /* //////////////////////////////////////// // Life-cycle //////////////////////////////////////// */ private ReaderBootstrapper(String pubId, SystemId sysId, Reader r, String appEncoding) { super(pubId, sysId); mIn = r; if (appEncoding == null) { // may still be able to figure it out if (r instanceof InputStreamReader) { appEncoding = ((InputStreamReader) r).getEncoding(); } } mInputEncoding = appEncoding; } /* //////////////////////////////////////// // Public API //////////////////////////////////////// */
Params:
  • r – Eventual reader that will be reading actual content, after bootstrapping finishes
  • appEncoding – Encoding that application declared; may be null. If not null, will be compared to actual declaration found; and incompatibility reported as a potential (but not necessarily fatal) problem.
/** * @param r Eventual reader that will be reading actual content, after * bootstrapping finishes * @param appEncoding Encoding that application declared; may be null. * If not null, will be compared to actual declaration found; and * incompatibility reported as a potential (but not necessarily fatal) * problem. */
public static ReaderBootstrapper getInstance(String pubId, SystemId sysId, Reader r, String appEncoding) { return new ReaderBootstrapper(pubId, sysId, r, appEncoding); }
Method called to do actual bootstrapping.
Returns:Actual reader to use for reading xml content
/** * Method called to do actual bootstrapping. * * @return Actual reader to use for reading xml content */
@Override public Reader bootstrapInput(ReaderConfig cfg, boolean mainDoc, int xmlVersion) throws IOException, XMLStreamException { /* First order of business: allocate input buffer. Not done during * construction for simplicity; that way config object need not be * passed before actual bootstrap method is called */ /* Let's make sure buffer is at least 6 chars (to know '<?xml ' * prefix), and preferably big enough to contain the whole declaration, * but not too long to waste space -- it won't be reused * by the real input reader. */ mCharBuffer = (cfg == null) ? new char[128] : cfg.allocSmallCBuffer(128); // 128 chars should be enough initialLoad(7); /* Only need 6 for signature ("<?xml\s"), but there may be a leading * BOM in there... and a valid xml declaration has to be longer * than 7 chars anyway (although, granted, shortest valid xml docl * is just 4 chars... "<a/>") */ if (mInputEnd >= 7) { char c = mCharBuffer[mInputPtr]; // BOM to skip? if (c == CHAR_BOM_MARKER) { c = mCharBuffer[++mInputPtr]; } if (c == '<') { if (mCharBuffer[mInputPtr+1] == '?' && mCharBuffer[mInputPtr+2] == 'x' && mCharBuffer[mInputPtr+3] == 'm' && mCharBuffer[mInputPtr+4] == 'l' && mCharBuffer[mInputPtr+5] <= CHAR_SPACE) { // Yup, got the declaration ok! mInputPtr += 6; // skip declaration readXmlDecl(mainDoc, xmlVersion); if (mFoundEncoding != null && mInputEncoding != null) { verifyXmlEncoding(cfg); } } } else { /* We may also get something that would be invalid xml * ("garbage" char; neither '<' nor space). If so, and * it's one of "well-known" cases, we can not only throw * an exception but also indicate a clue as to what is likely * to be wrong. */ /* Specifically, UTF-8 read via, say, ISO-8859-1 reader, can * "leak" marker (0xEF, 0xBB, 0xBF). While we could just eat * it, there's bound to be other problems cropping up, so let's * inform about the problem right away. */ if (c == 0xEF) { throw new WstxIOException("Unexpected first character (char code 0xEF), not valid in xml document: could be mangled UTF-8 BOM marker. Make sure that the Reader uses correct encoding or pass an InputStream instead"); } } } /* Ok, now; do we have unused chars we have read that need to * be merged in? */ if (mInputPtr < mInputEnd) { return new MergedReader(cfg, mIn, mCharBuffer, mInputPtr, mInputEnd); } return mIn; } @Override public String getInputEncoding() { return mInputEncoding; } @Override public int getInputTotal() { return mInputProcessed + mInputPtr; } @Override public int getInputColumn() { return (mInputPtr - mInputRowStart); } /* //////////////////////////////////////// // Internal methods, parsing //////////////////////////////////////// */ protected void verifyXmlEncoding(ReaderConfig cfg) throws XMLStreamException { String inputEnc = mInputEncoding; // Close enough? if (StringUtil.equalEncodings(inputEnc, mFoundEncoding)) { return; } /* Ok, maybe the difference is just with endianness indicator? * (UTF-16BE vs. UTF-16)? */ // !!! TBI XMLReporter rep = cfg.getXMLReporter(); if (rep != null) { Location loc = getLocation(); String msg = MessageFormat.format(ErrorConsts.W_MIXED_ENCODINGS, new Object[] { mFoundEncoding, inputEnc }); String type = ErrorConsts.WT_XML_DECL; /* 30-May-2008, tatus: Should wrap all the info as XMValidationProblem * since that's Woodstox' contract wrt. relatedInformation field. */ XMLValidationProblem prob = new XMLValidationProblem(loc, msg, XMLValidationProblem.SEVERITY_WARNING, type); rep.report(msg, type, prob, loc); } } /* ///////////////////////////////////////////////////// // Internal methods, loading input data ///////////////////////////////////////////////////// */ protected boolean initialLoad(int minimum) throws IOException { mInputPtr = 0; mInputEnd = 0; while (mInputEnd < minimum) { int count = mIn.read(mCharBuffer, mInputEnd, mCharBuffer.length - mInputEnd); if (count < 1) { return false; } mInputEnd += count; } return true; } protected void loadMore() throws IOException, WstxException { /* Need to make sure offsets are properly updated for error * reporting purposes, and do this now while previous amounts * are still known. */ mInputProcessed += mInputEnd; mInputRowStart -= mInputEnd; mInputPtr = 0; mInputEnd = mIn.read(mCharBuffer, 0, mCharBuffer.length); if (mInputEnd < 1) { throw new WstxEOFException(ParsingErrorMsgs.SUFFIX_IN_XML_DECL, getLocation()); } } /* ///////////////////////////////////////////////////// // Implementations of abstract parsing methods ///////////////////////////////////////////////////// */ @Override protected void pushback() { --mInputPtr; } @Override protected int getNext() throws IOException, WstxException { return (mInputPtr < mInputEnd) ? mCharBuffer[mInputPtr++] : nextChar(); } @Override protected int getNextAfterWs(boolean reqWs) throws IOException, WstxException { int count = 0; while (true) { char c = (mInputPtr < mInputEnd) ? mCharBuffer[mInputPtr++] : nextChar(); if (c > CHAR_SPACE) { if (reqWs && count == 0) { reportUnexpectedChar(c, ERR_XMLDECL_EXP_SPACE); } return c; } if (c == CHAR_CR || c == CHAR_LF) { skipCRLF(c); } else if (c == CHAR_NULL) { reportNull(); } ++count; } }
Returns:First character that does not match expected, if any; CHAR_NULL if match succeeded
/** * @return First character that does not match expected, if any; * CHAR_NULL if match succeeded */
@Override protected int checkKeyword(String exp) throws IOException, WstxException { int len = exp.length(); for (int ptr = 1; ptr < len; ++ptr) { char c = (mInputPtr < mInputEnd) ? mCharBuffer[mInputPtr++] : nextChar(); if (c != exp.charAt(ptr)) { return c; } if (c == CHAR_NULL) { reportNull(); } } return CHAR_NULL; } @Override protected int readQuotedValue(char[] kw, int quoteChar) throws IOException, WstxException { int i = 0; int len = kw.length; while (true) { char c = (mInputPtr < mInputEnd) ? mCharBuffer[mInputPtr++] : nextChar(); if (c == CHAR_CR || c == CHAR_LF) { skipCRLF(c); } else if (c == CHAR_NULL) { reportNull(); } if (c == quoteChar) { return (i < len) ? i : -1; } // Let's just truncate longer values, but match quote if (i < len) { kw[i++] = c; } } } @Override protected Location getLocation() { return new WstxInputLocation(null, mPublicId, mSystemId, mInputProcessed + mInputPtr - 1, mInputRow, mInputPtr - mInputRowStart); } /* ///////////////////////////////////////////////////// // Internal methods, single-byte access methods ///////////////////////////////////////////////////// */ protected char nextChar() throws IOException, WstxException { if (mInputPtr >= mInputEnd) { loadMore(); } return mCharBuffer[mInputPtr++]; } protected void skipCRLF(char lf) throws IOException, WstxException { if (lf == CHAR_CR) { char c = (mInputPtr < mInputEnd) ? mCharBuffer[mInputPtr++] : nextChar(); if (c != BYTE_LF) { --mInputPtr; // pushback if not 2-char/byte lf } } ++mInputRow; mInputRowStart = mInputPtr; } }