Improved bare key validation and quoted key support

This commit is contained in:
moandji.ezana 2015-02-12 22:38:17 +02:00
parent 8a6ca61101
commit 1c87a9e85a
4 changed files with 169 additions and 67 deletions

View file

@ -41,6 +41,18 @@ class Identifier {
return name;
}
String getBareName() {
if (isKey()) {
return name;
}
if (isTable()) {
return name.substring(1, name.length() - 1);
}
return name.substring(2, name.length() - 2);
}
boolean isKey() {
return type == Type.KEY;
}
@ -71,7 +83,7 @@ class Identifier {
}
}
return sb.toString();
return StringConverter.STRING_PARSER.replaceUnicodeCharacters(sb.toString());
}
private static boolean isValidKey(String name, Context context) {
@ -102,42 +114,72 @@ class Identifier {
}
private static boolean isValidTable(String name, Context context) {
boolean valid = true;
if (!name.endsWith("]")) {
valid = false;
}
String trimmed = name.substring(1, name.length() - 1).trim();
if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith(".")) {
valid = false;
}
if (!valid) {
context.errors.invalidTable(name, context.line.get());
return false;
}
char[] chars = name.toCharArray();
char[] chars = trimmed.toCharArray();
boolean quoted = false;
boolean terminated = false;
int endIndex = -1;
boolean preKey = true;
boolean valid = true;
boolean dotAllowed = false;
boolean quoteAllowed = true;
boolean charAllowed = true;
for (int i = 1; i < name.length() - 1; i++) {
char c = name.charAt(i);
if (c == '"' && chars[i - 1] != '\\') {
if (!quoted && i > 1 && chars [i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) {
valid = false;
break;
}
quoted = !quoted;
} else if (!quoted && c == '.') {
preKey = true;
} else if (!quoted && Character.isWhitespace(c)) {
if (preKey && i > 1 && chars[i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) {
valid = false;
break;
}
if (!preKey && chars.length > i + 1 && chars[i + 1] != '.' && chars[i + 1] != ']' && !Character.isWhitespace(chars[i + 1])) {
valid = false;
break;
}
} else if (!quoted && (ALLOWED_CHARS.indexOf(c) == -1)) {
valid = false;
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (!valid) {
break;
} else if (!quoted) {
preKey = false;
}
if (c == '"') {
if (!quoteAllowed) {
valid = false;
} else if (quoted && chars[i - 1] != '\\') {
charAllowed = false;
dotAllowed = true;
quoteAllowed = false;
} else if (!quoted) {
quoted = true;
quoteAllowed = true;
}
} else if (quoted) {
continue;
} else if (c == '.') {
if (dotAllowed) {
charAllowed = true;
dotAllowed = false;
quoteAllowed = true;
} else {
context.errors.emptyImplicitTable(name, context.line.get());
return false;
}
} else if (Character.isWhitespace(c)) {
char prev = chars[i - 1];
if (!Character.isWhitespace(prev) && prev != '.' && prev != '"') {
charAllowed = false;
dotAllowed = true;
quoteAllowed = true;
}
} else {
if (charAllowed && ALLOWED_CHARS_KEYS.indexOf(c) > -1) {
charAllowed = true;
dotAllowed = true;
quoteAllowed = false;
} else {
valid = false;
}
}
}
@ -151,47 +193,81 @@ class Identifier {
}
private static boolean isValidTableArray(String line, Context context) {
if (!line.endsWith("]]") || line.substring(2, line.length() - 2).trim().isEmpty()) {
context.errors.invalidTableArray(line, context.line.get());
return false;
}
char[] chars = line.toCharArray();
boolean quoted = false;
boolean preKey = true;
boolean valid = true;
for (int i = 2; i < line.length() - 2; i++) {
if (!line.endsWith("]]")) {
valid = false;
}
String trimmed = line.substring(2, line.length() - 2).trim();
if (trimmed.isEmpty() || trimmed.charAt(0) == '.' || trimmed.endsWith(".")) {
valid = false;
}
if (!valid) {
context.errors.invalidTableArray(line, context.line.get());
return false;
}
char[] chars = trimmed.toCharArray();
boolean quoted = false;
boolean dotAllowed = false;
boolean quoteAllowed = true;
boolean charAllowed = true;
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c == '"' && chars[i - 1] != '\\') {
if (!quoted && i > 1 && chars [i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) {
valid = false;
}
quoted = !quoted;
} else if (!quoted && c == '.') {
preKey = true;
} else if (!quoted && Character.isWhitespace(c)) {
if (preKey && i > 2 && chars[i - 1] != '.' && !Character.isWhitespace(chars[i - 1])) {
valid = false;
}
if (!preKey && chars.length > i + 1 && chars[i + 1] != '.' && chars[i + 1] != ']' && !Character.isWhitespace(chars[i + 1])) {
valid = false;
break;
}
} else if (!quoted && (ALLOWED_CHARS.indexOf(c) == -1)) {
valid = false;
} else if (!valid) {
if (!valid) {
break;
}
if (c == '"') {
if (!quoteAllowed) {
valid = false;
} else if (quoted && chars[i - 1] != '\\') {
charAllowed = false;
dotAllowed = true;
quoteAllowed = false;
} else if (!quoted) {
quoted = true;
quoteAllowed = true;
}
} else if (quoted) {
continue;
} else if (c == '.') {
if (dotAllowed) {
charAllowed = true;
dotAllowed = false;
quoteAllowed = true;
} else {
context.errors.emptyImplicitTable(line, context.line.get());
return false;
}
} else if (Character.isWhitespace(c)) {
char prev = chars[i - 1];
if (!Character.isWhitespace(prev) && prev != '.' && prev != '"') {
charAllowed = false;
dotAllowed = true;
quoteAllowed = true;
}
} else {
preKey = false;
if (charAllowed && ALLOWED_CHARS_KEYS.indexOf(c) > -1) {
charAllowed = true;
dotAllowed = true;
quoteAllowed = false;
} else {
valid = false;
}
}
}
if (!valid) {
context.errors.invalidTableArray(line, context.line.get());
return false;
}
return valid;
return true;
}
}

View file

@ -162,7 +162,8 @@ class Results {
}
}
void startTableArray(String tableName) {
void startTableArray(Identifier identifier) {
String tableName = identifier.getBareName();
while (stack.size() > 1) {
stack.pop();
}
@ -201,15 +202,11 @@ class Results {
}
void startTables(Identifier id) {
String tableName = id.getName().substring(1, id.getName().length() - 1);
String tableName = id.getBareName();
if (!tables.add(tableName)) {
errors.duplicateTable(tableName, -1);
}
if (tableName.endsWith(".")) {
errors.emptyImplicitTable(tableName, -1);
}
while (stack.size() > 1) {
stack.pop();
}
@ -219,7 +216,6 @@ class Results {
String tablePart = tableParts[i].name;
Container currentContainer = stack.peek();
if (tablePart.isEmpty()) {
errors.emptyImplicitTable(tableName, -1);
} else if (currentContainer.get(tablePart) instanceof Container) {
Container nextTable = (Container) currentContainer.get(tablePart);
stack.push(nextTable);

View file

@ -34,7 +34,7 @@ class TomlParser {
} else if (id.isTable()) {
results.startTables(id);
} else if (id.isTableArray()) {
results.startTableArray(Keys.getTableArrayName(id.getName()));
results.startTableArray(id);
}
}
} else if (c == '\n') {

View file

@ -45,6 +45,11 @@ public class BareKeysTest {
public void should_fail_when_characters_outside_accept_range_are_used_in_table_name() throws Exception {
new Toml().parse("[~]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_when_characters_outside_accept_range_are_used_in_table_array_name() throws Exception {
new Toml().parse("[[~]]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_when_dots_in_key_name() throws Exception {
@ -66,6 +71,16 @@ public class BareKeysTest {
new Toml().parse("[valid key]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_on_sharp_sign_in_table_array_name() throws Exception {
new Toml().parse("[[group#]]\nkey=1");
}
@Test(expected = IllegalStateException.class)
public void should_fail_on_spaces_in_table_array_name() throws Exception {
new Toml().parse("[[valid key]]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_on_question_marks_in_key_name() throws Exception {
new Toml().parse("key?=true");
@ -90,4 +105,19 @@ public class BareKeysTest {
public void should_fail_on_nested_table_name_starting_with_empty_table_name() {
new Toml().parse("[.b]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_on_nested_table_array_name_ending_with_empty_table_name() {
new Toml().parse("[[a.]]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_on_nested_table_array_name_containing_empty_table_name() {
new Toml().parse("[[a..b]]");
}
@Test(expected = IllegalStateException.class)
public void should_fail_on_nested_table_array_name_starting_with_empty_table_name() {
new Toml().parse("[[.b]]");
}
}