Added support for all special and Unicode characters and checking for duplicate key groups.

This commit is contained in:
moandji.ezana 2013-03-22 10:00:37 +02:00
parent 450e7402ef
commit 2483dc48ae
5 changed files with 93 additions and 9 deletions

View file

@ -61,7 +61,7 @@ A Toml object can be mapped to a custom class with the `Toml#to(Class<T>)` metho
Any keys not found in both the TOML and the class are ignored.
Key groups can be mapped to other custom classes or to `Map<String, Object>`.
Key groups can be mapped to other custom classes.
````
name = "Mwanji Ezana"
@ -92,8 +92,8 @@ When defaults are present, a shallow merge is performed.
## TODO
* Support all special characters
* Convert Toml to custom class
* Fail on invalid definitions
* Support negative numbers
## License

View file

@ -4,7 +4,9 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.parboiled.BaseParser;
import org.parboiled.Rule;
@ -16,6 +18,7 @@ class TomlParser extends BaseParser<Object> {
static class Results {
public Map<String, Object> values = new HashMap<String, Object>();
public Set<String> keyGroups = new HashSet<String>();
public StringBuilder errors = new StringBuilder();
}
@ -24,7 +27,25 @@ class TomlParser extends BaseParser<Object> {
}
Rule KeyGroup() {
return Sequence(KeyGroupDelimiter(), KeyGroupName(), addKeyGroup((String) pop()), KeyGroupDelimiter(), Spacing());
return Sequence(Sequence(KeyGroupDelimiter(), KeyGroupName(), addKeyGroup((String) pop()), KeyGroupDelimiter(), Spacing()), checkKeyGroup(match()));
}
Rule IllegalCharacters() {
return ZeroOrMore(TestNot(NewLine()), ANY);
}
boolean checkKeyGroup(String definition) {
String afterBracket = definition.substring(definition.indexOf(']') + 1);
for (char character : afterBracket.toCharArray()) {
if (character == '#') {
return true;
}
if (!Character.isWhitespace(character)) {
results().errors.append("Invalid key group definition: ").append(definition).append(". You may have forgotten a #");
}
}
return true;
}
Rule Key() {
@ -60,7 +81,7 @@ class TomlParser extends BaseParser<Object> {
}
Rule StringValue() {
return Sequence(push(new StringBuilder()), '"', OneOrMore(TestNot('"'), FirstOf(SpecialCharacter(), AnyCharacter())), pushString(((StringBuilder) pop()).toString()), '"');
return Sequence(push(new StringBuilder()), '"', OneOrMore(TestNot('"'), FirstOf(UnicodeCharacter(), SpecialCharacter(), AnyCharacter())), pushString(((StringBuilder) pop()).toString()), '"');
}
Rule Year() {
@ -83,6 +104,10 @@ class TomlParser extends BaseParser<Object> {
return CharRange('a', 'z');
}
Rule UnicodeCharacter() {
return Sequence(Sequence('\\', 'u', OneOrMore(FirstOf(CharRange('0', '9'), CharRange('A', 'F')))), pushCharacter(match()));
}
Rule SpecialCharacter() {
return Sequence(Sequence('\\', ANY), pushCharacter(match()));
}
@ -106,6 +131,11 @@ class TomlParser extends BaseParser<Object> {
return ZeroOrMore(FirstOf(Comment(), AnyOf(" \t\r\n\f")));
}
@SuppressNode
Rule NewLine() {
return AnyOf("\r\n");
}
@SuppressNode
Rule ArrayDelimiter() {
return Sequence(Spacing(), ',', Spacing());
@ -126,13 +156,20 @@ class TomlParser extends BaseParser<Object> {
}
Map<String, Object> newKeyGroup = (Map<String, Object>) getContext().getValueStack().peek();
if (!results().keyGroups.add(name)) {
results().errors.append("Could not create key group ").append(name).append(": key group already exists!\n");
return true;
}
for (String splitKey : split) {
if (!newKeyGroup.containsKey(splitKey)) {
newKeyGroup.put(splitKey, new HashMap<String, Object>());
}
Object currentValue = newKeyGroup.get(splitKey);
if (!(currentValue instanceof Map)) {
results().errors.append("Could not create key group ").append(name).append(": key already exists!\n");
results().errors.append("Could not create key group ").append(name).append(": key already has a value!\n");
return true;
}
@ -140,6 +177,7 @@ class TomlParser extends BaseParser<Object> {
}
push(newKeyGroup);
return true;
}
@ -213,6 +251,14 @@ class TomlParser extends BaseParser<Object> {
sb.append('\r');
} else if (sc.equals("\\\\")) {
sb.append('\\');
} else if (sc.equals("\\/")) {
sb.append('/');
} else if (sc.equals("\\b")) {
sb.append('\b');
} else if (sc.equals("\\f")) {
sb.append('\f');
} else if (sc.startsWith("\\u")) {
sb.append(Character.toChars(Integer.parseInt(sc.substring(2), 16)));
} else if (sc.startsWith("\\")) {
results().errors.append(sc + " is a reserved special character and cannot be used!\n");
} else {

View file

@ -10,6 +10,7 @@ import java.io.File;
import java.util.Calendar;
import java.util.TimeZone;
import org.junit.Ignore;
import org.junit.Test;
public class TomlTest {
@ -99,6 +100,14 @@ public class TomlTest {
assertEquals("value", toml.getKeyGroup("group.sub").getString("key"));
}
@Test
public void should_get_value_from_key_group_with_sub_key_group() throws Exception {
Toml toml = new Toml().parse("[a.b]\nc=1\n[a]\nd=2");
assertEquals(2, toml.getLong("a.d").intValue());
assertEquals(1, toml.getKeyGroup("a.b").getLong("c").intValue());
}
@Test
public void should_return_null_if_no_value_for_key() throws Exception {
Toml toml = new Toml().parse("");
@ -173,7 +182,14 @@ public class TomlTest {
public void should_support_special_characters_in_strings() {
Toml toml = new Toml().parse(new File(getClass().getResource("should_support_special_characters_in_strings.toml").getFile()));
assertEquals("\" \t \n \r \\", toml.getString("key"));
assertEquals("\" \t \n \r \\ / \b \f", toml.getString("key"));
}
@Test
public void should_support_unicode_characters_in_strings() throws Exception {
Toml toml = new Toml().parse("key=\"\\u00B1\"");
assertEquals("±", toml.getString("key"));
}
@Test(expected = IllegalStateException.class)
@ -200,11 +216,21 @@ public class TomlTest {
@Test(expected = IllegalStateException.class)
public void should_fail_when_key_is_overwritten_by_key_group() {
new Toml().parse("[fruit]\ntype=\"apple\"\n[fruit.type]\napple=\"yes\"");
new Toml().parse("[a]\nb=1\n[a.b]\nc=2");
}
@Test(expected = IllegalStateException.class)
public void should_fail_when_key_is_overwritten_by_another_key() {
new Toml().parse("[fruit]\ntype=\"apple\"\ntype=\"orange\"");
}
@Test(expected = IllegalStateException.class)
public void should_fail_when_key_group_defined_twice() throws Exception {
new Toml().parse("[a]\nb=1\n[a]\nc=2");
}
@Ignore @Test(expected = IllegalStateException.class)
public void should_fail_when_illegal_characters_after_key_group() throws Exception {
new Toml().parse("[error] if you didn't catch this, your parser is broken");
}
}

View file

@ -19,3 +19,15 @@ test_string = "You'll hate me after this - #" # " Annoying, isn't it?
"]",
# ] Oh yes I did
]
# Each of the following keygroups/key value pairs should produce an error. Uncomment to them to test
[error] if you didn't catch this, your parser is broken
#string = "Anything other than tabs, spaces and newline after a keygroup or key value pair has ended should produce an error unless it is a comment" like this
#array = [
# "This might most likely happen in multiline arrays",
# Like here,
# "or here,
# and here"
# ] End of array comment, forgot the #
#number = 3.14 pi <--again forgot the #

View file

@ -1 +1 @@
key = "\" \t \n \r \\"
key = "\" \t \n \r \\ \/ \b \f"