use of com.github.mangstadt.vinnie.VObjectProperty in project vinnie by mangstadt.
the class VObjectReaderTest method special_characters_in_group_and_name.
/**
* When there are special characters in the group and property name.
*/
@Test
public void special_characters_in_group_and_name() throws Exception {
// @formatter:off
String string = "g=,\"roup.P.=,\"ROP:value";
for (SyntaxStyle style : SyntaxStyle.values()) {
VObjectReader reader = reader(string, style);
VObjectDataListenerMock listener = spy(new VObjectDataListenerMock());
reader.parse(listener);
InOrder inorder = inOrder(listener);
inorder.verify(listener).onProperty(eq(new VObjectProperty("g=,\"roup", "P.=,\"ROP", "value")), any(Context.class));
// @formatter:off
String[] lines = string.split("\r\n");
int line = 0;
assertContexts(listener, context(lines[line], ++line));
// @formatter:on
}
}
use of com.github.mangstadt.vinnie.VObjectProperty in project vinnie by mangstadt.
the class VObjectReaderTest method parameter_values_in_double_quotes.
/**
* New style syntax lets you surround parameter values in double quotes.
* Doing this lets you put special characters like semi-colons in the
* property value.
*/
@Test
public void parameter_values_in_double_quotes() throws Exception {
// @formatter:off
String string = "PROP;PARAM=\"a;b:c,d\":value";
// @formatter:on
Map<SyntaxStyle, VObjectProperty> styleToProperty = new HashMap<SyntaxStyle, VObjectProperty>();
styleToProperty.put(SyntaxStyle.OLD, property().name("PROP").param("PARAM", "\"a").param(null, "b").value("c,d\":value").build());
styleToProperty.put(SyntaxStyle.NEW, property().name("PROP").param("PARAM", "a;b:c,d").value("value").build());
for (SyntaxStyle style : styleToProperty.keySet()) {
VObjectProperty expectedProperty = styleToProperty.get(style);
VObjectReader reader = reader(string, style);
VObjectDataListenerMock listener = spy(new VObjectDataListenerMock());
reader.parse(listener);
InOrder inorder = inOrder(listener);
inorder.verify(listener).onProperty(eq(expectedProperty), any(Context.class));
// @formatter:off
String[] lines = string.split("\r\n");
int line = 0;
assertContexts(listener, context(lines[line], ++line));
// @formatter:on
}
}
use of com.github.mangstadt.vinnie.VObjectProperty in project vinnie by mangstadt.
the class VObjectReaderTest method syntax_style_rules.
/**
* When a parameter value doesn't have a name.
*/
@Test
public void syntax_style_rules() throws Exception {
// @formatter:off
String string = "VERSION:1\r\n" + "BEGIN:COMP1\r\n" + // default to OLD style
"PROP;PARAM=\"value\":\r\n" + "END:COMP1\r\n" + "BEGIN:COMP1\r\n" + // wrong parent
"VERSION:2\r\n" + "PROP;PARAM=\"value\":\r\n" + "END:COMP1\r\n" + "BEGIN:COMP2\r\n" + // invalid version
"VERSION:3\r\n" + "PROP;PARAM=\"value\":\r\n" + "END:COMP2\r\n" + "BEGIN:COMP2\r\n" + "VERSION:2\r\n" + "PROP;PARAM=\"value\":\r\n" + // keep syntax of parent
"BEGIN:COMP3\r\n" + "PROP;PARAM=\"value\":\r\n" + "END:COMP3\r\n" + // change syntax
"BEGIN:COMP2\r\n" + "VERSION:1\r\n" + "PROP;PARAM=\"value\":\r\n" + "END:COMP2\r\n" + // syntax returns
"PROP;PARAM=\"value\":\r\n" + "END:COMP2\r\n";
// @formatter:on
SyntaxRules rules = new SyntaxRules(SyntaxStyle.OLD);
rules.addRule("COMP2", "1", SyntaxStyle.OLD);
rules.addRule("COMP2", "2", SyntaxStyle.NEW);
VObjectReader reader = new VObjectReader(new StringReader(string), rules);
VObjectDataListenerMock listener = spy(new VObjectDataListenerMock());
reader.parse(listener);
VObjectProperty oldStyleProp = property().name("PROP").param("PARAM", "\"value\"").value("").build();
VObjectProperty newStyleProp = property().name("PROP").param("PARAM", "value").value("").build();
InOrder inorder = inOrder(listener);
// @formatter:off
inorder.verify(listener).onProperty(eq(property().name("VERSION").value("1").build()), any(Context.class));
inorder.verify(listener).onComponentBegin(eq("COMP1"), any(Context.class));
inorder.verify(listener).onProperty(eq(oldStyleProp), any(Context.class));
inorder.verify(listener).onComponentEnd(eq("COMP1"), any(Context.class));
inorder.verify(listener).onComponentBegin(eq("COMP1"), any(Context.class));
inorder.verify(listener).onProperty(eq(property().name("VERSION").value("2").build()), any(Context.class));
inorder.verify(listener).onProperty(eq(oldStyleProp), any(Context.class));
inorder.verify(listener).onComponentEnd(eq("COMP1"), any(Context.class));
inorder.verify(listener).onComponentBegin(eq("COMP2"), any(Context.class));
inorder.verify(listener).onWarning(eq(Warning.UNKNOWN_VERSION), eq(property().name("VERSION").value("3").build()), isNull(Exception.class), any(Context.class));
inorder.verify(listener).onProperty(eq(property().name("VERSION").value("3").build()), any(Context.class));
inorder.verify(listener).onProperty(eq(oldStyleProp), any(Context.class));
inorder.verify(listener).onComponentEnd(eq("COMP2"), any(Context.class));
inorder.verify(listener).onComponentBegin(eq("COMP2"), any(Context.class));
inorder.verify(listener).onVersion(eq("2"), any(Context.class));
inorder.verify(listener).onProperty(eq(newStyleProp), any(Context.class));
inorder.verify(listener).onComponentBegin(eq("COMP3"), any(Context.class));
inorder.verify(listener).onProperty(eq(newStyleProp), any(Context.class));
inorder.verify(listener).onComponentEnd(eq("COMP3"), any(Context.class));
inorder.verify(listener).onComponentBegin(eq("COMP2"), any(Context.class));
inorder.verify(listener).onVersion(eq("1"), any(Context.class));
inorder.verify(listener).onProperty(eq(oldStyleProp), any(Context.class));
inorder.verify(listener).onComponentEnd(eq("COMP2"), any(Context.class));
inorder.verify(listener).onProperty(eq(newStyleProp), any(Context.class));
inorder.verify(listener).onComponentEnd(eq("COMP2"), any(Context.class));
// @formatter:on
// @formatter:off
String[] lines = string.split("\r\n");
int line = 0;
assertContexts(listener, context(lines[line], ++line), context(lines[line], ++line), context(asList("COMP1"), lines[line], ++line), context(lines[line], ++line), context(lines[line], ++line), context(asList("COMP1"), lines[line], ++line), context(asList("COMP1"), lines[line], ++line), context(lines[line], ++line), context(lines[line], ++line), context(asList("COMP2"), lines[line], line + 1), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(lines[line], ++line), context(lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2", "COMP3"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2", "COMP2"), lines[line], ++line), context(asList("COMP2", "COMP2"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(asList("COMP2"), lines[line], ++line), context(lines[line], ++line));
// @formatter:on
}
use of com.github.mangstadt.vinnie.VObjectProperty in project vinnie by mangstadt.
the class VObjectWriterTest method write_VObjectProperty.
@Test
public void write_VObjectProperty() throws Exception {
for (SyntaxStyle style : SyntaxStyle.values()) {
StringWriter sw = new StringWriter();
VObjectWriter writer = new VObjectWriter(sw, style);
VObjectProperty property = new VObjectProperty();
property.setGroup("group");
property.setName("PROP");
property.getParameters().put("PARAM", "pvalue");
property.setValue("value");
writer.writeProperty(property);
String actual = sw.toString();
// @formatter:off
String expected = "group.PROP;PARAM=pvalue:value\r\n";
// @formatter:on
assertEquals(expected, actual);
}
}
use of com.github.mangstadt.vinnie.VObjectProperty in project vinnie by mangstadt.
the class VObjectReader method parseProperty.
/**
* Parses the next property off the input stream.
* @param listener the data listener (for reporting warnings)
* @return the parsed property or null if the property could not be parsed
* @throws IOException if there was a problem reading from the input stream
*/
private VObjectProperty parseProperty(VObjectDataListener listener) throws IOException {
VObjectProperty property = new VObjectProperty();
/*
* The syntax style to assume the data is in.
*/
SyntaxStyle syntax = stack.peekSyntax();
/*
* The name of the parameter we're currently inside of.
*/
String curParamName = null;
/*
* The character that was used to escape the current character (for
* parameter values).
*/
char paramValueEscapeChar = 0;
/*
* Are we currently inside a parameter value that is surrounded with
* double-quotes?
*/
boolean inQuotes = false;
/*
* Are we currently inside the property value?
*/
boolean inValue = false;
/*
* Does the line use quoted-printable encoding, and does it end all of
* its folded lines with a "=" character?
*/
boolean foldedQuotedPrintableLine = false;
/*
* Are we currently inside the whitespace that prepends a folded line?
*/
boolean inFoldedLineWhitespace = false;
/*
* The current character.
*/
char ch = 0;
/*
* The previous character.
*/
char prevChar;
while (true) {
prevChar = ch;
int read = nextChar();
if (read < 0) {
// end of stream
eos = true;
break;
}
ch = (char) read;
if (prevChar == '\r' && ch == '\n') {
/*
* The newline was already processed when the "\r" character was
* encountered, so ignore the accompanying "\n" character.
*/
continue;
}
if (isNewline(ch)) {
foldedQuotedPrintableLine = (inValue && prevChar == '=' && property.getParameters().isQuotedPrintable());
if (foldedQuotedPrintableLine) {
/*
* Remove the "=" character that sometimes appears at the
* end of quoted-printable lines that are followed by a
* folded line.
*/
buffer.chop();
context.unfoldedLine.chop();
}
// keep track of the current line number
lineNumber++;
continue;
}
if (isNewline(prevChar)) {
if (isWhitespace(ch)) {
/*
* This line is a continuation of the previous line (the
* line is folded).
*/
inFoldedLineWhitespace = true;
continue;
}
if (foldedQuotedPrintableLine) {
/*
* The property's parameters indicate that the property
* value is quoted-printable. And the previous line ended
* with an equals sign. This means that folding whitespace
* may not be prepended to folded lines like it should.
*/
} else {
/*
* We're reached the end of the property.
*/
leftOver = ch;
break;
}
}
if (inFoldedLineWhitespace) {
if (isWhitespace(ch) && syntax == SyntaxStyle.OLD) {
/*
* 2.1 allows multiple whitespace characters to be used for
* folding (section 2.1.3).
*/
continue;
}
inFoldedLineWhitespace = false;
}
context.unfoldedLine.append(ch);
if (inValue) {
buffer.append(ch);
continue;
}
// decode escaped parameter value character
if (paramValueEscapeChar != 0) {
char escapeChar = paramValueEscapeChar;
paramValueEscapeChar = 0;
switch(escapeChar) {
case '\\':
switch(ch) {
case '\\':
buffer.append(ch);
continue;
case ';':
/*
* Semicolons can only be escaped in old style parameter
* values. If a new style parameter value has
* semicolons, the value should be surrounded in double
* quotes.
*/
buffer.append(ch);
continue;
}
break;
case '^':
switch(ch) {
case '^':
buffer.append(ch);
continue;
case 'n':
buffer.append(NEWLINE);
continue;
case '\'':
buffer.append('"');
continue;
}
break;
}
/*
* Treat the escape character as a normal character because it's
* not a valid escape sequence.
*/
buffer.append(escapeChar).append(ch);
continue;
}
// check for a parameter value escape character
if (curParamName != null) {
switch(syntax) {
case OLD:
if (ch == '\\') {
paramValueEscapeChar = ch;
continue;
}
break;
case NEW:
if (ch == '^' && caretDecodingEnabled) {
paramValueEscapeChar = ch;
continue;
}
break;
}
}
// set the group
if (ch == '.' && property.getGroup() == null && property.getName() == null) {
property.setGroup(buffer.getAndClear());
continue;
}
if ((ch == ';' || ch == ':') && !inQuotes) {
if (property.getName() == null) {
// set the property name
property.setName(buffer.getAndClear());
} else {
// set a parameter value
String paramValue = buffer.getAndClear();
if (syntax == SyntaxStyle.OLD) {
// old style allows whitespace to surround the "=", so remove it
paramValue = ltrim(paramValue);
}
property.getParameters().put(curParamName, paramValue);
curParamName = null;
}
if (ch == ':') {
// the rest of the line is the property value
inValue = true;
}
continue;
}
if (property.getName() != null) {
// it's a multi-valued parameter
if (ch == ',' && curParamName != null && !inQuotes && syntax != SyntaxStyle.OLD) {
String paramValue = buffer.getAndClear();
property.getParameters().put(curParamName, paramValue);
continue;
}
// set the parameter name
if (ch == '=' && curParamName == null) {
String paramName = buffer.getAndClear().toUpperCase();
if (syntax == SyntaxStyle.OLD) {
// old style allows whitespace to surround the "=", so remove it
paramName = rtrim(paramName);
}
curParamName = paramName;
continue;
}
// entering/leaving a double-quoted parameter value (new style only)
if (ch == '"' && curParamName != null && syntax != SyntaxStyle.OLD) {
inQuotes = !inQuotes;
continue;
}
}
buffer.append(ch);
}
/*
* Line or stream ended before the property value was reached.
*/
if (!inValue) {
return null;
}
property.setValue(buffer.getAndClear());
if (property.getParameters().isQuotedPrintable()) {
decodeQuotedPrintable(property, listener);
}
return property;
}
Aggregations