/**
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.waveprotocol.wave.model.document.parser;
import org.waveprotocol.wave.model.document.operation.impl.DocOpUtil;
import org.waveprotocol.wave.model.util.CollectionUtils;
import org.waveprotocol.wave.model.util.Pair;
import org.waveprotocol.wave.model.util.Utf16Util;
import java.util.ArrayList;
import java.util.List;
/**
* Parses the serialized annotation format.
*
*/
public class AnnotationParser {
int offset = 0;
String input;
private AnnotationParser(String input) throws XmlParseException {
this.input = input;
ensure(Utf16Util.isValidUtf16(input),
"Input is not valid UTF-16: ", input);
}
/**
* Parses the annotation string into key value pairs.
*
* @param input - for processing instruction <a? annotationStr>, "input" is
* the annotationStr, it doesn't include the <a? or the terminating >
* @return (key, value) pairs, annotation ends have a value of null.
* @throws XmlParseException
*/
public static List<Pair<String, String>> parseAnnotations(String input) throws XmlParseException {
return new AnnotationParser(input).parse();
}
private String quotedValue() throws XmlParseException {
if (input.charAt(offset) == '"') {
return quotedValue('"');
} else {
return quotedValue('\'');
}
}
private void ensure(boolean condition, Object ...messages) throws XmlParseException {
if (!condition) {
String[] toStr = new String[messages.length];
for (int i = 0; i < messages.length; ++i) {
toStr[i] = String.valueOf(messages[i]);
}
String concatenated = CollectionUtils.join(toStr);
throw new XmlParseException(concatenated);
}
}
private String quotedValue(char quoteChar) throws XmlParseException {
ensure(input.charAt(offset) == quoteChar,
"expected ", quoteChar, " at position ", offset, " in ", input);
offset++;
int start = offset;
int nextQuote;
while(true) {
nextQuote = input.indexOf(quoteChar, offset);
ensure(nextQuote != -1, "closing ", quoteChar, " not found");
offset = nextQuote;
if (input.charAt(nextQuote - 1) != '\\') {
break;
}
offset++;
}
String value = input.substring(start, offset);
offset++;
return DocOpUtil.annotationUnEscape(DocOpUtil.xmlTextUnEscape(value));
}
private List<Pair<String, String>> parse() throws XmlParseException {
List<Pair<String, String>> annotations = new ArrayList<Pair<String, String>>();
while (offset < input.length()) {
String name = quotedValue();
ensure(!name.contains("?"), "Invalid char ?: ", input);
ensure(!name.contains("@"), "Invalid char @: ", input);
if (offset < input.length() && input.charAt(offset) == '=') {
offset++;
String value = quotedValue();
annotations.add(new Pair<String, String> (name, value));
} else {
annotations.add(new Pair<String, String> (name, null));
}
while (offset < input.length() && input.charAt(offset) == ' ') {
offset++;
}
}
return annotations;
}
}