mirror of
https://github.com/plexusorg/toml4j.git
synced 2024-12-28 19:24:15 +00:00
Added support for all special and Unicode characters and checking for duplicate key groups.
This commit is contained in:
parent
450e7402ef
commit
2483dc48ae
5 changed files with 93 additions and 9 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 #
|
|
@ -1 +1 @@
|
|||
key = "\" \t \n \r \\"
|
||||
key = "\" \t \n \r \\ \/ \b \f"
|
Loading…
Reference in a new issue