AI2 Component  (Version nb184)
CsvUtil.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2009-2011 Google, All Rights reserved
3 // Copyright 2011-2012 MIT, All rights reserved
4 // Released under the Apache License, Version 2.0
5 // http://www.apache.org/licenses/LICENSE-2.0
6 
7 package com.google.appinventor.components.runtime.util;
8 
10 
11 import java.io.IOException;
12 import java.io.Reader;
13 import java.io.StringReader;
14 import java.util.ArrayList;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.regex.Pattern;
18 
24 public final class CsvUtil {
25 
26  private CsvUtil() {
27  }
28 
29  public static YailList fromCsvTable(String csvString) throws Exception {
30  CsvParser csvParser = new CsvParser(new StringReader(csvString));
31  ArrayList<YailList> csvList = new ArrayList<YailList>();
32  while (csvParser.hasNext()) {
33  csvList.add(YailList.makeList(csvParser.next()));
34  }
35  csvParser.throwAnyProblem();
36  return YailList.makeList(csvList);
37  }
38 
39  public static YailList fromCsvRow(String csvString) throws Exception {
40  CsvParser csvParser = new CsvParser(new StringReader(csvString));
41  if (csvParser.hasNext()) {
42  YailList row = YailList.makeList(csvParser.next());
43  if (csvParser.hasNext()) {
44  // more than one row is an error
45  throw new IllegalArgumentException("CSV text has multiple rows. Expected just one row.");
46  }
47  csvParser.throwAnyProblem();
48  return row;
49  }
50  throw new IllegalArgumentException("CSV text cannot be parsed as a row.");
51  }
52 
53  // Requires: elements of csvRow are strings
54  public static String toCsvRow(YailList csvRow) {
55  StringBuilder csvStringBuilder = new StringBuilder();
56  makeCsvRow(csvRow, csvStringBuilder);
57  return csvStringBuilder.toString();
58  }
59 
60  // Requires: elements of rows are strings
61  // TODO(sharon): do we want to enforce any consistency constraints here, e.g.,
62  // all rows have same number of elements?
63  public static String toCsvTable(YailList csvList) {
64  StringBuilder csvStringBuilder = new StringBuilder();
65  for (Object rowObj : csvList.toArray()) {
66  makeCsvRow((YailList) rowObj, csvStringBuilder);
67  // http://tools.ietf.org/html/rfc4180 suggests that CSV lines should be
68  // terminated
69  // by CRLF, hence the \r\n.
70  csvStringBuilder.append("\r\n");
71  }
72  return csvStringBuilder.toString();
73  }
74 
75  private static void makeCsvRow(YailList row, StringBuilder csvStringBuilder) {
76  String fieldDelim = "";
77  for (Object fieldObj : row.toArray()) {
78  String field = fieldObj.toString();
79  field = field.replaceAll("\"", "\"\"");
80  csvStringBuilder.append(fieldDelim).append("\"").append(field).append("\"");
81  fieldDelim = ",";
82  }
83  }
84 
85  /*
86  * Note: The CsvParser class was adapted from
87  * java/com/google/devtools/ode/server/util/CsvParser.java, which in turn was
88  * copied from: java/com/google/collaboration/tables/util/CsvParser.java
89  *
90  */
91  private static class CsvParser implements Iterator<List<String>> {
95  private final Pattern ESCAPED_QUOTE_PATTERN = Pattern.compile("\"\"");
96 
104  private final char[] buf = new char[10240];
105 
106  private final Reader in;
107 
113  private int pos;
114 
118  private int limit;
119 
123  private boolean opened = true;
124 
130  private int cellLength = -1;
131 
138  private int delimitedCellLength = -1;
139 
144  private Exception lastException;
145 
146  private long previouslyRead;
147 
148  public CsvParser(Reader in) {
149  this.in = in;
150  }
151 
152  public void skip(long charPosition) throws IOException {
153  while (charPosition > 0) {
154  int n = in.read(buf, 0, Math.min((int) charPosition, buf.length));
155  if (n < 0) {
156  break;
157  }
158  previouslyRead += n;
159  charPosition -= n;
160  }
161  }
162 
163  public boolean hasNext() {
164  if (limit == 0) {
165  fill();
166  }
167  return (pos < limit || indexAfterCompactionAndFilling(pos) < limit) && lookingAtCell();
168  }
169 
170  public ArrayList<String> next() {
171  ArrayList<String> result = Lists.newArrayList();
172  boolean trailingComma;
173  boolean haveMoreData;
174  do {
175  // Invariant: pos < limit && lookingAtCell() from hasNext() or previous
176  // iteration
177  if (buf[pos] != '"') {
178  // trim the string tokens we pull from the CSV entries, since it's common to include
179  // leading an trailing spaces here
180  result.add(new String(buf, pos, cellLength).trim());
181  } else {
182  String cell = new String(buf, pos + 1, cellLength - 2);
183  result.add(ESCAPED_QUOTE_PATTERN.matcher(cell).replaceAll("\"").trim());
184  }
185  trailingComma = delimitedCellLength > 0 && buf[pos + delimitedCellLength - 1] == ',';
186  pos += delimitedCellLength;
187  delimitedCellLength = cellLength = -1;
188  haveMoreData = pos < limit || indexAfterCompactionAndFilling(pos) < limit;
189  } while (trailingComma && haveMoreData && lookingAtCell());
190  return result;
191  }
192 
193  public long getCharPosition() {
194  return previouslyRead + pos;
195  }
196 
201  private int indexAfterCompactionAndFilling(int i) {
202  if (pos > 0) {
203  i = compact(i);
204  }
205  fill();
206  return i;
207  }
208 
213  private int compact(int i) {
214  int oldPos = pos;
215  pos = 0;
216  int toMove = limit - oldPos;
217  if (toMove > 0) {
218  System.arraycopy(buf, oldPos, buf, 0, toMove);
219  }
220  limit -= oldPos;
221  previouslyRead += oldPos;
222  return i - oldPos;
223  }
224 
228  private void fill() {
229  int toFill = buf.length - limit;
230  while (opened && toFill > 0) {
231  try {
232  int n = in.read(buf, limit, toFill);
233  if (n == -1) {
234  opened = false;
235  } else {
236  limit += n;
237  toFill -= n;
238  }
239  } catch (IOException e) {
240  lastException = e;
241  opened = false;
242  }
243  }
244  }
245 
246  private boolean lookingAtCell() {
247  return (buf[pos] == '"' ? findUnescapedEndQuote(pos + 1) : findUnquotedCellEnd(pos));
248  }
249 
250  private boolean findUnescapedEndQuote(int i) {
251  for (; i < limit || (i = indexAfterCompactionAndFilling(i)) < limit; i++) {
252  if (buf[i] == '"') {
253  i = checkedIndex(i + 1);
254  if (i == limit || buf[i] != '"') {
255  cellLength = i - pos;
256  return findDelimOrEnd(i);
257  }
258  }
259  }
260  lastException = new IllegalArgumentException("Syntax Error. unclosed quoted cell");
261  return false;
262  }
263 
268  private boolean findDelimOrEnd(int i) {
269  for (; i < limit || (i = indexAfterCompactionAndFilling(i)) < limit; i++) {
270  switch (buf[i]) {
271  case ' ':
272  case '\t':
273  // whitespace after closing quote
274  continue;
275  case '\r':
276  // In standard CSV \r\n terminates a cell. However, Macintosh uses
277  // one \r instead of \n.
278  int j = checkedIndex(i + 1);
279  delimitedCellLength = (buf[j] == '\n' ? checkedIndex(j + 1) : j) - pos;
280  return true;
281  case ',':
282  case '\n':
283  delimitedCellLength = (checkedIndex(i + 1) - pos);
284  return true;
285  default:
286  lastException = new IOException(
287  "Syntax Error: non-whitespace between closing quote and delimiter or end");
288  return false;
289  }
290  }
291  delimitedCellLength = (limit - pos);
292  return true;
293  }
294 
300  private int checkedIndex(int i) {
301  return i < limit ? i : indexAfterCompactionAndFilling(i);
302  }
303 
304  private boolean findUnquotedCellEnd(int i) {
305  for (; i < limit || (i = indexAfterCompactionAndFilling(i)) < limit; i++) {
306  switch (buf[i]) {
307  case ',':
308  case '\n':
309  cellLength = i - pos;
310  delimitedCellLength = cellLength + 1;
311  return true;
312  case '\r':
313  // In standard CSV \r\n terminates a cell. However, Macintosh uses
314  // one \r instead of \n.
315  cellLength = i - pos;
316  int j = checkedIndex(i + 1);
317  delimitedCellLength = (buf[j] == '\n' ? checkedIndex(j + 1) : j) - pos;
318  return true;
319  case '"':
320  lastException = new IllegalArgumentException("Syntax Error: quote in unquoted cell");
321  return false;
322  }
323  }
324  delimitedCellLength = cellLength = (limit - pos);
325  return true;
326  }
327 
328  public void remove() {
329  throw new UnsupportedOperationException();
330  }
331 
332  public void throwAnyProblem() throws Exception {
333  if (lastException != null) {
334  throw lastException;
335  }
336  }
337  }
338 }
com.google.appinventor.components.runtime.util.YailList
Definition: YailList.java:26
com.google.appinventor.components
com.google.appinventor.components.runtime.util.YailList.toArray
Object[] toArray()
Definition: YailList.java:97
com.google.appinventor.components.runtime.util.YailList.makeList
static YailList makeList(Object[] objects)
Definition: YailList.java:59
com.google.appinventor.components.runtime.util.CsvUtil.toCsvTable
static String toCsvTable(YailList csvList)
Definition: CsvUtil.java:63
com.google.appinventor.components.runtime.collect
Definition: Lists.java:7
com.google.appinventor.components.runtime.util.CsvUtil.toCsvRow
static String toCsvRow(YailList csvRow)
Definition: CsvUtil.java:54
com.google.appinventor.components.runtime.util.CsvUtil.fromCsvRow
static YailList fromCsvRow(String csvString)
Definition: CsvUtil.java:39
com.google.appinventor.components.runtime.util.CsvUtil.fromCsvTable
static YailList fromCsvTable(String csvString)
Definition: CsvUtil.java:29
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.collect.Lists
Definition: Lists.java:20
com.google.appinventor.components.runtime.util.CsvUtil
Definition: CsvUtil.java:24
com.google
com
com.google.appinventor