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. 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" name = "Mwanji Ezana"
@ -92,8 +92,8 @@ When defaults are present, a shallow merge is performed.
## TODO ## TODO
* Support all special characters * Fail on invalid definitions
* Convert Toml to custom class * Support negative numbers
## License ## License

View file

@ -4,7 +4,9 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import org.parboiled.BaseParser; import org.parboiled.BaseParser;
import org.parboiled.Rule; import org.parboiled.Rule;
@ -16,6 +18,7 @@ class TomlParser extends BaseParser<Object> {
static class Results { static class Results {
public Map<String, Object> values = new HashMap<String, Object>(); public Map<String, Object> values = new HashMap<String, Object>();
public Set<String> keyGroups = new HashSet<String>();
public StringBuilder errors = new StringBuilder(); public StringBuilder errors = new StringBuilder();
} }
@ -24,7 +27,25 @@ class TomlParser extends BaseParser<Object> {
} }
Rule KeyGroup() { 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() { Rule Key() {
@ -60,7 +81,7 @@ class TomlParser extends BaseParser<Object> {
} }
Rule StringValue() { 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() { Rule Year() {
@ -83,6 +104,10 @@ class TomlParser extends BaseParser<Object> {
return CharRange('a', 'z'); return CharRange('a', 'z');
} }
Rule UnicodeCharacter() {
return Sequence(Sequence('\\', 'u', OneOrMore(FirstOf(CharRange('0', '9'), CharRange('A', 'F')))), pushCharacter(match()));
}
Rule SpecialCharacter() { Rule SpecialCharacter() {
return Sequence(Sequence('\\', ANY), pushCharacter(match())); return Sequence(Sequence('\\', ANY), pushCharacter(match()));
} }
@ -106,6 +131,11 @@ class TomlParser extends BaseParser<Object> {
return ZeroOrMore(FirstOf(Comment(), AnyOf(" \t\r\n\f"))); return ZeroOrMore(FirstOf(Comment(), AnyOf(" \t\r\n\f")));
} }
@SuppressNode
Rule NewLine() {
return AnyOf("\r\n");
}
@SuppressNode @SuppressNode
Rule ArrayDelimiter() { Rule ArrayDelimiter() {
return Sequence(Spacing(), ',', Spacing()); return Sequence(Spacing(), ',', Spacing());
@ -126,13 +156,20 @@ class TomlParser extends BaseParser<Object> {
} }
Map<String, Object> newKeyGroup = (Map<String, Object>) getContext().getValueStack().peek(); 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) { for (String splitKey : split) {
if (!newKeyGroup.containsKey(splitKey)) { if (!newKeyGroup.containsKey(splitKey)) {
newKeyGroup.put(splitKey, new HashMap<String, Object>()); newKeyGroup.put(splitKey, new HashMap<String, Object>());
} }
Object currentValue = newKeyGroup.get(splitKey); Object currentValue = newKeyGroup.get(splitKey);
if (!(currentValue instanceof Map)) { 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; return true;
} }
@ -140,6 +177,7 @@ class TomlParser extends BaseParser<Object> {
} }
push(newKeyGroup); push(newKeyGroup);
return true; return true;
} }
@ -213,6 +251,14 @@ class TomlParser extends BaseParser<Object> {
sb.append('\r'); sb.append('\r');
} else if (sc.equals("\\\\")) { } else if (sc.equals("\\\\")) {
sb.append('\\'); 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("\\")) { } else if (sc.startsWith("\\")) {
results().errors.append(sc + " is a reserved special character and cannot be used!\n"); results().errors.append(sc + " is a reserved special character and cannot be used!\n");
} else { } else {

View file

@ -10,6 +10,7 @@ import java.io.File;
import java.util.Calendar; import java.util.Calendar;
import java.util.TimeZone; import java.util.TimeZone;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
public class TomlTest { public class TomlTest {
@ -99,6 +100,14 @@ public class TomlTest {
assertEquals("value", toml.getKeyGroup("group.sub").getString("key")); 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 @Test
public void should_return_null_if_no_value_for_key() throws Exception { public void should_return_null_if_no_value_for_key() throws Exception {
Toml toml = new Toml().parse(""); Toml toml = new Toml().parse("");
@ -173,7 +182,14 @@ public class TomlTest {
public void should_support_special_characters_in_strings() { 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())); 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) @Test(expected = IllegalStateException.class)
@ -200,11 +216,21 @@ public class TomlTest {
@Test(expected = IllegalStateException.class) @Test(expected = IllegalStateException.class)
public void should_fail_when_key_is_overwritten_by_key_group() { 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) @Test(expected = IllegalStateException.class)
public void should_fail_when_key_is_overwritten_by_another_key() { public void should_fail_when_key_is_overwritten_by_another_key() {
new Toml().parse("[fruit]\ntype=\"apple\"\ntype=\"orange\""); 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 # ] 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"