add ability for custom delimiters

This commit is contained in:
mameri 2024-02-09 11:52:18 +01:00
parent 010e83b925
commit 72214f1b43
2 changed files with 164 additions and 79 deletions

View File

@ -5,15 +5,15 @@ Public Domain.
*/ */
/** /**
* This provides static methods to convert comma delimited text into a * This provides static methods to convert comma (or otherwise) delimited text into a
* JSONArray, and to convert a JSONArray into comma delimited text. Comma * JSONArray, and to convert a JSONArray into comma (or otherwise) delimited text. Comma
* delimited text is a very popular format for data interchange. It is * delimited text is a very popular format for data interchange. It is
* understood by most database, spreadsheet, and organizer programs. * understood by most database, spreadsheet, and organizer programs.
* <p> * <p>
* Each row of text represents a row in a table or a data record. Each row * Each row of text represents a row in a table or a data record. Each row
* ends with a NEWLINE character. Each row contains one or more values. * ends with a NEWLINE character. Each row contains one or more values.
* Values are separated by commas. A value can contain any character except * Values are separated by commas. A value can contain any character except
* for comma, unless is is wrapped in single quotes or double quotes. * for comma, unless it is wrapped in single quotes or double quotes.
* <p> * <p>
* The first row usually contains the names of the columns. * The first row usually contains the names of the columns.
* <p> * <p>
@ -29,50 +29,48 @@ public class CDL {
* Get the next value. The value can be wrapped in quotes. The value can * Get the next value. The value can be wrapped in quotes. The value can
* be empty. * be empty.
* @param x A JSONTokener of the source text. * @param x A JSONTokener of the source text.
* @param delimiter used in the file
* @return The value string, or null if empty. * @return The value string, or null if empty.
* @throws JSONException if the quoted string is badly formed. * @throws JSONException if the quoted string is badly formed.
*/ */
private static String getValue(JSONTokener x) throws JSONException { private static String getValue(JSONTokener x, char delimiter) throws JSONException {
char c; char c;
char q; char q;
StringBuilder sb; StringBuilder sb;
do { do {
c = x.next(); c = x.next();
} while (c == ' ' || c == '\t'); } while (c == ' ' || c == '\t');
switch (c) { if (c == 0) {
case 0: return null;
return null; } else if (c == '"' || c == '\'') {
case '"': q = c;
case '\'': sb = new StringBuilder();
q = c; for (;;) {
sb = new StringBuilder(); c = x.next();
for (;;) { if (c == q) {
c = x.next(); //Handle escaped double-quote
if (c == q) { char nextC = x.next();
//Handle escaped double-quote if (nextC != '\"') {
char nextC = x.next(); // if our quote was the end of the file, don't step
if(nextC != '\"') { if (nextC > 0) {
// if our quote was the end of the file, don't step x.back();
if(nextC > 0) { }
x.back(); break;
} }
break; }
} if (c == 0 || c == '\n' || c == '\r') {
} throw x.syntaxError("Missing close quote '" + q + "'.");
if (c == 0 || c == '\n' || c == '\r') { }
throw x.syntaxError("Missing close quote '" + q + "'."); sb.append(c);
} }
sb.append(c); return sb.toString();
} } else if (c == delimiter) {
return sb.toString(); x.back();
case ',': return "";
x.back(); }
return ""; x.back();
default: return x.nextTo(delimiter);
x.back(); }
return x.nextTo(',');
}
}
/** /**
* Produce a JSONArray of strings from a row of comma delimited values. * Produce a JSONArray of strings from a row of comma delimited values.
@ -81,17 +79,25 @@ public class CDL {
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException { public static JSONArray rowToJSONArray(JSONTokener x) throws JSONException {
return rowToJSONArray(x, ',');
}
/**
* Same as {@link #rowToJSONArray(JSONTokener)}, but with a custom delimiter.
* @see #rowToJSONArray(JSONTokener)
*/
public static JSONArray rowToJSONArray(JSONTokener x, char delimiter) throws JSONException {
JSONArray ja = new JSONArray(); JSONArray ja = new JSONArray();
for (;;) { for (;;) {
String value = getValue(x); String value = getValue(x,delimiter);
char c = x.next(); char c = x.next();
if (value == null || if (value == null ||
(ja.length() == 0 && value.length() == 0 && c != ',')) { (ja.length() == 0 && value.length() == 0 && c != delimiter)) {
return null; return null;
} }
ja.put(value); ja.put(value);
for (;;) { for (;;) {
if (c == ',') { if (c == delimiter) {
break; break;
} }
if (c != ' ') { if (c != ' ') {
@ -116,9 +122,17 @@ public class CDL {
* @return A JSONObject combining the names and values. * @return A JSONObject combining the names and values.
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x) throws JSONException {
throws JSONException { return rowToJSONObject(names, x, ',');
JSONArray ja = rowToJSONArray(x); }
/**
* Same as {@link #rowToJSONObject(JSONArray, JSONTokener)}, but with a custom {@code delimiter}.
*
* @see #rowToJSONObject(JSONArray, JSONTokener)
*/
public static JSONObject rowToJSONObject(JSONArray names, JSONTokener x, char delimiter) throws JSONException {
JSONArray ja = rowToJSONArray(x, delimiter);
return ja != null ? ja.toJSONObject(names) : null; return ja != null ? ja.toJSONObject(names) : null;
} }
@ -130,15 +144,23 @@ public class CDL {
* @return A string ending in NEWLINE. * @return A string ending in NEWLINE.
*/ */
public static String rowToString(JSONArray ja) { public static String rowToString(JSONArray ja) {
return rowToString(ja, ',');
}
/**
* Same as {@link #rowToString(JSONArray)}, but with a custom delimiter.
* @see #rowToString(JSONArray)
*/
public static String rowToString(JSONArray ja, char delimiter) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < ja.length(); i += 1) { for (int i = 0; i < ja.length(); i += 1) {
if (i > 0) { if (i > 0) {
sb.append(','); sb.append(delimiter);
} }
Object object = ja.opt(i); Object object = ja.opt(i);
if (object != null) { if (object != null) {
String string = object.toString(); String string = object.toString();
if (string.length() > 0 && (string.indexOf(',') >= 0 || if (string.length() > 0 && (string.indexOf(delimiter) >= 0 ||
string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 || string.indexOf('\n') >= 0 || string.indexOf('\r') >= 0 ||
string.indexOf(0) >= 0 || string.charAt(0) == '"')) { string.indexOf(0) >= 0 || string.charAt(0) == '"')) {
sb.append('"'); sb.append('"');
@ -167,7 +189,15 @@ public class CDL {
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static JSONArray toJSONArray(String string) throws JSONException { public static JSONArray toJSONArray(String string) throws JSONException {
return toJSONArray(new JSONTokener(string)); return toJSONArray(string, ',');
}
/**
* Same as {@link #toJSONArray(String)}, but with a custom delimiter.
* @see #toJSONArray(String)
*/
public static JSONArray toJSONArray(String string, char delimiter) throws JSONException {
return toJSONArray(new JSONTokener(string), delimiter);
} }
/** /**
@ -178,7 +208,15 @@ public class CDL {
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static JSONArray toJSONArray(JSONTokener x) throws JSONException { public static JSONArray toJSONArray(JSONTokener x) throws JSONException {
return toJSONArray(rowToJSONArray(x), x); return toJSONArray(x, ',');
}
/**
* Same as {@link #toJSONArray(JSONTokener)}, but with a custom delimiter.
* @see #toJSONArray(JSONTokener)
*/
public static JSONArray toJSONArray(JSONTokener x, char delimiter) throws JSONException {
return toJSONArray(rowToJSONArray(x, delimiter), x, delimiter);
} }
/** /**
@ -189,9 +227,16 @@ public class CDL {
* @return A JSONArray of JSONObjects. * @return A JSONArray of JSONObjects.
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static JSONArray toJSONArray(JSONArray names, String string) public static JSONArray toJSONArray(JSONArray names, String string) throws JSONException {
throws JSONException { return toJSONArray(names, string, ',');
return toJSONArray(names, new JSONTokener(string)); }
/**
* Same as {@link #toJSONArray(JSONArray, String)}, but with a custom delimiter.
* @see #toJSONArray(JSONArray, String)
*/
public static JSONArray toJSONArray(JSONArray names, String string, char delimiter) throws JSONException {
return toJSONArray(names, new JSONTokener(string), delimiter);
} }
/** /**
@ -202,14 +247,21 @@ public class CDL {
* @return A JSONArray of JSONObjects. * @return A JSONArray of JSONObjects.
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static JSONArray toJSONArray(JSONArray names, JSONTokener x) public static JSONArray toJSONArray(JSONArray names, JSONTokener x) throws JSONException {
throws JSONException { return toJSONArray(names, x, ',');
}
/**
* Same as {@link #toJSONArray(JSONArray, JSONTokener)}, but with a custom delimiter.
* @see #toJSONArray(JSONArray, JSONTokener)
*/
public static JSONArray toJSONArray(JSONArray names, JSONTokener x, char delimiter) throws JSONException {
if (names == null || names.length() == 0) { if (names == null || names.length() == 0) {
return null; return null;
} }
JSONArray ja = new JSONArray(); JSONArray ja = new JSONArray();
for (;;) { for (;;) {
JSONObject jo = rowToJSONObject(names, x); JSONObject jo = rowToJSONObject(names, x, delimiter);
if (jo == null) { if (jo == null) {
break; break;
} }
@ -231,11 +283,19 @@ public class CDL {
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static String toString(JSONArray ja) throws JSONException { public static String toString(JSONArray ja) throws JSONException {
return toString(ja, ',');
}
/**
* Same as {@link #toString(JSONArray)}, but with a custom delimiter.
* @see #toString(JSONArray)
*/
public static String toString(JSONArray ja, char delimiter) throws JSONException {
JSONObject jo = ja.optJSONObject(0); JSONObject jo = ja.optJSONObject(0);
if (jo != null) { if (jo != null) {
JSONArray names = jo.names(); JSONArray names = jo.names();
if (names != null) { if (names != null) {
return rowToString(names) + toString(names, ja); return rowToString(names, delimiter) + toString(names, ja, delimiter);
} }
} }
return null; return null;
@ -250,8 +310,15 @@ public class CDL {
* @return A comma delimited text. * @return A comma delimited text.
* @throws JSONException if a called function fails * @throws JSONException if a called function fails
*/ */
public static String toString(JSONArray names, JSONArray ja) public static String toString(JSONArray names, JSONArray ja) throws JSONException {
throws JSONException { return toString(names, ja, ',');
}
/**
* Same as {@link #toString(JSONArray,JSONArray)}, but with a custom delimiter.
* @see #toString(JSONArray,JSONArray)
*/
public static String toString(JSONArray names, JSONArray ja, char delimiter) throws JSONException {
if (names == null || names.length() == 0) { if (names == null || names.length() == 0) {
return null; return null;
} }
@ -259,7 +326,7 @@ public class CDL {
for (int i = 0; i < ja.length(); i += 1) { for (int i = 0; i < ja.length(); i += 1) {
JSONObject jo = ja.optJSONObject(i); JSONObject jo = ja.optJSONObject(i);
if (jo != null) { if (jo != null) {
sb.append(rowToString(jo.toJSONArray(names))); sb.append(rowToString(jo.toJSONArray(names), delimiter));
} }
} }
return sb.toString(); return sb.toString();

View File

@ -24,14 +24,13 @@ public class CDLTest {
* String of lines where the column names are in the first row, * String of lines where the column names are in the first row,
* and all subsequent rows are values. All keys and values should be legal. * and all subsequent rows are values. All keys and values should be legal.
*/ */
String lines = new String( private static final String LINES = "Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" +
"Col 1, Col 2, \tCol 3, Col 4, Col 5, Col 6, Col 7\n" + "val1, val2, val3, val4, val5, val6, val7\n" +
"val1, val2, val3, val4, val5, val6, val7\n" + "1, 2, 3, 4\t, 5, 6, 7\n" +
"1, 2, 3, 4\t, 5, 6, 7\n" + "true, false, true, true, false, false, false\n" +
"true, false, true, true, false, false, false\n" + "0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" +
"0.23, 57.42, 5e27, -234.879, 2.34e5, 0.0, 9e-3\n" + "\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", va'l6, val7\n";
"\"va\tl1\", \"v\bal2\", \"val3\", \"val\f4\", \"val5\", va\'l6, val7\n"
);
/** /**
* CDL.toJSONArray() adds all values as strings, with no filtering or * CDL.toJSONArray() adds all values as strings, with no filtering or
@ -39,12 +38,11 @@ public class CDLTest {
* values all must be quoted in the cases where the JSONObject parsing * values all must be quoted in the cases where the JSONObject parsing
* might normally convert the value into a non-string. * might normally convert the value into a non-string.
*/ */
String expectedLines = new String( private static final String EXPECTED_LINES = "[{Col 1:val1, Col 2:val2, Col 3:val3, Col 4:val4, Col 5:val5, Col 6:val6, Col 7:val7}, " +
"[{Col 1:val1, Col 2:val2, Col 3:val3, Col 4:val4, Col 5:val5, Col 6:val6, Col 7:val7}, "+ "{Col 1:\"1\", Col 2:\"2\", Col 3:\"3\", Col 4:\"4\", Col 5:\"5\", Col 6:\"6\", Col 7:\"7\"}, " +
"{Col 1:\"1\", Col 2:\"2\", Col 3:\"3\", Col 4:\"4\", Col 5:\"5\", Col 6:\"6\", Col 7:\"7\"}, "+ "{Col 1:\"true\", Col 2:\"false\", Col 3:\"true\", Col 4:\"true\", Col 5:\"false\", Col 6:\"false\", Col 7:\"false\"}, " +
"{Col 1:\"true\", Col 2:\"false\", Col 3:\"true\", Col 4:\"true\", Col 5:\"false\", Col 6:\"false\", Col 7:\"false\"}, "+ "{Col 1:\"0.23\", Col 2:\"57.42\", Col 3:\"5e27\", Col 4:\"-234.879\", Col 5:\"2.34e5\", Col 6:\"0.0\", Col 7:\"9e-3\"}, " +
"{Col 1:\"0.23\", Col 2:\"57.42\", Col 3:\"5e27\", Col 4:\"-234.879\", Col 5:\"2.34e5\", Col 6:\"0.0\", Col 7:\"9e-3\"}, "+ "{Col 1:\"va\tl1\", Col 2:\"v\bal2\", Col 3:val3, Col 4:\"val\f4\", Col 5:val5, Col 6:va'l6, Col 7:val7}]";
"{Col 1:\"va\tl1\", Col 2:\"v\bal2\", Col 3:val3, Col 4:\"val\f4\", Col 5:val5, Col 6:va\'l6, Col 7:val7}]");
/** /**
* Attempts to create a JSONArray from a null string. * Attempts to create a JSONArray from a null string.
@ -194,8 +192,7 @@ public class CDLTest {
public void emptyString() { public void emptyString() {
String emptyStr = ""; String emptyStr = "";
JSONArray jsonArray = CDL.toJSONArray(emptyStr); JSONArray jsonArray = CDL.toJSONArray(emptyStr);
assertTrue("CDL should return null when the input string is empty", assertNull("CDL should return null when the input string is empty", jsonArray);
jsonArray == null);
} }
/** /**
@ -254,7 +251,7 @@ public class CDLTest {
jsonObject.put("Col \r1", "V1"); jsonObject.put("Col \r1", "V1");
// \r will be filtered from value // \r will be filtered from value
jsonObject.put("Col 2", "V2\r"); jsonObject.put("Col 2", "V2\r");
assertTrue("expected length should be 1",jsonArray.length() == 1); assertEquals("expected length should be 1", 1, jsonArray.length());
String cdlStr = CDL.toString(jsonArray); String cdlStr = CDL.toString(jsonArray);
jsonObject = jsonArray.getJSONObject(0); jsonObject = jsonArray.getJSONObject(0);
assertTrue(cdlStr.contains("\"Col 1\"")); assertTrue(cdlStr.contains("\"Col 1\""));
@ -268,8 +265,15 @@ public class CDLTest {
*/ */
@Test @Test
public void textToJSONArray() { public void textToJSONArray() {
JSONArray jsonArray = CDL.toJSONArray(this.lines); JSONArray jsonArray = CDL.toJSONArray(LINES);
JSONArray expectedJsonArray = new JSONArray(this.expectedLines); JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
}
@Test
public void textToJSONArrayPipeDelimited() {
char delimiter = '|';
JSONArray jsonArray = CDL.toJSONArray(LINES.replaceAll(",", String.valueOf(delimiter)), delimiter);
JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray); Util.compareActualVsExpectedJsonArrays(jsonArray, expectedJsonArray);
} }
@ -293,10 +297,24 @@ public class CDLTest {
*/ */
@Test @Test
public void textToJSONArrayAndBackToString() { public void textToJSONArrayAndBackToString() {
JSONArray jsonArray = CDL.toJSONArray(this.lines); JSONArray jsonArray = CDL.toJSONArray(LINES);
String jsonStr = CDL.toString(jsonArray); String jsonStr = CDL.toString(jsonArray);
JSONArray finalJsonArray = CDL.toJSONArray(jsonStr); JSONArray finalJsonArray = CDL.toJSONArray(jsonStr);
JSONArray expectedJsonArray = new JSONArray(this.expectedLines); JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
}
/**
* Create a JSONArray from a string of lines,
* then convert to string and then back to JSONArray
* with a custom delimiter
*/
@Test
public void textToJSONArrayAndBackToStringCustomDelimiter() {
JSONArray jsonArray = CDL.toJSONArray(LINES, ',');
String jsonStr = CDL.toString(jsonArray, ';');
JSONArray finalJsonArray = CDL.toJSONArray(jsonStr, ';');
JSONArray expectedJsonArray = new JSONArray(EXPECTED_LINES);
Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray); Util.compareActualVsExpectedJsonArrays(finalJsonArray, expectedJsonArray);
} }