001 /* 002 * Java CSV is a stream based library for reading and writing 003 * CSV and other delimited data. 004 * 005 * Copyright (C) Bruce Dunwiddie bruce@csvreader.com 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 020 */ 021 package com.csvreader; 022 023 import java.io.BufferedReader; 024 import java.io.File; 025 import java.io.FileInputStream; 026 import java.io.FileNotFoundException; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.io.InputStreamReader; 030 import java.io.Reader; 031 import java.io.StringReader; 032 import java.nio.charset.Charset; 033 import java.text.NumberFormat; 034 import java.util.HashMap; 035 036 /** 037 * A stream based parser for parsing delimited text data from a file or a 038 * stream. 039 */ 040 public class CsvReader { 041 private Reader inputStream = null; 042 043 private String fileName = null; 044 045 // this holds all the values for switches that the user is allowed to set 046 private UserSettings userSettings = new UserSettings(); 047 048 private Charset charset = null; 049 050 private boolean useCustomRecordDelimiter = false; 051 052 // this will be our working buffer to hold data chunks 053 // read in from the data file 054 055 private DataBuffer dataBuffer = new DataBuffer(); 056 057 private ColumnBuffer columnBuffer = new ColumnBuffer(); 058 059 private RawRecordBuffer rawBuffer = new RawRecordBuffer(); 060 061 private boolean[] isQualified = null; 062 063 private String rawRecord = ""; 064 065 private HeadersHolder headersHolder = new HeadersHolder(); 066 067 // these are all more or less global loop variables 068 // to keep from needing to pass them all into various 069 // methods during parsing 070 071 private boolean startedColumn = false; 072 073 private boolean startedWithQualifier = false; 074 075 private boolean hasMoreData = true; 076 077 private char lastLetter = '\0'; 078 079 private boolean hasReadNextLine = false; 080 081 private int columnsCount = 0; 082 083 private long currentRecord = 0; 084 085 private String[] values = new String[StaticSettings.INITIAL_COLUMN_COUNT]; 086 087 private boolean initialized = false; 088 089 private boolean closed = false; 090 091 /** 092 * Double up the text qualifier to represent an occurance of the text 093 * qualifier. 094 */ 095 public static final int ESCAPE_MODE_DOUBLED = 1; 096 097 /** 098 * Use a backslash character before the text qualifier to represent an 099 * occurance of the text qualifier. 100 */ 101 public static final int ESCAPE_MODE_BACKSLASH = 2; 102 103 /** 104 * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file 105 * as the data source. 106 * 107 * @param fileName 108 * The path to the file to use as the data source. 109 * @param delimiter 110 * The character to use as the column delimiter. 111 * @param charset 112 * The {@link java.nio.charset.Charset Charset} to use while 113 * parsing the data. 114 */ 115 public CsvReader(String fileName, char delimiter, Charset charset) 116 throws FileNotFoundException { 117 if (fileName == null) { 118 throw new IllegalArgumentException( 119 "Parameter fileName can not be null."); 120 } 121 122 if (charset == null) { 123 throw new IllegalArgumentException( 124 "Parameter charset can not be null."); 125 } 126 127 if (!new File(fileName).exists()) { 128 throw new FileNotFoundException("File " + fileName 129 + " does not exist."); 130 } 131 132 this.fileName = fileName; 133 this.userSettings.Delimiter = delimiter; 134 this.charset = charset; 135 136 isQualified = new boolean[values.length]; 137 } 138 139 /** 140 * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file 141 * as the data source. Uses ISO-8859-1 as the 142 * {@link java.nio.charset.Charset Charset}. 143 * 144 * @param fileName 145 * The path to the file to use as the data source. 146 * @param delimiter 147 * The character to use as the column delimiter. 148 */ 149 public CsvReader(String fileName, char delimiter) 150 throws FileNotFoundException { 151 this(fileName, delimiter, Charset.forName("ISO-8859-1")); 152 } 153 154 /** 155 * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file 156 * as the data source. Uses a comma as the column delimiter and 157 * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}. 158 * 159 * @param fileName 160 * The path to the file to use as the data source. 161 */ 162 public CsvReader(String fileName) throws FileNotFoundException { 163 this(fileName, Letters.COMMA); 164 } 165 166 /** 167 * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a 168 * {@link java.io.Reader Reader} object as the data source. 169 * 170 * @param inputStream 171 * The stream to use as the data source. 172 * @param delimiter 173 * The character to use as the column delimiter. 174 */ 175 public CsvReader(Reader inputStream, char delimiter) { 176 if (inputStream == null) { 177 throw new IllegalArgumentException( 178 "Parameter inputStream can not be null."); 179 } 180 181 this.inputStream = inputStream; 182 this.userSettings.Delimiter = delimiter; 183 initialized = true; 184 185 isQualified = new boolean[values.length]; 186 } 187 188 /** 189 * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a 190 * {@link java.io.Reader Reader} object as the data source. Uses a 191 * comma as the column delimiter. 192 * 193 * @param inputStream 194 * The stream to use as the data source. 195 */ 196 public CsvReader(Reader inputStream) { 197 this(inputStream, Letters.COMMA); 198 } 199 200 /** 201 * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an 202 * {@link java.io.InputStream InputStream} object as the data source. 203 * 204 * @param inputStream 205 * The stream to use as the data source. 206 * @param delimiter 207 * The character to use as the column delimiter. 208 * @param charset 209 * The {@link java.nio.charset.Charset Charset} to use while 210 * parsing the data. 211 */ 212 public CsvReader(InputStream inputStream, char delimiter, Charset charset) { 213 this(new InputStreamReader(inputStream, charset), delimiter); 214 } 215 216 /** 217 * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an 218 * {@link java.io.InputStream InputStream} object as the data 219 * source. Uses a comma as the column delimiter. 220 * 221 * @param inputStream 222 * The stream to use as the data source. 223 * @param charset 224 * The {@link java.nio.charset.Charset Charset} to use while 225 * parsing the data. 226 */ 227 public CsvReader(InputStream inputStream, Charset charset) { 228 this(new InputStreamReader(inputStream, charset)); 229 } 230 231 public boolean getCaptureRawRecord() { 232 return userSettings.CaptureRawRecord; 233 } 234 235 public void setCaptureRawRecord(boolean captureRawRecord) { 236 userSettings.CaptureRawRecord = captureRawRecord; 237 } 238 239 public String getRawRecord() { 240 return rawRecord; 241 } 242 243 /** 244 * Gets whether leading and trailing whitespace characters are being trimmed 245 * from non-textqualified column data. Default is true. 246 * 247 * @return Whether leading and trailing whitespace characters are being 248 * trimmed from non-textqualified column data. 249 */ 250 public boolean getTrimWhitespace() { 251 return userSettings.TrimWhitespace; 252 } 253 254 /** 255 * Sets whether leading and trailing whitespace characters should be trimmed 256 * from non-textqualified column data or not. Default is true. 257 * 258 * @param trimWhitespace 259 * Whether leading and trailing whitespace characters should be 260 * trimmed from non-textqualified column data or not. 261 */ 262 public void setTrimWhitespace(boolean trimWhitespace) { 263 userSettings.TrimWhitespace = trimWhitespace; 264 } 265 266 /** 267 * Gets the character being used as the column delimiter. Default is comma, 268 * ','. 269 * 270 * @return The character being used as the column delimiter. 271 */ 272 public char getDelimiter() { 273 return userSettings.Delimiter; 274 } 275 276 /** 277 * Sets the character to use as the column delimiter. Default is comma, ','. 278 * 279 * @param delimiter 280 * The character to use as the column delimiter. 281 */ 282 public void setDelimiter(char delimiter) { 283 userSettings.Delimiter = delimiter; 284 } 285 286 public char getRecordDelimiter() { 287 return userSettings.RecordDelimiter; 288 } 289 290 /** 291 * Sets the character to use as the record delimiter. 292 * 293 * @param recordDelimiter 294 * The character to use as the record delimiter. Default is 295 * combination of standard end of line characters for Windows, 296 * Unix, or Mac. 297 */ 298 public void setRecordDelimiter(char recordDelimiter) { 299 useCustomRecordDelimiter = true; 300 userSettings.RecordDelimiter = recordDelimiter; 301 } 302 303 /** 304 * Gets the character to use as a text qualifier in the data. 305 * 306 * @return The character to use as a text qualifier in the data. 307 */ 308 public char getTextQualifier() { 309 return userSettings.TextQualifier; 310 } 311 312 /** 313 * Sets the character to use as a text qualifier in the data. 314 * 315 * @param textQualifier 316 * The character to use as a text qualifier in the data. 317 */ 318 public void setTextQualifier(char textQualifier) { 319 userSettings.TextQualifier = textQualifier; 320 } 321 322 /** 323 * Whether text qualifiers will be used while parsing or not. 324 * 325 * @return Whether text qualifiers will be used while parsing or not. 326 */ 327 public boolean getUseTextQualifier() { 328 return userSettings.UseTextQualifier; 329 } 330 331 /** 332 * Sets whether text qualifiers will be used while parsing or not. 333 * 334 * @param useTextQualifier 335 * Whether to use a text qualifier while parsing or not. 336 */ 337 public void setUseTextQualifier(boolean useTextQualifier) { 338 userSettings.UseTextQualifier = useTextQualifier; 339 } 340 341 /** 342 * Gets the character being used as a comment signal. 343 * 344 * @return The character being used as a comment signal. 345 */ 346 public char getComment() { 347 return userSettings.Comment; 348 } 349 350 /** 351 * Sets the character to use as a comment signal. 352 * 353 * @param comment 354 * The character to use as a comment signal. 355 */ 356 public void setComment(char comment) { 357 userSettings.Comment = comment; 358 } 359 360 /** 361 * Gets whether comments are being looked for while parsing or not. 362 * 363 * @return Whether comments are being looked for while parsing or not. 364 */ 365 public boolean getUseComments() { 366 return userSettings.UseComments; 367 } 368 369 /** 370 * Sets whether comments are being looked for while parsing or not. 371 * 372 * @param useComments 373 * Whether comments are being looked for while parsing or not. 374 */ 375 public void setUseComments(boolean useComments) { 376 userSettings.UseComments = useComments; 377 } 378 379 /** 380 * Gets the current way to escape an occurance of the text qualifier inside 381 * qualified data. 382 * 383 * @return The current way to escape an occurance of the text qualifier 384 * inside qualified data. 385 */ 386 public int getEscapeMode() { 387 return userSettings.EscapeMode; 388 } 389 390 /** 391 * Sets the current way to escape an occurance of the text qualifier inside 392 * qualified data. 393 * 394 * @param escapeMode 395 * The way to escape an occurance of the text qualifier inside 396 * qualified data. 397 * @exception IllegalArgumentException 398 * When an illegal value is specified for escapeMode. 399 */ 400 public void setEscapeMode(int escapeMode) throws IllegalArgumentException { 401 if (escapeMode != ESCAPE_MODE_DOUBLED 402 && escapeMode != ESCAPE_MODE_BACKSLASH) { 403 throw new IllegalArgumentException( 404 "Parameter escapeMode must be a valid value."); 405 } 406 407 userSettings.EscapeMode = escapeMode; 408 } 409 410 public boolean getSkipEmptyRecords() { 411 return userSettings.SkipEmptyRecords; 412 } 413 414 public void setSkipEmptyRecords(boolean skipEmptyRecords) { 415 userSettings.SkipEmptyRecords = skipEmptyRecords; 416 } 417 418 /** 419 * Safety caution to prevent the parser from using large amounts of memory 420 * in the case where parsing settings like file encodings don't end up 421 * matching the actual format of a file. This switch can be turned off if 422 * the file format is known and tested. With the switch off, the max column 423 * lengths and max column count per record supported by the parser will 424 * greatly increase. Default is true. 425 * 426 * @return The current setting of the safety switch. 427 */ 428 public boolean getSafetySwitch() { 429 return userSettings.SafetySwitch; 430 } 431 432 /** 433 * Safety caution to prevent the parser from using large amounts of memory 434 * in the case where parsing settings like file encodings don't end up 435 * matching the actual format of a file. This switch can be turned off if 436 * the file format is known and tested. With the switch off, the max column 437 * lengths and max column count per record supported by the parser will 438 * greatly increase. Default is true. 439 * 440 * @param safetySwitch 441 */ 442 public void setSafetySwitch(boolean safetySwitch) { 443 userSettings.SafetySwitch = safetySwitch; 444 } 445 446 /** 447 * Gets the count of columns found in this record. 448 * 449 * @return The count of columns found in this record. 450 */ 451 public int getColumnCount() { 452 return columnsCount; 453 } 454 455 /** 456 * Gets the index of the current record. 457 * 458 * @return The index of the current record. 459 */ 460 public long getCurrentRecord() { 461 return currentRecord - 1; 462 } 463 464 /** 465 * Gets the count of headers read in by a previous call to 466 * {@link com.csvreader.CsvReader#readHeaders readHeaders()}. 467 * 468 * @return The count of headers read in by a previous call to 469 * {@link com.csvreader.CsvReader#readHeaders readHeaders()}. 470 */ 471 public int getHeaderCount() { 472 return headersHolder.Length; 473 } 474 475 /** 476 * Returns the header values as a string array. 477 * 478 * @return The header values as a String array. 479 * @exception IOException 480 * Thrown if this object has already been closed. 481 */ 482 public String[] getHeaders() throws IOException { 483 checkClosed(); 484 485 if (headersHolder.Headers == null) { 486 return null; 487 } 488 489 // use clone here to prevent the outside code from 490 // setting values on the array directly, which would 491 // throw off the index lookup based on header name 492 String[] clone = new String[headersHolder.Length]; 493 System.arraycopy(headersHolder.Headers, 0, clone, 0, headersHolder.Length); 494 495 return clone; 496 } 497 498 public void setHeaders(String[] headers) { 499 headersHolder.Headers = headers; 500 501 headersHolder.IndexByName.clear(); 502 503 if(headers == null){ 504 return; 505 } 506 507 headersHolder.Length = headers.length; 508 509 // use headersHolder.Length here in case headers is null 510 for (int i = 0; i < headersHolder.Length; i++) { 511 headersHolder.IndexByName.put(headers[i], new Integer(i)); 512 } 513 } 514 515 public String[] getValues() throws IOException { 516 checkClosed(); 517 518 // need to return a clone, and can't use clone because values.Length 519 // might be greater than columnsCount 520 String[] clone = new String[columnsCount]; 521 System.arraycopy(values, 0, clone, 0, columnsCount); 522 return clone; 523 } 524 525 /** 526 * Returns the current column value for a given column index. 527 * 528 * @param columnIndex 529 * The index of the column. 530 * @return The current column value. 531 * @exception IOException 532 * Thrown if this object has already been closed. 533 */ 534 public String get(int columnIndex) throws IOException { 535 checkClosed(); 536 537 if(columnIndex < 0 || columnIndex >= columnsCount){ 538 return ""; 539 } 540 541 return values[columnIndex]; 542 } 543 544 /** 545 * Returns the current column value for a given column header name. 546 * 547 * @param headerName 548 * The header name of the column. 549 * @return The current column value. 550 * @exception IOException 551 * Thrown if this object has already been closed. 552 */ 553 public String get(String headerName) throws IOException { 554 checkClosed(); 555 556 return get(getIndex(headerName)); 557 } 558 559 /** 560 * Creates a {@link com.csvreader.CsvReader CsvReader} object using a string 561 * of data as the source. Uses ISO-8859-1 as the 562 * {@link java.nio.charset.Charset Charset}. 563 * 564 * @param data 565 * The String of data to use as the source. 566 * @return A {@link com.csvreader.CsvReader CsvReader} object using the 567 * String of data as the source. 568 */ 569 public static CsvReader parse(String data) { 570 if (data == null) { 571 throw new IllegalArgumentException( 572 "Parameter data can not be null."); 573 } 574 575 return new CsvReader(new StringReader(data)); 576 } 577 578 /** 579 * Reads another record. 580 * 581 * @return Whether another record was successfully read or not. 582 * @exception IOException 583 * Thrown if an error occurs while reading data from the 584 * source stream. 585 */ 586 public boolean readRecord() throws IOException { 587 checkClosed(); 588 589 columnsCount = 0; 590 rawBuffer.Position = 0; 591 592 dataBuffer.LineStart = dataBuffer.Position; 593 594 hasReadNextLine = false; 595 596 // check to see if we've already found the end of data 597 598 if (hasMoreData) { 599 // loop over the data stream until the end of data is found 600 // or the end of the record is found 601 602 do { 603 if (dataBuffer.Position == dataBuffer.Count) { 604 checkDataLength(); 605 } else { 606 startedWithQualifier = false; 607 608 // grab the current letter as a char 609 610 char currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 611 612 if (userSettings.UseTextQualifier 613 && currentLetter == userSettings.TextQualifier) { 614 // this will be a text qualified column, so 615 // we need to set startedWithQualifier to make it 616 // enter the seperate branch to handle text 617 // qualified columns 618 619 lastLetter = currentLetter; 620 621 // read qualified 622 startedColumn = true; 623 dataBuffer.ColumnStart = dataBuffer.Position + 1; 624 startedWithQualifier = true; 625 boolean lastLetterWasQualifier = false; 626 627 char escapeChar = userSettings.TextQualifier; 628 629 if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) { 630 escapeChar = Letters.BACKSLASH; 631 } 632 633 boolean eatingTrailingJunk = false; 634 boolean lastLetterWasEscape = false; 635 boolean readingComplexEscape = false; 636 int escape = ComplexEscape.UNICODE; 637 int escapeLength = 0; 638 char escapeValue = (char) 0; 639 640 dataBuffer.Position++; 641 642 do { 643 if (dataBuffer.Position == dataBuffer.Count) { 644 checkDataLength(); 645 } else { 646 // grab the current letter as a char 647 648 currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 649 650 if (eatingTrailingJunk) { 651 dataBuffer.ColumnStart = dataBuffer.Position + 1; 652 653 if (currentLetter == userSettings.Delimiter) { 654 endColumn(); 655 } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF)) 656 || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) { 657 endColumn(); 658 659 endRecord(); 660 } 661 } else if (readingComplexEscape) { 662 escapeLength++; 663 664 switch (escape) { 665 case ComplexEscape.UNICODE: 666 escapeValue *= (char) 16; 667 escapeValue += hexToDec(currentLetter); 668 669 if (escapeLength == 4) { 670 readingComplexEscape = false; 671 } 672 673 break; 674 case ComplexEscape.OCTAL: 675 escapeValue *= (char) 8; 676 escapeValue += (char) (currentLetter - '0'); 677 678 if (escapeLength == 3) { 679 readingComplexEscape = false; 680 } 681 682 break; 683 case ComplexEscape.DECIMAL: 684 escapeValue *= (char) 10; 685 escapeValue += (char) (currentLetter - '0'); 686 687 if (escapeLength == 3) { 688 readingComplexEscape = false; 689 } 690 691 break; 692 case ComplexEscape.HEX: 693 escapeValue *= (char) 16; 694 escapeValue += hexToDec(currentLetter); 695 696 if (escapeLength == 2) { 697 readingComplexEscape = false; 698 } 699 700 break; 701 } 702 703 if (!readingComplexEscape) { 704 appendLetter(escapeValue); 705 } else { 706 dataBuffer.ColumnStart = dataBuffer.Position + 1; 707 } 708 } else if (currentLetter == userSettings.TextQualifier) { 709 if (lastLetterWasEscape) { 710 lastLetterWasEscape = false; 711 lastLetterWasQualifier = false; 712 } else { 713 updateCurrentValue(); 714 715 if (userSettings.EscapeMode == ESCAPE_MODE_DOUBLED) { 716 lastLetterWasEscape = true; 717 } 718 719 lastLetterWasQualifier = true; 720 } 721 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH 722 && lastLetterWasEscape) { 723 switch (currentLetter) { 724 case 'n': 725 appendLetter(Letters.LF); 726 break; 727 case 'r': 728 appendLetter(Letters.CR); 729 break; 730 case 't': 731 appendLetter(Letters.TAB); 732 break; 733 case 'b': 734 appendLetter(Letters.BACKSPACE); 735 break; 736 case 'f': 737 appendLetter(Letters.FORM_FEED); 738 break; 739 case 'e': 740 appendLetter(Letters.ESCAPE); 741 break; 742 case 'v': 743 appendLetter(Letters.VERTICAL_TAB); 744 break; 745 case 'a': 746 appendLetter(Letters.ALERT); 747 break; 748 case '0': 749 case '1': 750 case '2': 751 case '3': 752 case '4': 753 case '5': 754 case '6': 755 case '7': 756 escape = ComplexEscape.OCTAL; 757 readingComplexEscape = true; 758 escapeLength = 1; 759 escapeValue = (char) (currentLetter - '0'); 760 dataBuffer.ColumnStart = dataBuffer.Position + 1; 761 break; 762 case 'u': 763 case 'x': 764 case 'o': 765 case 'd': 766 case 'U': 767 case 'X': 768 case 'O': 769 case 'D': 770 switch (currentLetter) { 771 case 'u': 772 case 'U': 773 escape = ComplexEscape.UNICODE; 774 break; 775 case 'x': 776 case 'X': 777 escape = ComplexEscape.HEX; 778 break; 779 case 'o': 780 case 'O': 781 escape = ComplexEscape.OCTAL; 782 break; 783 case 'd': 784 case 'D': 785 escape = ComplexEscape.DECIMAL; 786 break; 787 } 788 789 readingComplexEscape = true; 790 escapeLength = 0; 791 escapeValue = (char) 0; 792 dataBuffer.ColumnStart = dataBuffer.Position + 1; 793 794 break; 795 default: 796 break; 797 } 798 799 lastLetterWasEscape = false; 800 801 // can only happen for ESCAPE_MODE_BACKSLASH 802 } else if (currentLetter == escapeChar) { 803 updateCurrentValue(); 804 lastLetterWasEscape = true; 805 } else { 806 if (lastLetterWasQualifier) { 807 if (currentLetter == userSettings.Delimiter) { 808 endColumn(); 809 } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF)) 810 || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) { 811 endColumn(); 812 813 endRecord(); 814 } else { 815 dataBuffer.ColumnStart = dataBuffer.Position + 1; 816 817 eatingTrailingJunk = true; 818 } 819 820 // make sure to clear the flag for next 821 // run of the loop 822 823 lastLetterWasQualifier = false; 824 } 825 } 826 827 // keep track of the last letter because we need 828 // it for several key decisions 829 830 lastLetter = currentLetter; 831 832 if (startedColumn) { 833 dataBuffer.Position++; 834 835 if (userSettings.SafetySwitch 836 && dataBuffer.Position 837 - dataBuffer.ColumnStart 838 + columnBuffer.Position > 100000) { 839 close(); 840 841 throw new IOException( 842 "Maximum column length of 100,000 exceeded in column " 843 + NumberFormat 844 .getIntegerInstance() 845 .format( 846 columnsCount) 847 + " in record " 848 + NumberFormat 849 .getIntegerInstance() 850 .format( 851 currentRecord) 852 + ". Set the SafetySwitch property to false" 853 + " if you're expecting column lengths greater than 100,000 characters to" 854 + " avoid this error."); 855 } 856 } 857 } // end else 858 859 } while (hasMoreData && startedColumn); 860 } else if (currentLetter == userSettings.Delimiter) { 861 // we encountered a column with no data, so 862 // just send the end column 863 864 lastLetter = currentLetter; 865 866 endColumn(); 867 } else if (useCustomRecordDelimiter 868 && currentLetter == userSettings.RecordDelimiter) { 869 // this will skip blank lines 870 if (startedColumn || columnsCount > 0 871 || !userSettings.SkipEmptyRecords) { 872 endColumn(); 873 874 endRecord(); 875 } else { 876 dataBuffer.LineStart = dataBuffer.Position + 1; 877 } 878 879 lastLetter = currentLetter; 880 } else if (!useCustomRecordDelimiter 881 && (currentLetter == Letters.CR || currentLetter == Letters.LF)) { 882 // this will skip blank lines 883 if (startedColumn 884 || columnsCount > 0 885 || (!userSettings.SkipEmptyRecords && (currentLetter == Letters.CR || lastLetter != Letters.CR))) { 886 endColumn(); 887 888 endRecord(); 889 } else { 890 dataBuffer.LineStart = dataBuffer.Position + 1; 891 } 892 893 lastLetter = currentLetter; 894 } else if (userSettings.UseComments && columnsCount == 0 895 && currentLetter == userSettings.Comment) { 896 // encountered a comment character at the beginning of 897 // the line so just ignore the rest of the line 898 899 lastLetter = currentLetter; 900 901 skipLine(); 902 } else if (userSettings.TrimWhitespace 903 && (currentLetter == Letters.SPACE || currentLetter == Letters.TAB)) { 904 // do nothing, this will trim leading whitespace 905 // for both text qualified columns and non 906 907 startedColumn = true; 908 dataBuffer.ColumnStart = dataBuffer.Position + 1; 909 } else { 910 // since the letter wasn't a special letter, this 911 // will be the first letter of our current column 912 913 startedColumn = true; 914 dataBuffer.ColumnStart = dataBuffer.Position; 915 boolean lastLetterWasBackslash = false; 916 boolean readingComplexEscape = false; 917 int escape = ComplexEscape.UNICODE; 918 int escapeLength = 0; 919 char escapeValue = (char) 0; 920 921 boolean firstLoop = true; 922 923 do { 924 if (!firstLoop 925 && dataBuffer.Position == dataBuffer.Count) { 926 checkDataLength(); 927 } else { 928 if (!firstLoop) { 929 // grab the current letter as a char 930 currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 931 } 932 933 if (!userSettings.UseTextQualifier 934 && userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH 935 && currentLetter == Letters.BACKSLASH) { 936 if (lastLetterWasBackslash) { 937 lastLetterWasBackslash = false; 938 } else { 939 updateCurrentValue(); 940 lastLetterWasBackslash = true; 941 } 942 } else if (readingComplexEscape) { 943 escapeLength++; 944 945 switch (escape) { 946 case ComplexEscape.UNICODE: 947 escapeValue *= (char) 16; 948 escapeValue += hexToDec(currentLetter); 949 950 if (escapeLength == 4) { 951 readingComplexEscape = false; 952 } 953 954 break; 955 case ComplexEscape.OCTAL: 956 escapeValue *= (char) 8; 957 escapeValue += (char) (currentLetter - '0'); 958 959 if (escapeLength == 3) { 960 readingComplexEscape = false; 961 } 962 963 break; 964 case ComplexEscape.DECIMAL: 965 escapeValue *= (char) 10; 966 escapeValue += (char) (currentLetter - '0'); 967 968 if (escapeLength == 3) { 969 readingComplexEscape = false; 970 } 971 972 break; 973 case ComplexEscape.HEX: 974 escapeValue *= (char) 16; 975 escapeValue += hexToDec(currentLetter); 976 977 if (escapeLength == 2) { 978 readingComplexEscape = false; 979 } 980 981 break; 982 } 983 984 if (!readingComplexEscape) { 985 appendLetter(escapeValue); 986 } else { 987 dataBuffer.ColumnStart = dataBuffer.Position + 1; 988 } 989 } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH 990 && lastLetterWasBackslash) { 991 switch (currentLetter) { 992 case 'n': 993 appendLetter(Letters.LF); 994 break; 995 case 'r': 996 appendLetter(Letters.CR); 997 break; 998 case 't': 999 appendLetter(Letters.TAB); 1000 break; 1001 case 'b': 1002 appendLetter(Letters.BACKSPACE); 1003 break; 1004 case 'f': 1005 appendLetter(Letters.FORM_FEED); 1006 break; 1007 case 'e': 1008 appendLetter(Letters.ESCAPE); 1009 break; 1010 case 'v': 1011 appendLetter(Letters.VERTICAL_TAB); 1012 break; 1013 case 'a': 1014 appendLetter(Letters.ALERT); 1015 break; 1016 case '0': 1017 case '1': 1018 case '2': 1019 case '3': 1020 case '4': 1021 case '5': 1022 case '6': 1023 case '7': 1024 escape = ComplexEscape.OCTAL; 1025 readingComplexEscape = true; 1026 escapeLength = 1; 1027 escapeValue = (char) (currentLetter - '0'); 1028 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1029 break; 1030 case 'u': 1031 case 'x': 1032 case 'o': 1033 case 'd': 1034 case 'U': 1035 case 'X': 1036 case 'O': 1037 case 'D': 1038 switch (currentLetter) { 1039 case 'u': 1040 case 'U': 1041 escape = ComplexEscape.UNICODE; 1042 break; 1043 case 'x': 1044 case 'X': 1045 escape = ComplexEscape.HEX; 1046 break; 1047 case 'o': 1048 case 'O': 1049 escape = ComplexEscape.OCTAL; 1050 break; 1051 case 'd': 1052 case 'D': 1053 escape = ComplexEscape.DECIMAL; 1054 break; 1055 } 1056 1057 readingComplexEscape = true; 1058 escapeLength = 0; 1059 escapeValue = (char) 0; 1060 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1061 1062 break; 1063 default: 1064 break; 1065 } 1066 1067 lastLetterWasBackslash = false; 1068 } else { 1069 if (currentLetter == userSettings.Delimiter) { 1070 endColumn(); 1071 } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF)) 1072 || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) { 1073 endColumn(); 1074 1075 endRecord(); 1076 } 1077 } 1078 1079 // keep track of the last letter because we need 1080 // it for several key decisions 1081 1082 lastLetter = currentLetter; 1083 firstLoop = false; 1084 1085 if (startedColumn) { 1086 dataBuffer.Position++; 1087 1088 if (userSettings.SafetySwitch 1089 && dataBuffer.Position 1090 - dataBuffer.ColumnStart 1091 + columnBuffer.Position > 100000) { 1092 close(); 1093 1094 throw new IOException( 1095 "Maximum column length of 100,000 exceeded in column " 1096 + NumberFormat 1097 .getIntegerInstance() 1098 .format( 1099 columnsCount) 1100 + " in record " 1101 + NumberFormat 1102 .getIntegerInstance() 1103 .format( 1104 currentRecord) 1105 + ". Set the SafetySwitch property to false" 1106 + " if you're expecting column lengths greater than 100,000 characters to" 1107 + " avoid this error."); 1108 } 1109 } 1110 } // end else 1111 } while (hasMoreData && startedColumn); 1112 } 1113 1114 if (hasMoreData) { 1115 dataBuffer.Position++; 1116 } 1117 } // end else 1118 } while (hasMoreData && !hasReadNextLine); 1119 1120 // check to see if we hit the end of the file 1121 // without processing the current record 1122 1123 if (startedColumn || lastLetter == userSettings.Delimiter) { 1124 endColumn(); 1125 1126 endRecord(); 1127 } 1128 } 1129 1130 if (userSettings.CaptureRawRecord) { 1131 if (hasMoreData) { 1132 if (rawBuffer.Position == 0) { 1133 rawRecord = new String(dataBuffer.Buffer, 1134 dataBuffer.LineStart, dataBuffer.Position 1135 - dataBuffer.LineStart - 1); 1136 } else { 1137 rawRecord = new String(rawBuffer.Buffer, 0, 1138 rawBuffer.Position) 1139 + new String(dataBuffer.Buffer, 1140 dataBuffer.LineStart, dataBuffer.Position 1141 - dataBuffer.LineStart - 1); 1142 } 1143 } else { 1144 // for hasMoreData to ever be false, all data would have had to 1145 // have been 1146 // copied to the raw buffer 1147 rawRecord = new String(rawBuffer.Buffer, 0, rawBuffer.Position); 1148 } 1149 } else { 1150 rawRecord = ""; 1151 } 1152 1153 return hasReadNextLine; 1154 } 1155 1156 /** 1157 * @exception IOException 1158 * Thrown if an error occurs while reading data from the 1159 * source stream. 1160 */ 1161 private void checkDataLength() throws IOException { 1162 if (!initialized) { 1163 if (fileName != null) { 1164 inputStream = new BufferedReader(new InputStreamReader( 1165 new FileInputStream(fileName), charset), 1166 StaticSettings.MAX_FILE_BUFFER_SIZE); 1167 } 1168 1169 charset = null; 1170 initialized = true; 1171 } 1172 1173 updateCurrentValue(); 1174 1175 if (userSettings.CaptureRawRecord && dataBuffer.Count > 0) { 1176 if (rawBuffer.Buffer.length - rawBuffer.Position < dataBuffer.Count 1177 - dataBuffer.LineStart) { 1178 int newLength = rawBuffer.Buffer.length 1179 + Math.max(dataBuffer.Count - dataBuffer.LineStart, 1180 rawBuffer.Buffer.length); 1181 1182 char[] holder = new char[newLength]; 1183 1184 System.arraycopy(rawBuffer.Buffer, 0, holder, 0, 1185 rawBuffer.Position); 1186 1187 rawBuffer.Buffer = holder; 1188 } 1189 1190 System.arraycopy(dataBuffer.Buffer, dataBuffer.LineStart, 1191 rawBuffer.Buffer, rawBuffer.Position, dataBuffer.Count 1192 - dataBuffer.LineStart); 1193 1194 rawBuffer.Position += dataBuffer.Count - dataBuffer.LineStart; 1195 } 1196 1197 try { 1198 dataBuffer.Count = inputStream.read(dataBuffer.Buffer, 0, 1199 dataBuffer.Buffer.length); 1200 } catch (IOException ex) { 1201 close(); 1202 1203 throw ex; 1204 } 1205 1206 // if no more data could be found, set flag stating that 1207 // the end of the data was found 1208 1209 if (dataBuffer.Count == -1) { 1210 hasMoreData = false; 1211 } 1212 1213 dataBuffer.Position = 0; 1214 dataBuffer.LineStart = 0; 1215 dataBuffer.ColumnStart = 0; 1216 } 1217 1218 /** 1219 * Read the first record of data as column headers. 1220 * 1221 * @return Whether the header record was successfully read or not. 1222 * @exception IOException 1223 * Thrown if an error occurs while reading data from the 1224 * source stream. 1225 */ 1226 public boolean readHeaders() throws IOException { 1227 boolean result = readRecord(); 1228 1229 // copy the header data from the column array 1230 // to the header string array 1231 1232 headersHolder.Length = columnsCount; 1233 1234 headersHolder.Headers = new String[columnsCount]; 1235 1236 for (int i = 0; i < headersHolder.Length; i++) { 1237 String columnValue = get(i); 1238 1239 headersHolder.Headers[i] = columnValue; 1240 1241 // if there are duplicate header names, we will save the last one 1242 headersHolder.IndexByName.put(columnValue, new Integer(i)); 1243 } 1244 1245 if (result) { 1246 currentRecord--; 1247 } 1248 1249 columnsCount = 0; 1250 1251 return result; 1252 } 1253 1254 /** 1255 * Returns the column header value for a given column index. 1256 * 1257 * @param columnIndex 1258 * The index of the header column being requested. 1259 * @return The value of the column header at the given column index. 1260 * @exception IOException 1261 * Thrown if this object has already been closed. 1262 */ 1263 public String getHeader(int columnIndex) throws IOException { 1264 checkClosed(); 1265 1266 if(columnIndex < 0 || columnIndex >= headersHolder.Length){ 1267 return ""; 1268 } 1269 1270 return headersHolder.Headers[columnIndex]; 1271 } 1272 1273 public boolean isQualified(int columnIndex) throws IOException { 1274 checkClosed(); 1275 1276 if (columnIndex < 0 || columnIndex >= columnsCount) { 1277 return false; 1278 } 1279 1280 return isQualified[columnIndex]; 1281 } 1282 1283 /** 1284 * @exception IOException 1285 * Thrown if a very rare extreme exception occurs during 1286 * parsing, normally resulting from improper data format. 1287 */ 1288 private void endColumn() throws IOException { 1289 String currentValue = ""; 1290 1291 // must be called before setting startedColumn = false 1292 if (startedColumn) { 1293 if (columnBuffer.Position == 0) { 1294 if (dataBuffer.ColumnStart < dataBuffer.Position) { 1295 int lastLetter = dataBuffer.Position - 1; 1296 1297 if (userSettings.TrimWhitespace && !startedWithQualifier) { 1298 while (lastLetter >= dataBuffer.ColumnStart 1299 && (dataBuffer.Buffer[lastLetter] == Letters.SPACE || dataBuffer.Buffer[lastLetter] == Letters.TAB)) { 1300 lastLetter--; 1301 } 1302 } 1303 1304 currentValue = new String(dataBuffer.Buffer, 1305 dataBuffer.ColumnStart, lastLetter 1306 - dataBuffer.ColumnStart + 1); 1307 } 1308 } else { 1309 updateCurrentValue(); 1310 1311 int lastLetter = columnBuffer.Position - 1; 1312 1313 if (userSettings.TrimWhitespace && !startedWithQualifier) { 1314 while (lastLetter >= 0 1315 && (columnBuffer.Buffer[lastLetter] == Letters.SPACE || columnBuffer.Buffer[lastLetter] == Letters.SPACE)) { 1316 lastLetter--; 1317 } 1318 } 1319 1320 currentValue = new String(columnBuffer.Buffer, 0, 1321 lastLetter + 1); 1322 } 1323 } 1324 1325 columnBuffer.Position = 0; 1326 1327 startedColumn = false; 1328 1329 if (columnsCount >= 100000 && userSettings.SafetySwitch) { 1330 close(); 1331 1332 throw new IOException( 1333 "Maximum column count of 100,000 exceeded in record " 1334 + NumberFormat.getIntegerInstance().format( 1335 currentRecord) 1336 + ". Set the SafetySwitch property to false" 1337 + " if you're expecting more than 100,000 columns per record to" 1338 + " avoid this error."); 1339 } 1340 1341 // check to see if our current holder array for 1342 // column chunks is still big enough to handle another 1343 // column chunk 1344 1345 if (columnsCount == values.length) { 1346 // holder array needs to grow to be able to hold another column 1347 int newLength = values.length * 2; 1348 1349 String[] holder = new String[newLength]; 1350 1351 System.arraycopy(values, 0, holder, 0, values.length); 1352 1353 values = holder; 1354 1355 boolean[] qualifiedHolder = new boolean[newLength]; 1356 1357 System.arraycopy(isQualified, 0, qualifiedHolder, 0, 1358 isQualified.length); 1359 1360 isQualified = qualifiedHolder; 1361 } 1362 1363 values[columnsCount] = currentValue; 1364 1365 isQualified[columnsCount] = startedWithQualifier; 1366 1367 currentValue = ""; 1368 1369 columnsCount++; 1370 } 1371 1372 private void appendLetter(char letter) { 1373 if (columnBuffer.Position == columnBuffer.Buffer.length) { 1374 int newLength = columnBuffer.Buffer.length * 2; 1375 1376 char[] holder = new char[newLength]; 1377 1378 System.arraycopy(columnBuffer.Buffer, 0, holder, 0, 1379 columnBuffer.Position); 1380 1381 columnBuffer.Buffer = holder; 1382 } 1383 columnBuffer.Buffer[columnBuffer.Position++] = letter; 1384 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1385 } 1386 1387 private void updateCurrentValue() { 1388 if (startedColumn && dataBuffer.ColumnStart < dataBuffer.Position) { 1389 if (columnBuffer.Buffer.length - columnBuffer.Position < dataBuffer.Position 1390 - dataBuffer.ColumnStart) { 1391 int newLength = columnBuffer.Buffer.length 1392 + Math.max( 1393 dataBuffer.Position - dataBuffer.ColumnStart, 1394 columnBuffer.Buffer.length); 1395 1396 char[] holder = new char[newLength]; 1397 1398 System.arraycopy(columnBuffer.Buffer, 0, holder, 0, 1399 columnBuffer.Position); 1400 1401 columnBuffer.Buffer = holder; 1402 } 1403 1404 System.arraycopy(dataBuffer.Buffer, dataBuffer.ColumnStart, 1405 columnBuffer.Buffer, columnBuffer.Position, 1406 dataBuffer.Position - dataBuffer.ColumnStart); 1407 1408 columnBuffer.Position += dataBuffer.Position 1409 - dataBuffer.ColumnStart; 1410 } 1411 1412 dataBuffer.ColumnStart = dataBuffer.Position + 1; 1413 } 1414 1415 private void endRecord() { 1416 // this flag is used as a loop exit condition 1417 // during parsing 1418 1419 hasReadNextLine = true; 1420 1421 currentRecord++; 1422 } 1423 1424 /** 1425 * Gets the corresponding column index for a given column header name. 1426 * 1427 * @param headerName 1428 * The header name of the column. 1429 * @return The column index for the given column header name. Returns 1430 * -1 if not found. 1431 * @exception IOException 1432 * Thrown if this object has already been closed. 1433 */ 1434 public int getIndex(String headerName) throws IOException { 1435 checkClosed(); 1436 1437 Object indexValue = headersHolder.IndexByName.get(headerName); 1438 1439 if(indexValue == null){ 1440 return -1; 1441 } 1442 1443 return ((Integer) indexValue).intValue(); 1444 } 1445 1446 /** 1447 * Skips the next record of data by parsing each column. Does not 1448 * increment 1449 * {@link com.csvreader.CsvReader#getCurrentRecord getCurrentRecord()}. 1450 * 1451 * @return Whether another record was successfully skipped or not. 1452 * @exception IOException 1453 * Thrown if an error occurs while reading data from the 1454 * source stream. 1455 */ 1456 public boolean skipRecord() throws IOException { 1457 checkClosed(); 1458 1459 boolean recordRead = false; 1460 1461 if (hasMoreData) { 1462 recordRead = readRecord(); 1463 1464 if (recordRead) { 1465 currentRecord--; 1466 } 1467 } 1468 1469 return recordRead; 1470 } 1471 1472 /** 1473 * Skips the next line of data using the standard end of line characters and 1474 * does not do any column delimited parsing. 1475 * 1476 * @return Whether a line was successfully skipped or not. 1477 * @exception IOException 1478 * Thrown if an error occurs while reading data from the 1479 * source stream. 1480 */ 1481 public boolean skipLine() throws IOException { 1482 checkClosed(); 1483 1484 // clear public column values for current line 1485 1486 columnsCount = 0; 1487 1488 boolean skippedLine = false; 1489 1490 if (hasMoreData) { 1491 boolean foundEol = false; 1492 1493 do { 1494 if (dataBuffer.Position == dataBuffer.Count) { 1495 checkDataLength(); 1496 } else { 1497 skippedLine = true; 1498 1499 // grab the current letter as a char 1500 1501 char currentLetter = dataBuffer.Buffer[dataBuffer.Position]; 1502 1503 if (currentLetter == Letters.CR 1504 || currentLetter == Letters.LF) { 1505 foundEol = true; 1506 } 1507 1508 // keep track of the last letter because we need 1509 // it for several key decisions 1510 1511 lastLetter = currentLetter; 1512 1513 if (!foundEol) { 1514 dataBuffer.Position++; 1515 } 1516 1517 } // end else 1518 } while (hasMoreData && !foundEol); 1519 1520 columnBuffer.Position = 0; 1521 1522 dataBuffer.LineStart = dataBuffer.Position + 1; 1523 } 1524 1525 rawBuffer.Position = 0; 1526 rawRecord = ""; 1527 1528 return skippedLine; 1529 } 1530 1531 /** 1532 * Closes and releases all related resources. 1533 */ 1534 public void close() { 1535 if (!closed) { 1536 close(true); 1537 1538 closed = true; 1539 } 1540 } 1541 1542 /** 1543 * 1544 */ 1545 private void close(boolean closing) { 1546 if (!closed) { 1547 if (closing) { 1548 charset = null; 1549 headersHolder.Headers = null; 1550 headersHolder.IndexByName = null; 1551 dataBuffer.Buffer = null; 1552 columnBuffer.Buffer = null; 1553 rawBuffer.Buffer = null; 1554 } 1555 1556 try { 1557 if (initialized) { 1558 inputStream.close(); 1559 } 1560 } catch (Exception e) { 1561 // just eat the exception 1562 } 1563 1564 inputStream = null; 1565 1566 closed = true; 1567 } 1568 } 1569 1570 /** 1571 * @exception IOException 1572 * Thrown if this object has already been closed. 1573 */ 1574 private void checkClosed() throws IOException { 1575 if (closed) { 1576 throw new IOException( 1577 "This instance of the CsvReader class has already been closed."); 1578 } 1579 } 1580 1581 /** 1582 * 1583 */ 1584 protected void finalize() { 1585 close(false); 1586 } 1587 1588 private class ComplexEscape { 1589 private static final int UNICODE = 1; 1590 1591 private static final int OCTAL = 2; 1592 1593 private static final int DECIMAL = 3; 1594 1595 private static final int HEX = 4; 1596 } 1597 1598 private static char hexToDec(char hex) { 1599 char result; 1600 1601 if (hex >= 'a') { 1602 result = (char) (hex - 'a' + 10); 1603 } else if (hex >= 'A') { 1604 result = (char) (hex - 'A' + 10); 1605 } else { 1606 result = (char) (hex - '0'); 1607 } 1608 1609 return result; 1610 } 1611 1612 private class DataBuffer { 1613 public char[] Buffer; 1614 1615 public int Position; 1616 1617 // / <summary> 1618 // / How much usable data has been read into the stream, 1619 // / which will not always be as long as Buffer.Length. 1620 // / </summary> 1621 public int Count; 1622 1623 // / <summary> 1624 // / The position of the cursor in the buffer when the 1625 // / current column was started or the last time data 1626 // / was moved out to the column buffer. 1627 // / </summary> 1628 public int ColumnStart; 1629 1630 public int LineStart; 1631 1632 public DataBuffer() { 1633 Buffer = new char[StaticSettings.MAX_BUFFER_SIZE]; 1634 Position = 0; 1635 Count = 0; 1636 ColumnStart = 0; 1637 LineStart = 0; 1638 } 1639 } 1640 1641 private class ColumnBuffer { 1642 public char[] Buffer; 1643 1644 public int Position; 1645 1646 public ColumnBuffer() { 1647 Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE]; 1648 Position = 0; 1649 } 1650 } 1651 1652 private class RawRecordBuffer { 1653 public char[] Buffer; 1654 1655 public int Position; 1656 1657 public RawRecordBuffer() { 1658 Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE 1659 * StaticSettings.INITIAL_COLUMN_COUNT]; 1660 Position = 0; 1661 } 1662 } 1663 1664 private class Letters { 1665 public static final char LF = '\n'; 1666 1667 public static final char CR = '\r'; 1668 1669 public static final char QUOTE = '"'; 1670 1671 public static final char COMMA = ','; 1672 1673 public static final char SPACE = ' '; 1674 1675 public static final char TAB = '\t'; 1676 1677 public static final char POUND = '#'; 1678 1679 public static final char BACKSLASH = '\\'; 1680 1681 public static final char NULL = '\0'; 1682 1683 public static final char BACKSPACE = '\b'; 1684 1685 public static final char FORM_FEED = '\f'; 1686 1687 public static final char ESCAPE = '\u001B'; // ASCII/ANSI escape 1688 1689 public static final char VERTICAL_TAB = '\u000B'; 1690 1691 public static final char ALERT = '\u0007'; 1692 } 1693 1694 private class UserSettings { 1695 // having these as publicly accessible members will prevent 1696 // the overhead of the method call that exists on properties 1697 public boolean CaseSensitive; 1698 1699 public char TextQualifier; 1700 1701 public boolean TrimWhitespace; 1702 1703 public boolean UseTextQualifier; 1704 1705 public char Delimiter; 1706 1707 public char RecordDelimiter; 1708 1709 public char Comment; 1710 1711 public boolean UseComments; 1712 1713 public int EscapeMode; 1714 1715 public boolean SafetySwitch; 1716 1717 public boolean SkipEmptyRecords; 1718 1719 public boolean CaptureRawRecord; 1720 1721 public UserSettings() { 1722 CaseSensitive = true; 1723 TextQualifier = Letters.QUOTE; 1724 TrimWhitespace = true; 1725 UseTextQualifier = true; 1726 Delimiter = Letters.COMMA; 1727 RecordDelimiter = Letters.NULL; 1728 Comment = Letters.POUND; 1729 UseComments = false; 1730 EscapeMode = CsvReader.ESCAPE_MODE_DOUBLED; 1731 SafetySwitch = true; 1732 SkipEmptyRecords = true; 1733 CaptureRawRecord = true; 1734 } 1735 } 1736 1737 private class HeadersHolder { 1738 public String[] Headers; 1739 1740 public int Length; 1741 1742 public HashMap<String, Integer> IndexByName; 1743 1744 public HeadersHolder() { 1745 Headers = null; 1746 Length = 0; 1747 IndexByName = new HashMap<String, Integer>(); 1748 } 1749 } 1750 1751 private class StaticSettings { 1752 // these are static instead of final so they can be changed in unit test 1753 // isn't visible outside this class and is only accessed once during 1754 // CsvReader construction 1755 public static final int MAX_BUFFER_SIZE = 1024; 1756 1757 public static final int MAX_FILE_BUFFER_SIZE = 4 * 1024; 1758 1759 public static final int INITIAL_COLUMN_COUNT = 10; 1760 1761 public static final int INITIAL_COLUMN_BUFFER_SIZE = 50; 1762 } 1763 }