Author: ebourg
Date: Thu Mar 15 08:52:05 2012
New Revision: 1300850
URL: http://svn.apache.org/viewvc?rev=1300850&view=rev
Log:
Moved the lexer in a separate file
Added:
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java
(with props)
Modified:
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java
Added:
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java
URL:
http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java?rev=1300850&view=auto
==============================================================================
--- commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java
(added)
+++ commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java
Thu Mar 15 08:52:05 2012
@@ -0,0 +1,344 @@
+/*
+ * 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.commons.csv;
+
+import java.io.IOException;
+
+import static org.apache.commons.csv.CSVLexer.Token.Type.*;
+
+class CSVLexer {
+
+ /** length of the initial token (content-)buffer */
+ private static final int INITIAL_TOKEN_LENGTH = 50;
+
+ private final StringBuilder wsBuf = new StringBuilder();
+
+ private final CSVFormat format;
+
+ /** The input stream */
+ private final ExtendedBufferedReader in;
+
+ /**
+ * Token is an internal token representation.
+ * <p/>
+ * It is used as contract between the lexer and the parser.
+ */
+ static class Token {
+
+ enum Type {
+ /** Token has no valid content, i.e. is in its initialized state.
*/
+ INVALID,
+
+ /** Token with content, at beginning or in the middle of a line. */
+ TOKEN,
+
+ /** Token (which can have content) when end of file is reached. */
+ EOF,
+
+ /** Token with content when end of a line is reached. */
+ EORECORD
+ }
+
+ /** Token type */
+ Type type = INVALID;
+
+ /** The content buffer. */
+ StringBuilder content = new StringBuilder(INITIAL_TOKEN_LENGTH);
+
+ /** Token ready flag: indicates a valid token with content (ready for
the parser). */
+ boolean isReady;
+
+ Token reset() {
+ content.setLength(0);
+ type = INVALID;
+ isReady = false;
+ return this;
+ }
+ }
+
+ CSVLexer(CSVFormat format, ExtendedBufferedReader in) {
+ this.format = format;
+ this.in = in;
+ }
+
+ public int getLineNumber() {
+ return in.getLineNumber();
+ }
+
+ /**
+ * Returns the next token.
+ * <p/>
+ * A token corresponds to a term, a record change or an end-of-file
indicator.
+ *
+ * @param tkn an existing Token object to reuse. The caller is responsible
to initialize the Token.
+ * @return the next token found
+ * @throws java.io.IOException on stream access error
+ */
+ Token nextToken(Token tkn) throws IOException {
+ wsBuf.setLength(0); // reuse
+
+ // get the last read char (required for empty line detection)
+ int lastChar = in.readAgain();
+
+ // read the next char and set eol
+ /* note: unfortunately isEndOfLine may consumes a character silently.
+ * this has no effect outside of the method. so a simple
workaround
+ * is to call 'readAgain' on the stream...
+ */
+ int c = in.read();
+ boolean eol = isEndOfLine(c);
+ c = in.readAgain();
+
+ // empty line detection: eol AND (last char was EOL or beginning)
+ if (format.isEmptyLinesIgnored()) {
+ while (eol
+ && (lastChar == '\n' || lastChar == '\r' || lastChar ==
ExtendedBufferedReader.UNDEFINED)
+ && !isEndOfFile(lastChar)) {
+ // go on char ahead ...
+ lastChar = c;
+ c = in.read();
+ eol = isEndOfLine(c);
+ c = in.readAgain();
+ // reached end of file without any content (empty line at the
end)
+ if (isEndOfFile(c)) {
+ tkn.type = EOF;
+ return tkn;
+ }
+ }
+ }
+
+ // did we reach eof during the last iteration already ? EOF
+ if (isEndOfFile(lastChar) || (lastChar != format.getDelimiter() &&
isEndOfFile(c))) {
+ tkn.type = EOF;
+ return tkn;
+ }
+
+ // important: make sure a new char gets consumed in each iteration
+ while (!tkn.isReady && tkn.type != EOF) {
+ // ignore whitespaces at beginning of a token
+ if (format.isLeadingSpacesIgnored()) {
+ while (isWhitespace(c) && !eol) {
+ wsBuf.append((char) c);
+ c = in.read();
+ eol = isEndOfLine(c);
+ }
+ }
+
+ // ok, start of token reached: comment, encapsulated, or token
+ if (c == format.getCommentStart()) {
+ // ignore everything till end of line and continue (incr
linecount)
+ in.readLine();
+ tkn = nextToken(tkn.reset());
+ } else if (c == format.getDelimiter()) {
+ // empty token return TOKEN("")
+ tkn.type = TOKEN;
+ tkn.isReady = true;
+ } else if (eol) {
+ // empty token return EORECORD("")
+ //noop: tkn.content.append("");
+ tkn.type = EORECORD;
+ tkn.isReady = true;
+ } else if (c == format.getEncapsulator()) {
+ // consume encapsulated token
+ encapsulatedTokenLexer(tkn, c);
+ } else if (isEndOfFile(c)) {
+ // end of file return EOF()
+ //noop: tkn.content.append("");
+ tkn.type = EOF;
+ tkn.isReady = true;
+ } else {
+ // next token must be a simple token
+ // add removed blanks when not ignoring whitespace chars...
+ if (!format.isLeadingSpacesIgnored()) {
+ tkn.content.append(wsBuf);
+ }
+ simpleTokenLexer(tkn, c);
+ }
+ }
+ return tkn;
+ }
+
+ /**
+ * A simple token lexer
+ * <p/>
+ * Simple token are tokens which are not surrounded by encapsulators.
+ * A simple token might contain escaped delimiters (as \, or \;). The
+ * token is finished when one of the following conditions become true:
+ * <ul>
+ * <li>end of line has been reached (EORECORD)</li>
+ * <li>end of stream has been reached (EOF)</li>
+ * <li>an unescaped delimiter has been reached (TOKEN)</li>
+ * </ul>
+ *
+ * @param tkn the current token
+ * @param c the current character
+ * @return the filled token
+ * @throws IOException on stream access error
+ */
+ private Token simpleTokenLexer(Token tkn, int c) throws IOException {
+ while (true) {
+ if (isEndOfLine(c)) {
+ // end of record
+ tkn.type = EORECORD;
+ tkn.isReady = true;
+ break;
+ } else if (isEndOfFile(c)) {
+ // end of file
+ tkn.type = EOF;
+ tkn.isReady = true;
+ break;
+ } else if (c == format.getDelimiter()) {
+ // end of token
+ tkn.type = TOKEN;
+ tkn.isReady = true;
+ break;
+ } else if (c == format.getEscape()) {
+ tkn.content.append((char) readEscape(c));
+ } else {
+ tkn.content.append((char) c);
+ }
+
+ c = in.read();
+ }
+
+ if (format.isTrailingSpacesIgnored()) {
+ trimTrailingSpaces(tkn.content);
+ }
+
+ return tkn;
+ }
+
+ private void trimTrailingSpaces(StringBuilder buffer) {
+ int length = buffer.length();
+ while (length > 0 && Character.isWhitespace(buffer.charAt(length -
1))) {
+ length = length - 1;
+ }
+ if (length != buffer.length()) {
+ buffer.setLength(length);
+ }
+ }
+
+ /**
+ * An encapsulated token lexer
+ * <p/>
+ * Encapsulated tokens are surrounded by the given encapsulating-string.
+ * The encapsulator itself might be included in the token using a
+ * doubling syntax (as "", '') or using escaping (as in \", \').
+ * Whitespaces before and after an encapsulated token are ignored.
+ *
+ * @param tkn the current token
+ * @param c the current character
+ * @return a valid token object
+ * @throws IOException on invalid state
+ */
+ private Token encapsulatedTokenLexer(Token tkn, int c) throws IOException {
+ // save current line
+ int startLineNumber = getLineNumber();
+ // ignore the given delimiter
+ // assert c == delimiter;
+ while (true) {
+ c = in.read();
+
+ if (c == format.getEscape()) {
+ tkn.content.append((char) readEscape(c));
+ } else if (c == format.getEncapsulator()) {
+ if (in.lookAhead() == format.getEncapsulator()) {
+ // double or escaped encapsulator -> add single
encapsulator to token
+ c = in.read();
+ tkn.content.append((char) c);
+ } else {
+ // token finish mark (encapsulator) reached: ignore
whitespace till delimiter
+ while (true) {
+ c = in.read();
+ if (c == format.getDelimiter()) {
+ tkn.type = TOKEN;
+ tkn.isReady = true;
+ return tkn;
+ } else if (isEndOfFile(c)) {
+ tkn.type = EOF;
+ tkn.isReady = true;
+ return tkn;
+ } else if (isEndOfLine(c)) {
+ // ok eo token reached
+ tkn.type = EORECORD;
+ tkn.isReady = true;
+ return tkn;
+ } else if (!isWhitespace(c)) {
+ // error invalid char between token and next
delimiter
+ throw new IOException("(line " + getLineNumber() +
") invalid char between encapsulated token and delimiter");
+ }
+ }
+ }
+ } else if (isEndOfFile(c)) {
+ // error condition (end of file before end of token)
+ throw new IOException("(startline " + startLineNumber + ") EOF
reached before encapsulated token finished");
+ } else {
+ // consume character
+ tkn.content.append((char) c);
+ }
+ }
+ }
+
+ private int readEscape(int c) throws IOException {
+ // assume c is the escape char (normally a backslash)
+ c = in.read();
+ switch (c) {
+ case 'r':
+ return '\r';
+ case 'n':
+ return '\n';
+ case 't':
+ return '\t';
+ case 'b':
+ return '\b';
+ case 'f':
+ return '\f';
+ default:
+ return c;
+ }
+ }
+
+ /**
+ * @return true if the given char is a whitespace character
+ */
+ private boolean isWhitespace(int c) {
+ return (c != format.getDelimiter()) && Character.isWhitespace((char)
c);
+ }
+
+ /**
+ * Greedy - accepts \n, \r and \r\n
+ * This checker consumes silently the second control-character...
+ *
+ * @return true if the given character is a line-terminator
+ */
+ private boolean isEndOfLine(int c) throws IOException {
+ // check if we have \r\n...
+ if (c == '\r' && in.lookAhead() == '\n') {
+ // note: does not change c outside of this method !!
+ c = in.read();
+ }
+ return (c == '\n' || c == '\r');
+ }
+
+ /**
+ * @return true if the given character indicates end of file
+ */
+ private boolean isEndOfFile(int c) {
+ return c == ExtendedBufferedReader.END_OF_STREAM;
+ }
+}
\ No newline at end of file
Propchange:
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVLexer.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Modified:
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java
URL:
http://svn.apache.org/viewvc/commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java?rev=1300850&r1=1300849&r2=1300850&view=diff
==============================================================================
---
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java
(original)
+++
commons/proper/csv/trunk/src/main/java/org/apache/commons/csv/CSVParser.java
Thu Mar 15 08:52:05 2012
@@ -230,326 +230,3 @@ public class CSVParser implements Iterab
return lexer.getLineNumber();
}
}
-
-
-class CSVLexer {
-
- /** length of the initial token (content-)buffer */
- private static final int INITIAL_TOKEN_LENGTH = 50;
-
- private final StringBuilder wsBuf = new StringBuilder();
-
- private final CSVFormat format;
-
- /** The input stream */
- private final ExtendedBufferedReader in;
-
- /**
- * Token is an internal token representation.
- * <p/>
- * It is used as contract between the lexer and the parser.
- */
- static class Token {
-
- enum Type {
- /** Token has no valid content, i.e. is in its initialized state.
*/
- INVALID,
-
- /** Token with content, at beginning or in the middle of a line. */
- TOKEN,
-
- /** Token (which can have content) when end of file is reached. */
- EOF,
-
- /** Token with content when end of a line is reached. */
- EORECORD
- }
-
- /** Token type */
- Type type = INVALID;
-
- /** The content buffer. */
- StringBuilder content = new StringBuilder(INITIAL_TOKEN_LENGTH);
-
- /** Token ready flag: indicates a valid token with content (ready for
the parser). */
- boolean isReady;
-
- Token reset() {
- content.setLength(0);
- type = INVALID;
- isReady = false;
- return this;
- }
- }
-
- CSVLexer(CSVFormat format, ExtendedBufferedReader in) {
- this.format = format;
- this.in = in;
- }
-
- public int getLineNumber() {
- return in.getLineNumber();
- }
-
- /**
- * Returns the next token.
- * <p/>
- * A token corresponds to a term, a record change or an end-of-file
indicator.
- *
- * @param tkn an existing Token object to reuse. The caller is responsible
to initialize the Token.
- * @return the next token found
- * @throws IOException on stream access error
- */
- Token nextToken(Token tkn) throws IOException {
- wsBuf.setLength(0); // reuse
-
- // get the last read char (required for empty line detection)
- int lastChar = in.readAgain();
-
- // read the next char and set eol
- /* note: unfortunately isEndOfLine may consumes a character silently.
- * this has no effect outside of the method. so a simple
workaround
- * is to call 'readAgain' on the stream...
- */
- int c = in.read();
- boolean eol = isEndOfLine(c);
- c = in.readAgain();
-
- // empty line detection: eol AND (last char was EOL or beginning)
- if (format.isEmptyLinesIgnored()) {
- while (eol
- && (lastChar == '\n' || lastChar == '\r' || lastChar ==
ExtendedBufferedReader.UNDEFINED)
- && !isEndOfFile(lastChar)) {
- // go on char ahead ...
- lastChar = c;
- c = in.read();
- eol = isEndOfLine(c);
- c = in.readAgain();
- // reached end of file without any content (empty line at the
end)
- if (isEndOfFile(c)) {
- tkn.type = EOF;
- return tkn;
- }
- }
- }
-
- // did we reach eof during the last iteration already ? EOF
- if (isEndOfFile(lastChar) || (lastChar != format.getDelimiter() &&
isEndOfFile(c))) {
- tkn.type = EOF;
- return tkn;
- }
-
- // important: make sure a new char gets consumed in each iteration
- while (!tkn.isReady && tkn.type != EOF) {
- // ignore whitespaces at beginning of a token
- if (format.isLeadingSpacesIgnored()) {
- while (isWhitespace(c) && !eol) {
- wsBuf.append((char) c);
- c = in.read();
- eol = isEndOfLine(c);
- }
- }
-
- // ok, start of token reached: comment, encapsulated, or token
- if (c == format.getCommentStart()) {
- // ignore everything till end of line and continue (incr
linecount)
- in.readLine();
- tkn = nextToken(tkn.reset());
- } else if (c == format.getDelimiter()) {
- // empty token return TOKEN("")
- tkn.type = TOKEN;
- tkn.isReady = true;
- } else if (eol) {
- // empty token return EORECORD("")
- //noop: tkn.content.append("");
- tkn.type = EORECORD;
- tkn.isReady = true;
- } else if (c == format.getEncapsulator()) {
- // consume encapsulated token
- encapsulatedTokenLexer(tkn, c);
- } else if (isEndOfFile(c)) {
- // end of file return EOF()
- //noop: tkn.content.append("");
- tkn.type = EOF;
- tkn.isReady = true;
- } else {
- // next token must be a simple token
- // add removed blanks when not ignoring whitespace chars...
- if (!format.isLeadingSpacesIgnored()) {
- tkn.content.append(wsBuf);
- }
- simpleTokenLexer(tkn, c);
- }
- }
- return tkn;
- }
-
- /**
- * A simple token lexer
- * <p/>
- * Simple token are tokens which are not surrounded by encapsulators.
- * A simple token might contain escaped delimiters (as \, or \;). The
- * token is finished when one of the following conditions become true:
- * <ul>
- * <li>end of line has been reached (EORECORD)</li>
- * <li>end of stream has been reached (EOF)</li>
- * <li>an unescaped delimiter has been reached (TOKEN)</li>
- * </ul>
- *
- * @param tkn the current token
- * @param c the current character
- * @return the filled token
- * @throws IOException on stream access error
- */
- private Token simpleTokenLexer(Token tkn, int c) throws IOException {
- while (true) {
- if (isEndOfLine(c)) {
- // end of record
- tkn.type = EORECORD;
- tkn.isReady = true;
- break;
- } else if (isEndOfFile(c)) {
- // end of file
- tkn.type = EOF;
- tkn.isReady = true;
- break;
- } else if (c == format.getDelimiter()) {
- // end of token
- tkn.type = TOKEN;
- tkn.isReady = true;
- break;
- } else if (c == format.getEscape()) {
- tkn.content.append((char) readEscape(c));
- } else {
- tkn.content.append((char) c);
- }
-
- c = in.read();
- }
-
- if (format.isTrailingSpacesIgnored()) {
- trimTrailingSpaces(tkn.content);
- }
-
- return tkn;
- }
-
- private void trimTrailingSpaces(StringBuilder buffer) {
- int length = buffer.length();
- while (length > 0 && Character.isWhitespace(buffer.charAt(length -
1))) {
- length = length - 1;
- }
- if (length != buffer.length()) {
- buffer.setLength(length);
- }
- }
-
- /**
- * An encapsulated token lexer
- * <p/>
- * Encapsulated tokens are surrounded by the given encapsulating-string.
- * The encapsulator itself might be included in the token using a
- * doubling syntax (as "", '') or using escaping (as in \", \').
- * Whitespaces before and after an encapsulated token are ignored.
- *
- * @param tkn the current token
- * @param c the current character
- * @return a valid token object
- * @throws IOException on invalid state
- */
- private Token encapsulatedTokenLexer(Token tkn, int c) throws IOException {
- // save current line
- int startLineNumber = getLineNumber();
- // ignore the given delimiter
- // assert c == delimiter;
- while (true) {
- c = in.read();
-
- if (c == format.getEscape()) {
- tkn.content.append((char) readEscape(c));
- } else if (c == format.getEncapsulator()) {
- if (in.lookAhead() == format.getEncapsulator()) {
- // double or escaped encapsulator -> add single
encapsulator to token
- c = in.read();
- tkn.content.append((char) c);
- } else {
- // token finish mark (encapsulator) reached: ignore
whitespace till delimiter
- while (true) {
- c = in.read();
- if (c == format.getDelimiter()) {
- tkn.type = TOKEN;
- tkn.isReady = true;
- return tkn;
- } else if (isEndOfFile(c)) {
- tkn.type = EOF;
- tkn.isReady = true;
- return tkn;
- } else if (isEndOfLine(c)) {
- // ok eo token reached
- tkn.type = EORECORD;
- tkn.isReady = true;
- return tkn;
- } else if (!isWhitespace(c)) {
- // error invalid char between token and next
delimiter
- throw new IOException("(line " + getLineNumber() +
") invalid char between encapsulated token and delimiter");
- }
- }
- }
- } else if (isEndOfFile(c)) {
- // error condition (end of file before end of token)
- throw new IOException("(startline " + startLineNumber + ") EOF
reached before encapsulated token finished");
- } else {
- // consume character
- tkn.content.append((char) c);
- }
- }
- }
-
- private int readEscape(int c) throws IOException {
- // assume c is the escape char (normally a backslash)
- c = in.read();
- switch (c) {
- case 'r':
- return '\r';
- case 'n':
- return '\n';
- case 't':
- return '\t';
- case 'b':
- return '\b';
- case 'f':
- return '\f';
- default:
- return c;
- }
- }
-
- /**
- * @return true if the given char is a whitespace character
- */
- private boolean isWhitespace(int c) {
- return (c != format.getDelimiter()) && Character.isWhitespace((char)
c);
- }
-
- /**
- * Greedy - accepts \n, \r and \r\n
- * This checker consumes silently the second control-character...
- *
- * @return true if the given character is a line-terminator
- */
- private boolean isEndOfLine(int c) throws IOException {
- // check if we have \r\n...
- if (c == '\r' && in.lookAhead() == '\n') {
- // note: does not change c outside of this method !!
- c = in.read();
- }
- return (c == '\n' || c == '\r');
- }
-
- /**
- * @return true if the given character indicates end of file
- */
- private boolean isEndOfFile(int c) {
- return c == ExtendedBufferedReader.END_OF_STREAM;
- }
-}